Linux socket使用-聊天系統(tǒng)
聲明:謝絕一切形式的轉(zhuǎn)載
socket套接字
socket起源于Unix,而Unix/Linux基本哲學(xué)之一就是“一切皆文件”,都可以用“打開open –> 讀寫write/read –> 關(guān)閉close”模式來操作。Socket就是該模式的一個(gè)實(shí)現(xiàn), socket即是一種特殊的文件,一些socket函數(shù)就是對(duì)其進(jìn)行的操作(讀/寫IO、打開、關(guān)閉). 說白了Socket是應(yīng)用層與TCP/IP協(xié)議族通信的中間軟件抽象層,它是一組接口。在設(shè)計(jì)模式中,Socket其實(shí)就是一個(gè)門面模式,它把復(fù)雜的TCP/IP協(xié)議族隱藏在Socket接口后面,對(duì)用戶來說,一組簡(jiǎn)單的接口就是全部,讓Socket去組織數(shù)據(jù),以符合指定的協(xié)議。

TCP/IP
TCP三次握手
tcp的三次握手如下所示。

有很多面試官很特別容易問為什么兩次不行?個(gè)人感覺,兩次也不是不行。只是在有些情況下會(huì)產(chǎn)生無效的連接。 比如客戶端發(fā)起連接,由于網(wǎng)絡(luò)阻塞,服務(wù)器一直沒有收到連接請(qǐng)求,客戶端沒有收到任何回應(yīng),啟動(dòng)重傳機(jī)制,又發(fā)起了一次連接,這次很順利,然后服務(wù)端正常返回信息,第二次的連接成功了??墒?,萬萬沒想到,那個(gè)阻塞的連接經(jīng)過跋山涉水終于到了服務(wù)器端,此時(shí)服務(wù)器作何處理那? 服務(wù)器端認(rèn)為你咋又發(fā)了個(gè)請(qǐng)求呀,于是再次進(jìn)行確認(rèn),客戶端收到一條服務(wù)器端的一個(gè)確認(rèn),不予理會(huì)。可服務(wù)器卻堅(jiān)持認(rèn)為連接已經(jīng)成功,等待客戶端傳輸數(shù)據(jù),于是服務(wù)器的許多資源就浪費(fèi)了。
抓包分析
個(gè)人認(rèn)為,涉及到tcp/ip原理講解的,空講理論略顯不足,甚至有點(diǎn)"耍流氓"的感覺。"三次握手,四次揮手"這種理論書本中講述的很清楚了,下面以WireShark工具見證一下這個(gè)理論。
三次握手

通過上面可以看出三次握手的流程
客戶端向服務(wù)器端發(fā)送[SYN],seq=0
服務(wù)端向客戶端返回[SYN,ACK] Ack=1 seq=0
客戶端向服務(wù)器端返回[ACK] Ack=1
四次揮手


tcp狀態(tài)變遷圖

基本的socket函數(shù)
使用socket編程用到的函數(shù)和基本流程如下:

注意:還有兩個(gè)函數(shù)send和recieve。這兩個(gè)函數(shù)比較容易望文生義,認(rèn)為是send把數(shù)據(jù)發(fā)送出去。其實(shí)不是這樣子的,只有當(dāng)send把socket的發(fā)送緩沖區(qū)填滿的時(shí)候數(shù)據(jù)才進(jìn)行發(fā)送,通過TCP/IP協(xié)議進(jìn)行發(fā)送。進(jìn)行數(shù)據(jù)傳輸?shù)氖菂f(xié)議。
簡(jiǎn)單聊天系統(tǒng)
前面的理論知識(shí)還是很有必要了解的。之后開發(fā)程序就簡(jiǎn)單多了。先看以下效果:

