日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > linux >内容正文

linux

【Linux网络编程】TCP网络编程中connect listen和accept三者之间的关系

發(fā)布時(shí)間:2024/4/24 linux 59 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【Linux网络编程】TCP网络编程中connect listen和accept三者之间的关系 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

00. 目錄

文章目錄

    • 00. 目錄
    • 01. TCP服務(wù)端和客戶端流程
    • 02. connect函數(shù)
    • 03. listen函數(shù)
    • 04. 三次握手
    • 05. accept函數(shù)
    • 06. 附錄

01. TCP服務(wù)端和客戶端流程

02. connect函數(shù)

對(duì)于客戶端的 connect() 函數(shù),該函數(shù)的功能為客戶端主動(dòng)連接服務(wù)器,建立連接是通過三次握手,而這個(gè)連接的過程是由內(nèi)核完成,不是這個(gè)函數(shù)完成的,這個(gè)函數(shù)的作用僅僅是通知 Linux 內(nèi)核,讓 Linux 內(nèi)核自動(dòng)完成 TCP 三次握手連接,最后把連接的結(jié)果返回給這個(gè)函數(shù)的返回值(成功連接為0, 失敗為-1)。

通常的情況,客戶端的 connect() 函數(shù)默認(rèn)會(huì)一直阻塞,直到三次握手成功或超時(shí)失敗才返回(正常的情況,這個(gè)過程很快完成)。

03. listen函數(shù)

對(duì)于服務(wù)器,它是被動(dòng)連接的。舉一個(gè)生活中的例子,通常的情況下,移動(dòng)的客服(相當(dāng)于服務(wù)器)是等待著客戶(相當(dāng)于客戶端)電話的到來。而這個(gè)過程,需要調(diào)用listen()函數(shù)。

listen() 函數(shù)的主要作用就是將套接字( sockfd )變成被動(dòng)的連接監(jiān)聽套接字(被動(dòng)等待客戶端的連接),至于參數(shù) backlog 的作用是設(shè)置內(nèi)核中連接隊(duì)列的長(zhǎng)度,TCP 三次握手也不是由這個(gè)函數(shù)完成,listen()的作用僅僅告訴內(nèi)核一些信息。

這里需要注意的是,listen()函數(shù)不會(huì)阻塞,它主要做的事情為,將該套接字和套接字對(duì)應(yīng)的連接隊(duì)列長(zhǎng)度告訴 Linux 內(nèi)核,然后,listen()函數(shù)就結(jié)束。

這樣的話,當(dāng)有一個(gè)客戶端主動(dòng)連接(connect()),Linux 內(nèi)核就自動(dòng)完成TCP 三次握手,將建立好的鏈接自動(dòng)存儲(chǔ)到隊(duì)列中,如此重復(fù)。

所以,只要 TCP 服務(wù)器調(diào)用了 listen(),客戶端就可以通過 connect() 和服務(wù)器建立連接,而這個(gè)連接的過程是由內(nèi)核完成。

下面為測(cè)試的服務(wù)器和客戶端代碼,運(yùn)行程序時(shí),要先運(yùn)行服務(wù)器,再運(yùn)行客戶端:

