作者:反反复复-念念不忘 | 来源:互联网 | 2023-07-05 15:36
- #include
- int listen(int sockfd, int backlog);
-
listen() 函数的主要作用就是将套接字( sockfd )变成被动连接的监听套接字(被动等待客户端的连接),至于参数 backlog 的作用是设置内核中连接队列的长度(这个长度有什么用,后面做详细的解释),TCP 三次握手也不是由这个函数完成,listen()的作用仅仅告诉内核一些信息。
这里需要注意的是,listen()函数不会阻塞,它主要做的事情为,将该套接字和套接字对应的连接队列长度告诉 Linux 内核,然后,listen()函数就结束。
这样的话,当有一个客户端主动连接(connect()),Linux 内核就自动完成TCP 三次握手,将建立好的链接自动存储到队列中,如此重复。
所以,只要 TCP 服务器调用了 listen(),客户端就可以通过 connect() 和服务器建立连接,而这个连接的过程是由内核完成。
下面为测试的服务器和客户端代码,运行程序时,要先运行服务器,再运行客户端:
服务器代码:
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- int main(int argc, char *argv[])
- {
- unsigned short port = 8000;
-
- int sockfd;
- 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 &#61; AF_INET;
- my_addr.sin_port &#61; htons(port);
- my_addr.sin_addr.s_addr &#61; htonl(INADDR_ANY);
-
- int err_log &#61; bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
- if( err_log !&#61; 0)
- {
- perror("binding");
- close(sockfd);
- exit(-1);
- }
-
- err_log &#61; listen(sockfd, 0);
- if(err_log !&#61; 0)
- {
- perror("listen");
- close(sockfd);
- exit(-1);
- }
-
- printf("listen client &#64;port&#61;%d...\n",port);
-
- sleep(10);
-
- system("netstat -an | grep 8000");
-
- return 0;
- }
客户端的代码&#xff1a;
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- int main(int argc, char *argv[])
- {
- unsigned short port &#61; 8000;
- char *server_ip &#61; "10.221.20.23";
-
- int sockfd;
- sockfd &#61; socket(AF_INET, SOCK_STREAM, 0);
- if(sockfd < 0)
- {
- perror("socket");
- exit(-1);
- }
-
- struct sockaddr_in server_addr;
- bzero(&server_addr,sizeof(server_addr));
- server_addr.sin_family &#61; AF_INET;
- server_addr.sin_port &#61; htons(port);
- inet_pton(AF_INET, server_ip, &server_addr.sin_addr.s_addr);
-
- int err_log &#61; connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
- if(err_log !&#61; 0)
- {
- perror("connect");
- close(sockfd);
- exit(-1);
- }
-
- system("netstat -an | grep 8000");
- while(1);
- return 0;
- }
运行时先运行服务器&#xff0c;再运行客户端&#xff0c;运行结果如下
三次握手的连接队列
这里详细的介绍一下 listen() 函数的第二个参数&#xff08; backlog&#xff09;的作用&#xff1a;告诉内核连接队列的长度。
为了更好的理解 backlog 参数&#xff0c;我们必须认识到内核为任何一个给定的监听套接口维护一个队列&#xff0c;该队列由两部分构成&#xff0c;分别是完成连接接队列、未完成连接队列&#xff1a;
1、未完成连接队列&#xff08;incomplete connection queue&#xff09;&#xff0c;当服务器每收到客户端的一个SYN分节&#xff0c;就会将该客户端放入未完成连接队列&#xff0c;而服务器套接口处于 SYN_RCVD 状态。
2、已完成连接队列&#xff08;completed connection queue&#xff09;&#xff0c;当客户端和服务器彻底完成三次握手过程&#xff0c;客户端将从未完成连接队列升级成已完成连接队列&#xff0c;并从未完成连接队列中清空该客户端&#xff0c;这些套接口处于 ESTABLISHED 状态。
当来自客户的 SYN 到达时&#xff0c;TCP 在未完成连接队列中创建一个新项&#xff0c;然后响应以三次握手的第二个分节&#xff1a;服务器的 SYN 响应&#xff0c;其中稍带对客户 SYN 的 ACK&#xff08;即SYN&#43;ACK&#xff09;&#xff0c;这一项一直保留在未完成连接队列中&#xff0c;直到三次握手的第三个分节&#xff08;客户对服务器 SYN 的 ACK &#xff09;到达或者该项超时为止&#xff08;曾经源自Berkeley的实现为这些未完成连接的项设置的超时值为75秒&#xff09;。
如果三次握手正常完成&#xff0c;该项就从未完成连接队列移到已完成连接队列的队尾。
backlog 参数历史上被定义为上面两个队列的大小之和&#xff0c;大多数实现默认值为 5&#xff0c;当服务器把这个完成连接队列的某个连接取走后&#xff0c;这个队列的位置又空出一个&#xff0c;这样来回实现动态平衡&#xff0c;但在高并发 web 服务器中此值显然不够。
四、accept()函数
accept()函数功能是&#xff0c;从连接队列头部取出一个已经完成的连接&#xff0c;如果这个队列没有已经完成的连接&#xff0c;accept()函数就会阻塞&#xff0c;直到取出队列中已完成的用户连接为止。
如果&#xff0c;服务器不能及时调用 accept() 取走队列中已完成的连接&#xff0c;队列满掉后会怎样呢&#xff1f;UNP&#xff08;《unix网络编程》&#xff09;告诉我们&#xff0c;服务器的连接队列满掉后&#xff0c;服务器不会对再对建立新连接的syn进行应答&#xff0c;所以客户端的 connect 就会返回 ETIMEDOUT。但实际上Linux的并不是这样的&#xff01;
下面为测试代码&#xff0c;服务器 listen() 函数只指定队列长度为 2&#xff0c;客户端有 6 个不同的套接字主动连接服务器&#xff0c;同时&#xff0c;保证客户端的 6 个 connect()函数都先调用完毕&#xff0c;服务器的 accpet() 才开始调用
服务器代码&#xff1a;
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- int main(int argc, char *argv[])
- {
- unsigned short port &#61; 8000;
-
- int sockfd &#61; 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 &#61; AF_INET;
- my_addr.sin_port &#61; htons(port);
- my_addr.sin_addr.s_addr &#61; htonl(INADDR_ANY);
-
- int err_log &#61; bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
- if( err_log !&#61; 0)
- {
- perror("binding");
- close(sockfd);
- exit(-1);
- }
-
- err_log &#61; listen(sockfd, 1);
- if(err_log !&#61; 0)
- {
- perror("listen");
- close(sockfd);
- exit(-1);
- }
- printf("after listen\n");
-
- sleep(20);
-
- printf("listen client &#64;port&#61;%d...\n",port);
-
- int i &#61; 0;
-
- while(1)
- {
-
- struct sockaddr_in client_addr;
- char cli_ip[INET_ADDRSTRLEN] &#61; "";
- socklen_t cliaddr_len &#61; sizeof(client_addr);
-
- int connfd;
- connfd &#61; 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", &#43;&#43;i);
- printf("client ip&#61;%s,port&#61;%d\n", cli_ip,ntohs(client_addr.sin_port));
-
- char recv_buf[512] &#61; {0};
- while( recv(connfd, recv_buf, sizeof(recv_buf), 0) > 0 )
- {
- printf("recv data &#61;&#61;%s\n",recv_buf);
- break;
- }
-
- close(connfd);
-
-
-
- }
- close(sockfd);
- return 0;
- }
客户端&#xff1a; - #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- void test_connect(int data)
- {
- unsigned short port &#61; 8000;
- char *server_ip &#61; "10.221.20.23";
-
- int sockfd;
- sockfd &#61; socket(AF_INET, SOCK_STREAM, 0);
- if(sockfd < 0)
- {
- perror("socket");
- exit(-1);
- }
-
- struct sockaddr_in server_addr;
- bzero(&server_addr,sizeof(server_addr));
- server_addr.sin_family &#61; AF_INET;
- server_addr.sin_port &#61; htons(port);
- inet_pton(AF_INET, server_ip, &server_addr.sin_addr);
-
- int err_log &#61; connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
- if(err_log !&#61; 0)
- {
- perror("connect");
- close(sockfd);
- exit(-1);
- }
- printf("---data&#61;%d---\n",data);
- system("netstat -an | grep 8000");
-
- char send_buf[100]&#61;"this is for test";
- send(sockfd, send_buf, strlen(send_buf), 0);
-
-
- }
-
- int main(int argc, char *argv[])
- {
- pid_t pid;
- pid &#61; fork();
-
- if(0 &#61;&#61; pid){
-
- test_connect(1);
-
- pid_t pid &#61; fork();
- if(0 &#61;&#61; pid){
- test_connect(2);
- }else if(pid > 0){
- test_connect(3);
- }
-
- }else if(pid > 0){
-
- test_connect(4);
-
- pid_t pid &#61; fork();
- if(0 &#61;&#61; pid){
- test_connect(5);
-
- }else if(pid > 0){
- test_connect(6);
- }
-
- }
- while(1);
-
- return 0;
- }
同样是先运行服务器&#xff0c;在运行客户端&#xff0c;服务器 accept()函数前延时了 20 秒&#xff0c; 保证了客户端的 connect() 全部调用完毕后再调用 accept(),运行结果如下
客户端结果图&#xff1a;
服务器结果图&#xff1a;
对于上面服务器的代码&#xff0c;我们把lisen()的第二个参数改为大于 6 的数(如 10)&#xff0c;重新运行程序&#xff0c;发现&#xff0c;客户端 connect() 立马返回连接成功&#xff0c; 服务器 accpet() 函数也立马返回成功。
TCP 的连接队列满后&#xff0c;Linux 不会如书中所说的拒绝连接&#xff0c;只是有些会延时连接&#xff0c;写程序时服务器的 listen() 的第二个参数最好还是根据需要填写&#xff0c;写太大不好&#xff08;具体可以看cat /proc/sys/net/core/somaxconn&#xff0c;默认最大值限制是 128&#xff09;&#xff0c;浪费资源&#xff0c;写太小也不好&#xff0c;延时建立连接。
以上源码下载