服務(wù)器端代碼
/*************************************************************************
> File Name: ser.c
> Author: 無情劍客
> Mail: 2250911301@qq.com
> Created Time: 2020年7月1日21時(shí)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;
? ?//創(chuàng)建一個(gè)socket描述符,此描述符僅是本主機(jī)上的一個(gè)普通文件描述符而已
? ?listenfd = socket(AF_INET, SOCK_STREAM, 0);
? ?//定義一個(gè)結(jié)構(gòu)體變量servaddr,用來記錄給定的IP和port信息,為bind函數(shù)做準(zhǔn)備
? ?struct sockaddr_in serveraddr;
? ?bzero(&serveraddr, sizeof(serveraddr));
? ?serveraddr.sin_family = AF_INET;
? ?serveraddr.sin_port = htons(PORT); //把端口轉(zhuǎn)化為網(wǎng)絡(luò)字節(jié)序,即大端模式
? ?serveraddr.sin_addr.s_addr = INADDR_ANY;
? ?//把“本地含義的描述符”綁定到一個(gè)IP和Port上,此時(shí)這個(gè)socket才具備對(duì)外連接的能力
? ?bind(listenfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
? ?//創(chuàng)建一個(gè)監(jiān)聽隊(duì)列,用來保存用戶的請(qǐng)求連接信息(ip、port、protocol)
? ?listen(listenfd, BACKLOG);
? ?printf("======端口綁定成功,等待客戶端的連接======\n");
? ?//讓操作系統(tǒng)回填client的連接信息(ip、port、protocol)
? ?struct sockaddr_in peeraddr;
? ?socklen_t peer_len = sizeof(peeraddr);
? ?int connfd;
? ?while(1)
? ?{
? ? ? ?//accept函數(shù)從listen函數(shù)維護(hù)的監(jiān)聽隊(duì)列里取一個(gè)客戶連接請(qǐng)求處理
? ? ? ?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("客戶端已經(jīng)關(guān)閉!\n");
? ? ? ? ? ? ? ?break;
? ? ? ? ? ?}
? ? ? ? ? ?char vul_buffer[64] ;
? ? ? ? ? ?strcpy(vul_buffer,buf);
? ? ? ? ? ?printf("客戶端說: ");
? ? ? ? ? ?fputs(vul_buffer, stdout);
? ? ? ? ? ?memset(buf, '\0', MAXDATASIZE/sizeof (char));
? ? ? ? ? ?printf("請(qǐng)輸入: ");
? ? ? ? ? ?fgets(buf, sizeof(buf), stdin);
? ? ? ? ? ?send(connfd, buf, recv_length, 0);
? ? ? ?}
? ? ? ?close(connfd);
? ? ? ?close(listenfd);
? ? ? ?return 0;
? ?}
}
這里使用了strcpy這個(gè)危險(xiǎn)函數(shù),后續(xù)文章會(huì)針對(duì)這個(gè)漏洞進(jìn)行攻擊。
客戶端代碼
/*************************************************************************
> File Name: client.c
> Author: 無情劍客
> Mail: 2250911301@qq.com
> Created Time: 2020年07月01日 星期三 21時(shí)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, "請(qǐng)您輸入ip地址!\n");
? ? ? ?exit(1);
? ?}
? ?int sockfd;
? ?sockfd = socket(AF_INET, SOCK_STREAM, 0);
? ?const char *server_ip = argv[1]; //從命令行獲取輸入的ip地址,此處沒有對(duì)ip地址進(jìn)行檢驗(yàn)
? ?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("=====================服務(wù)器鏈接成功=====================\n");
? ?char buf[MAXDATASIZE];
? ?memset(buf, 0 , ?sizeof(buf));
? ?printf("請(qǐng)輸入: ");
? ?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("服務(wù)器說: ");
? ? ? ?fputs(buf, stdout);
? ? ? ?memset(buf, 0, sizeof(buf));
? ? ? ?printf("請(qǐng)輸入: ");
? ?}
? ?printf("客戶端將要被關(guān)閉,下次再見\n");
? ?close(sockfd);
? ?return 0;
}
使用如下命令進(jìn)行編譯
gcc -o client client.c && gcc -o server ?server.c
公眾號(hào)
更多網(wǎng)絡(luò)安全,歡迎關(guān)注我的公眾號(hào):無情劍客