服務(wù)器

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int main(int argc, char *argv[]) {unsigned short port = 8000; int sockfd;sockfd = socket(AF_INET, SOCK_STREAM, 0);// 創(chuàng)建通信端點(diǎn):套接字if(sockfd < 0){perror("socket");exit(-1);}struct sockaddr_in my_addr;bzero(&my_addr, sizeof(my_addr)); my_addr.sin_family = AF_INET;my_addr.sin_port = htons(port);my_addr.sin_addr.s_addr = htonl(INADDR_ANY);int err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));if( err_log != 0){perror("binding");close(sockfd); exit(-1);}err_log = listen(sockfd, 10);if(err_log != 0){perror("listen");close(sockfd); exit(-1);} printf("listen client @port=%d...\n",port);sleep(10); // 延時(shí)10ssystem("netstat -an | grep 8000"); // 查看連接狀態(tài)return 0; }

客戶端:

#include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <arpa/inet.h> #include <sys/socket.h> #include <netinet/in.h> int main(int argc, char *argv[]) {unsigned short port = 8000; // 服務(wù)器的端口號(hào)char *server_ip = "10.221.20.12"; // 服務(wù)器ip地址int sockfd;sockfd = socket(AF_INET, SOCK_STREAM, 0);// 創(chuàng)建通信端點(diǎn):套接字if(sockfd < 0){perror("socket");exit(-1);}struct sockaddr_in server_addr;bzero(&server_addr,sizeof(server_addr)); // 初始化服務(wù)器地址server_addr.sin_family = AF_INET;server_addr.sin_port = htons(port);inet_pton(AF_INET, server_ip, &server_addr.sin_addr);int err_log = connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)); // 主動(dòng)連接服務(wù)器if(err_log != 0){perror("connect");close(sockfd);exit(-1);}system("netstat -an | grep 8000"); // 查看連接狀態(tài)while(1);return 0; }

運(yùn)行程序時(shí),要先運(yùn)行服務(wù)器,再運(yùn)行客戶端,運(yùn)行結(jié)果如下:

04. 三次握手

這里詳細(xì)的介紹一下 listen() 函數(shù)的第二個(gè)參數(shù)( backlog)的作用:告訴內(nèi)核連接隊(duì)列的長(zhǎng)度。

為了更好的理解 backlog 參數(shù),我們必須認(rèn)識(shí)到內(nèi)核為任何一個(gè)給定的監(jiān)聽套接字維護(hù)兩個(gè)隊(duì)列:

1、未完成連接隊(duì)列(incomplete connection queue),每個(gè)這樣的 SYN 分節(jié)對(duì)應(yīng)其中一項(xiàng):已由某個(gè)客戶發(fā)出并到達(dá)服務(wù)器,而服務(wù)器正在等待完成相應(yīng)的 TCP 三次握手過程。這些套接字處于 SYN_RCVD 狀態(tài)。

2、已完成連接隊(duì)列(completed connection queue),每個(gè)已完成 TCP 三次握手過程的客戶對(duì)應(yīng)其中一項(xiàng)。這些套接口處于 ESTABLISHED 狀態(tài)。

當(dāng)來自客戶的 SYN 到達(dá)時(shí),TCP 在未完成連接隊(duì)列中創(chuàng)建一個(gè)新項(xiàng),然后響應(yīng)以三次握手的第二個(gè)分節(jié):服務(wù)器的 SYN 響應(yīng),其中稍帶對(duì)客戶 SYN 的 ACK(即SYN+ACK),這一項(xiàng)一直保留在未完成連接隊(duì)列中,直到三次握手的第三個(gè)分節(jié)(客戶對(duì)服務(wù)器 SYN 的 ACK )到達(dá)或者該項(xiàng)超時(shí)為止(曾經(jīng)源自Berkeley的實(shí)現(xiàn)為這些未完成連接的項(xiàng)設(shè)置的超時(shí)值為75秒)。

如果三次握手正常完成,該項(xiàng)就從未完成連接隊(duì)列移到已完成連接隊(duì)列的隊(duì)尾。

backlog 參數(shù)歷史上被定義為上面兩個(gè)隊(duì)列的大小之和,大多數(shù)實(shí)現(xiàn)默認(rèn)值為 5,當(dāng)服務(wù)器把這個(gè)完成連接隊(duì)列的某個(gè)連接取走后,這個(gè)隊(duì)列的位置又空出一個(gè),這樣來回實(shí)現(xiàn)動(dòng)態(tài)平衡,但在高并發(fā) web 服務(wù)器中此值顯然不夠。

05. accept函數(shù)

accept()函數(shù)功能是從處于 established 狀態(tài)的連接隊(duì)列頭部取出一個(gè)已經(jīng)完成的連接,如果這個(gè)隊(duì)列沒有已經(jīng)完成的連接,accept()函數(shù)就會(huì)阻塞,直到取出隊(duì)列中已完成的用戶連接為止。

如果,服務(wù)器不能及時(shí)調(diào)用 accept() 取走隊(duì)列中已完成的連接,隊(duì)列滿掉后會(huì)怎樣呢?UNP(《unix網(wǎng)絡(luò)編程》)告訴我們,服務(wù)器的連接隊(duì)列滿掉后,服務(wù)器不會(huì)對(duì)再對(duì)建立新連接的syn進(jìn)行應(yīng)答,所以客戶端的 connect 就會(huì)返回 ETIMEDOUT。但實(shí)際上Linux的并不是這樣的!

下面為測(cè)試代碼,服務(wù)器 listen() 函數(shù)只指定隊(duì)列長(zhǎng)度為 2,客戶端有 6 個(gè)不同的套接字主動(dòng)連接服務(wù)器,同時(shí),保證客戶端的 6 個(gè) connect()函數(shù)都先調(diào)用完畢,服務(wù)器的 accpet() 才開始調(diào)用。

服務(wù)器

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int main(int argc, char *argv[]) {unsigned short port = 8000; int sockfd = socket(AF_INET, SOCK_STREAM, 0); if(sockfd < 0){perror("socket");exit(-1);}struct sockaddr_in my_addr;bzero(&my_addr, sizeof(my_addr)); my_addr.sin_family = AF_INET;my_addr.sin_port = htons(port);my_addr.sin_addr.s_addr = htonl(INADDR_ANY);int err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));if( err_log != 0){perror("binding");close(sockfd); exit(-1);}err_log = listen(sockfd, 2); // 等待隊(duì)列為2if(err_log != 0){perror("listen");close(sockfd); exit(-1);} printf("after listen\n");sleep(20); //延時(shí) 20秒printf("listen client @port=%d...\n",port);int i = 0;while(1){ struct sockaddr_in client_addr; char cli_ip[INET_ADDRSTRLEN] = ""; socklen_t cliaddr_len = sizeof(client_addr); int connfd;connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len); if(connfd < 0){perror("accept");continue;}inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);printf("-----------%d------\n", ++i);printf("client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port));char recv_buf[512] = {0};while( recv(connfd, recv_buf, sizeof(recv_buf), 0) > 0 ){printf("recv data ==%s\n",recv_buf);break;}close(connfd); //關(guān)閉已連接套接字//printf("client closed!\n");}close(sockfd); //關(guān)閉監(jiān)聽套接字return 0; }

