聲明:謝絕一切形式的轉載
socket套接字
socket起源於Unix,而Unix/Linux基本哲學之一就是“一切皆文件”,都可以用“打開open –> 讀寫write/read –> 關閉close”模式來操作。Socket就是該模式的一個實現, socket即是一種特殊的文件,一些socket函數就是對其進行的操作(讀/寫IO、打開、關閉). 說白了Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket接口後面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議。
TCP/IP
TCP三次握手
tcp的三次握手如下所示。
有很多面試官很特別容易問爲什麼兩次不行?個人感覺,兩次也不是不行。只是在有些情況下會產生無效的連接。
比如客戶端發起連接,由於網絡阻塞,服務器一直沒有收到連接請求,客戶端沒有收到任何迴應,啓動重傳機制,又發起了一次連接,這次很順利,然後服務端正常返回信息,第二次的連接成功了。可是,萬萬沒想到,那個阻塞的連接經過跋山涉水終於到了服務器端,此時服務器作何處理那?
服務器端認爲你咋又發了個請求呀,於是再次進行確認,客戶端收到一條服務器端的一個確認,不予理會。可服務器卻堅持認爲連接已經成功,等待客戶端傳輸數據,於是服務器的許多資源就浪費了。
抓包分析
個人認爲,涉及到tcp/ip原理講解的,空講理論略顯不足,甚至有點"耍流氓"的感覺。"三次握手,四次揮手"這種理論書本中講述的很清楚了,下面以WireShark工具見證一下這個理論。
三次握手
通過上面可以看出三次握手的流程
- 客戶端向服務器端發送[SYN],seq=0
- 服務端向客戶端返回[SYN,ACK] Ack=1 seq=0
- 客戶端向服務器端返回[ACK] Ack=1
四次揮手
tcp狀態變遷圖
基本的socket函數
使用socket編程用到的函數和基本流程如下:
注意:還有兩個函數send和recieve。這兩個函數比較容易望文生義,認爲是send把數據發送出去。其實不是這樣子的,只有當send把socket的發送緩衝區填滿的時候數據才進行發送,通過TCP/IP協議進行發送。進行數據傳輸的是協議。
簡單聊天系統
前面的理論知識還是很有必要了解的。之後開發程序就簡單多了。先看以下效果:
服務器端代碼
/*************************************************************************
> File Name: server.c
> Author: 無情劍客
> Mail: [email protected]
> Created Time: 2020年7月1日21時43分10秒
************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#define PORT 6666
#define BACKLOG 10
#define MAXDATASIZE 2048
int main(int argc, char *argv[])
{
int listenfd;
//創建一個socket描述符,此描述符僅是本主機上的一個普通文件描述符而已
listenfd = socket(AF_INET, SOCK_STREAM, 0);
//定義一個結構體變量servaddr,用來記錄給定的IP和port信息,爲bind函數做準備
struct sockaddr_in serveraddr;
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(PORT); //把端口轉化爲網絡字節序,即大端模式
serveraddr.sin_addr.s_addr = INADDR_ANY;
//把“本地含義的描述符”綁定到一個IP和Port上,此時這個socket才具備對外連接的能力
bind(listenfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
//創建一個監聽隊列,用來保存用戶的請求連接信息(ip、port、protocol)
listen(listenfd, BACKLOG);
printf("======端口綁定成功,等待客戶端的連接======\n");
//讓操作系統回填client的連接信息(ip、port、protocol)
struct sockaddr_in peeraddr;
socklen_t peer_len = sizeof(peeraddr);
int connfd;
while(1)
{
//accept函數從listen函數維護的監聽隊列裏取一個客戶連接請求處理
connfd = accept(listenfd, (struct sockaddr*)&peeraddr, &peer_len);
printf("\n=====================客戶端鏈接成功=====================\n");
printf("IP = %s:PORT = %d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
char buf[MAXDATASIZE];
while(1)
{
memset(buf, '\0', MAXDATASIZE/sizeof (char));
int recv_length = recv(connfd, buf, MAXDATASIZE/sizeof (char), 0);
if(recv_length == 0)
{
printf("客戶端已經關閉!\n");
break;
}
char vul_buffer[64] ;
strcpy(vul_buffer,buf);
printf("客戶端說: ");
fputs(vul_buffer, stdout);
memset(buf, '\0', MAXDATASIZE/sizeof (char));
printf("請輸入: ");
fgets(buf, sizeof(buf), stdin);
send(connfd, buf, recv_length, 0);
}
close(connfd);
close(listenfd);
return 0;
}
}
這裏使用了strcpy這個危險函數,後續文章會針對這個漏洞進行攻擊。
客戶端代碼
/*************************************************************************
> File Name: client.c
> Author: 無情劍客
> Mail: [email protected]
> Created Time: 2020年07月01日 星期三 21時44分37秒
************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<netinet/in.h>
#define PORT 6666
#define MAXDATASIZE 2048
int main(int argc, char *argv[])
{
if(argc != 2)
{
fprintf(stderr, "請您輸入ip地址!\n");
exit(1);
}
int sockfd;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
const char *server_ip = argv[1]; //從命令行獲取輸入的ip地址,此處沒有對ip地址進行檢驗
struct sockaddr_in serveraddr;
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(PORT);
inet_pton(AF_INET, server_ip, &serveraddr.sin_addr);
connect(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
printf("=====================服務器鏈接成功=====================\n");
char buf[MAXDATASIZE];
memset(buf, 0 , sizeof(buf));
printf("請輸入: ");
while(fgets(buf, sizeof(buf), stdin) != NULL && (strcmp(buf, "quit")))
{
send(sockfd, buf, sizeof(buf), 0);
memset(buf, 0, sizeof(buf));
recv(sockfd, buf, sizeof(buf), 0);
printf("服務器說: ");
fputs(buf, stdout);
memset(buf, 0, sizeof(buf));
printf("請輸入: ");
}
printf("客戶端將要被關閉,下次再見\n");
close(sockfd);
return 0;
}
使用如下命令進行編譯
gcc -o client client.c && gcc -o server server.c
公衆號
瞭解更多網絡安全內容,歡迎關注我的公衆號:無情劍客