客戶端

#include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <arpa/inet.h> #include <sys/socket.h> #include <netinet/in.h>void test_connect() {unsigned short port = 8000; // 服務(wù)器的端口號(hào)char *server_ip = "10.221.20.12"; // 服務(wù)器ip地址int sockfd;sockfd = socket(AF_INET, SOCK_STREAM, 0);// 創(chuàng)建通信端點(diǎn):套接字if(sockfd < 0){perror("socket");exit(-1);}struct sockaddr_in server_addr;bzero(&server_addr,sizeof(server_addr)); // 初始化服務(wù)器地址server_addr.sin_family = AF_INET;server_addr.sin_port = htons(port);inet_pton(AF_INET, server_ip, &server_addr.sin_addr);int err_log = connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)); // 主動(dòng)連接服務(wù)器if(err_log != 0){perror("connect");close(sockfd);exit(-1);}printf("err_log ========= %d\n", err_log);char send_buf[100]="this is for test";send(sockfd, send_buf, strlen(send_buf), 0); // 向服務(wù)器發(fā)送信息system("netstat -an | grep 8000"); // 查看連接狀態(tài)//close(sockfd); }int main(int argc, char *argv[]) {pid_t pid;pid = fork();if(0 == pid){test_connect(); // 1pid_t pid = fork();if(0 == pid){test_connect(); // 2}else if(pid > 0){test_connect(); // 3}}else if(pid > 0){test_connect(); // 4pid_t pid = fork();if(0 == pid){test_connect(); // 5}else if(pid > 0){test_connect(); // 6}}while(1);return 0; }

同樣是先運(yùn)行服務(wù)器,在運(yùn)行客戶端,服務(wù)器 accept()函數(shù)前延時(shí)了 20 秒, 保證了客戶端的 connect() 全部調(diào)用完畢后再調(diào)用 accept(),運(yùn)行結(jié)果如下:


客戶端運(yùn)行結(jié)果

按照 UNP 的說法,連接隊(duì)列滿后(這里設(shè)置長(zhǎng)度為 2,發(fā)了 6 個(gè)連接),以后再調(diào)用 connect() 應(yīng)該統(tǒng)統(tǒng)超時(shí)失敗,但實(shí)際上測(cè)試結(jié)果是:有的 connect()立刻成功返回了,有的經(jīng)過明顯延遲后成功返回了。對(duì)于服務(wù)器 accpet() 函數(shù)也是這樣的結(jié)果:有的立馬成功返回,有的延遲后成功返回

對(duì)于上面服務(wù)器的代碼,我們把lisen()的第二個(gè)參數(shù)改為 0 的數(shù),重新運(yùn)行程序,發(fā)現(xiàn)客戶端 connect() 全部返回連接成功(有些會(huì)延時(shí))

服務(wù)器 accpet() 函數(shù)卻不能把連接隊(duì)列的所有連接都取出來:
圖片保存下來直接上傳(img-i12tqYDQ-1595920060035)(assets/image-

對(duì)于上面服務(wù)器的代碼,我們把lisen()的第二個(gè)參數(shù)改為大于 6 的數(shù)(如 10),重新運(yùn)行程序,發(fā)現(xiàn),客戶端 connect() 立馬返回連接成功, 服務(wù)器 accpet() 函數(shù)也立馬返回成功。

TCP 的連接隊(duì)列滿后,Linux 不會(huì)如書中所說的拒絕連接,只是有些會(huì)延時(shí)連接,而且accept()未必能把已經(jīng)建立好的連接全部取出來(如:當(dāng)隊(duì)列的長(zhǎng)度指定為 0 ),寫程序時(shí)服務(wù)器的 listen() 的第二個(gè)參數(shù)最好還是根據(jù)需要填寫,寫太大不好(具體可以看cat /proc/sys/net/core/somaxconn,默認(rèn)最大值限制是 128),浪費(fèi)資源,寫太小也不好,延時(shí)建立連接。

06. 附錄

5.1【Linux】一步一步學(xué)Linux網(wǎng)絡(luò)編程教程匯總

總結(jié)

以上是生活随笔為你收集整理的【Linux网络编程】TCP网络编程中connect listen和accept三者之间的关系的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。