转自:http://blog.csdn.net/tennysonsky/article/details/45621341

写在前面:

    1. accept 只是从全连接队列拿出一个已经建立好的socket,如果队列为空,则阻塞。

    2. connect 过程为三次握手过程,是由内核完成的,connect只是通知内核:我要发起连接了。所以下图中的connect指向的是服务器的listen函数和accept函数之间

    3. listen 函数不阻塞,仅仅告知内核,将socket变成被动连接的监听套接字,并在listen之前可以进行一些socket选项参数的设置,例如是否重用等。

    4. backlog参数表示连接队列的长度, 在Linux 2.2版本 以后,该参数仅表示已完成队列的大小,不包含未完成连接的大小,由系统参数 net/ipv4/tcp_max_syn_backlog限制该队列长度的最大值,因此方案是:通过系统参数设置半连接队列大小,通过应用参数设置全连接大小。 不同系统,该参数的含义不同,可参考http://veithen.github.io/2014/01/01/how-tcp-backlog-works-in-linux.html的说明

    5. 半连接队列长度是指服务器处于SYN_RCVD状态的套接字个数,半连接到全连接的通过设置重传次数来判断是否超时(tcp_synack_retries,客户端的超时对应为tcp_syn_retries ,重传时间间隔一般为3s,6s,12s,...)

    6. 全连接队列长度是指处于ESTABLISHED状态的套接字个数,即上面第4点说明的backlog的长度(特指Linux 2.2版本后), 这个设置是个参考值, 不是精确值. 如果大于/proc/sys/net/core/somaxconn, 则取somaxconn的值, 该值表示已连接队列最大值

转自:http://www.jianshu.com/p/fe2228a77429

已完成队列满后
  通常未完成队列的长度大于已完成队列.
  已完成队列满后, 当服务器收到来自客户端的ACK包时
  如果 /proc/sys/net/ipv4/tcp_abort_on_overflow 设为 1, 直接回RST包,结束连接.
  否则忽视ACK包.
  内核有定时器管理未完成队列,对于由于网络原因没收到ACK包或是收到ACK包后被忽视的SYN_RCVD连接重发SYN+ACK包, 最多重发次数由/proc/sys/net/ipv4/tcp_synack_retries 设定.

  backlog 即上述已完成队列的大小, 这个设置是个参考值,不是精确值. 内核会做些调整, 大于/proc/sys/net/core/somaxconn, 则取somaxconn的值

未完成队列满后
  如果启用syncookies (net.ipv4.tcp_syncookies = 1),新的连接不进入未完成队列,不受影响.
  否则,服务器不在接受新的连接.

SYN 洪水攻击(syn flood attack)
  通过伪造IP向服务器发送SYN包,塞满服务器的未完成队列,服务器发送SYN+ACK包 没回复,反复SYN+ACK包,使服务器不可用.

  启用syncookies 是简单有效的抵御措施.
  启用syncookies,仅未完成队列满后才生效.

以下是转载文章内容, 下文的backlog说明不一定是错误的,因为它并没有明确系统环境

基于 TCP 的网络编程开发分为服务器端和客户端两部分,常见的核心步骤和流程如下:

connect()函数

对于客户端的 connect() 函数,该函数的功能为客户端主动连接服务器,建立连接是通过三次握手,而这个连接的过程是由内核完成,不是这个函数完成的,这个函数的作用仅仅是通知 Linux 内核,让 Linux 内核自动完成 TCP 三次握手连接(三次握手详情,请看《浅谈 TCP 三次握手》),最后把连接的结果返回给这个函数的返回值(成功连接为0, 失败为-1)。

通常的情况,客户端的 connect() 函数默认会一直阻塞,直到三次握手成功或超时失败才返回(正常的情况,这个过程很快完成)。

listen()函数

对于服务器,它是被动连接的。举一个生活中的例子,通常的情况下,移动的客服(相当于服务器)是等待着客户(相当于客户端)电话的到来。而这个过程,需要调用listen()函数。

  1. #include<sys/socket.h>
  2. int listen(int sockfd, int backlog);

listen() 函数的主要作用就是将套接字( sockfd )变成被动的连接监听套接字(被动等待客户端的连接),至于参数 backlog 的作用是设置内核中连接队列的长度(这个长度有什么用,后面做详细的解释),TCP 三次握手也不是由这个函数完成,listen()的作用仅仅告诉内核一些信息。

这里需要注意的是,listen()函数不会阻塞,它主要做的事情为,将该套接字和套接字对应的连接队列长度告诉 Linux 内核,然后,listen()函数就结束。

这样的话,当有一个客户端主动连接(connect()),Linux 内核就自动完成TCP 三次握手,将建立好的链接自动存储到队列中,如此重复。

所以,只要 TCP 服务器调用了 listen(),客户端就可以通过 connect() 和服务器建立连接,而这个连接的过程是由内核完成

下面为测试的服务器和客户端代码,运行程序时,要先运行服务器,再运行客户端:

服务器:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <sys/socket.h>
  6. #include <netinet/in.h>
  7. #include <arpa/inet.h>
  8. int main(int argc, char *argv[])
  9. {
  10. unsigned short port = ;
  11.  
  12. int sockfd;
  13. sockfd = socket(AF_INET, SOCK_STREAM, );// 创建通信端点:套接字
  14. if(sockfd < )
  15. {
  16. perror("socket");
  17. exit(-);
  18. }
  19.  
  20. struct sockaddr_in my_addr;
  21. bzero(&my_addr, sizeof(my_addr));
  22. my_addr.sin_family = AF_INET;
  23. my_addr.sin_port = htons(port);
  24. my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  25.  
  26. int err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
  27. if( err_log != )
  28. {
  29. perror("binding");
  30. close(sockfd);
  31. exit(-);
  32. }
  33.  
  34. err_log = listen(sockfd, );
  35. if(err_log != )
  36. {
  37. perror("listen");
  38. close(sockfd);
  39. exit(-);
  40. }
  41.  
  42. printf("listen client @port=%d...\n",port);
  43.  
  44. sleep(); // 延时10s
  45.  
  46. system("netstat -an | grep 8000"); // 查看连接状态
  47.  
  48. return ;
  49. }

客户端:

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <string.h>
  4. #include <stdlib.h>
  5. #include <arpa/inet.h>
  6. #include <sys/socket.h>
  7. #include <netinet/in.h>
  8. int main(int argc, char *argv[])
  9. {
  10. unsigned short port = ; // 服务器的端口号
  11. char *server_ip = "10.221.20.12"; // 服务器ip地址
  12.  
  13. int sockfd;
  14. sockfd = socket(AF_INET, SOCK_STREAM, );// 创建通信端点:套接字
  15. if(sockfd < )
  16. {
  17. perror("socket");
  18. exit(-);
  19. }
  20.  
  21. struct sockaddr_in server_addr;
  22. bzero(&server_addr,sizeof(server_addr)); // 初始化服务器地址
  23. server_addr.sin_family = AF_INET;
  24. server_addr.sin_port = htons(port);
  25. inet_pton(AF_INET, server_ip, &server_addr.sin_addr);
  26.  
  27. int err_log = connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)); // 主动连接服务器
  28. if(err_log != )
  29. {
  30. perror("connect");
  31. close(sockfd);
  32. exit(-);
  33. }
  34.  
  35. system("netstat -an | grep 8000"); // 查看连接状态
  36.  
  37. while();
  38.  
  39. return ;
  40. }

运行程序时,要先运行服务器,再运行客户端,运行结果如下:

三次握手的连接队列

这里详细的介绍一下 listen() 函数的第二个参数( backlog)的作用:告诉内核连接队列的长度。

为了更好的理解 backlog 参数,我们必须认识到内核为任何一个给定的监听套接口维护两个队列:

1、未完成连接队列(incomplete connection queue),每个这样的 SYN 分节对应其中一项:已由某个客户发出并到达服务器,而服务器正在等待完成相应的 TCP 三次握手过程。这些套接口处于 SYN_RCVD 状态。

2、已完成连接队列(completed connection queue),每个已完成 TCP 三次握手过程的客户对应其中一项。这些套接口处于 ESTABLISHED 状态。

当来自客户的 SYN 到达时,TCP 在未完成连接队列中创建一个新项,然后响应以三次握手的第二个分节:服务器的 SYN 响应,其中稍带对客户 SYN 的 ACK(即SYN+ACK),这一项一直保留在未完成连接队列中,直到三次握手的第三个分节(客户对服务器 SYN 的 ACK )到达或者该项超时为止(曾经源自Berkeley的实现为这些未完成连接的项设置的超时值为75秒)。

如果三次握手正常完成,该项就从未完成连接队列移到已完成连接队列的队尾。

backlog 参数历史上被定义为上面两个队列的大小之和,大多数实现默认值为 5,当服务器把这个完成连接队列的某个连接取走后,这个队列的位置又空出一个,这样来回实现动态平衡,但在高并发 web 服务器中此值显然不够。

accept()函数

accept()函数功能是,从处于 established 状态的连接队列头部取出一个已经完成的连接,如果这个队列没有已经完成的连接,accept()函数就会阻塞,直到取出队列中已完成的用户连接为止。

如果,服务器不能及时调用 accept() 取走队列中已完成的连接,队列满掉后会怎样呢?UNP(《unix网络编程》)告诉我们,服务器的连接队列满掉后,服务器不会对再对建立新连接的syn进行应答,所以客户端的 connect 就会返回 ETIMEDOUT。但实际上Linux的并不是这样的!

下面为测试代码,服务器 listen() 函数只指定队列长度为 2,客户端有 6 个不同的套接字主动连接服务器,同时,保证客户端的 6 个 connect()函数都先调用完毕,服务器的 accpet() 才开始调用。

服务器:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <sys/socket.h>
  6. #include <netinet/in.h>
  7. #include <arpa/inet.h>
  8.  
  9. int main(int argc, char *argv[])
  10. {
  11. unsigned short port = ;
  12.  
  13. int sockfd = socket(AF_INET, SOCK_STREAM, );
  14. if(sockfd < )
  15. {
  16. perror("socket");
  17. exit(-);
  18. }
  19.  
  20. struct sockaddr_in my_addr;
  21. bzero(&my_addr, sizeof(my_addr));
  22. my_addr.sin_family = AF_INET;
  23. my_addr.sin_port = htons(port);
  24. my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  25.  
  26. int err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
  27. if( err_log != )
  28. {
  29. perror("binding");
  30. close(sockfd);
  31. exit(-);
  32. }
  33.  
  34. err_log = listen(sockfd, ); // 等待队列为2
  35. if(err_log != )
  36. {
  37. perror("listen");
  38. close(sockfd);
  39. exit(-);
  40. }
  41. printf("after listen\n");
  42.  
  43. sleep(); //延时 20秒
  44.  
  45. printf("listen client @port=%d...\n",port);
  46.  
  47. int i = ;
  48.  
  49. while()
  50. {
  51.  
  52. struct sockaddr_in client_addr;
  53. char cli_ip[INET_ADDRSTRLEN] = "";
  54. socklen_t cliaddr_len = sizeof(client_addr);
  55.  
  56. int connfd;
  57. connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);
  58. if(connfd < )
  59. {
  60. perror("accept");
  61. continue;
  62. }
  63.  
  64. inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
  65. printf("-----------%d------\n", ++i);
  66. printf("client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port));
  67.  
  68. char recv_buf[] = {};
  69. while( recv(connfd, recv_buf, sizeof(recv_buf), ) > )
  70. {
  71. printf("recv data ==%s\n",recv_buf);
  72. break;
  73. }
  74.  
  75. close(connfd); //关闭已连接套接字
  76. //printf("client closed!\n");
  77. }
  78. close(sockfd); //关闭监听套接字
  79. return ;
  80. }

客户端:

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <string.h>
  4. #include <stdlib.h>
  5. #include <arpa/inet.h>
  6. #include <sys/socket.h>
  7. #include <netinet/in.h>
  8.  
  9. void test_connect()
  10. {
  11. unsigned short port = ; // 服务器的端口号
  12. char *server_ip = "10.221.20.12"; // 服务器ip地址
  13.  
  14. int sockfd;
  15. sockfd = socket(AF_INET, SOCK_STREAM, );// 创建通信端点:套接字
  16. if(sockfd < )
  17. {
  18. perror("socket");
  19. exit(-);
  20. }
  21.  
  22. struct sockaddr_in server_addr;
  23. bzero(&server_addr,sizeof(server_addr)); // 初始化服务器地址
  24. server_addr.sin_family = AF_INET;
  25. server_addr.sin_port = htons(port);
  26. inet_pton(AF_INET, server_ip, &server_addr.sin_addr);
  27.  
  28. int err_log = connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)); // 主动连接服务器
  29. if(err_log != )
  30. {
  31. perror("connect");
  32. close(sockfd);
  33. exit(-);
  34. }
  35.  
  36. printf("err_log ========= %d\n", err_log);
  37.  
  38. char send_buf[]="this is for test";
  39. send(sockfd, send_buf, strlen(send_buf), ); // 向服务器发送信息
  40.  
  41. system("netstat -an | grep 8000"); // 查看连接状态
  42.  
  43. //close(sockfd);
  44. }
  45.  
  46. int main(int argc, char *argv[])
  47. {
  48. pid_t pid;
  49. pid = fork();
  50.  
  51. if( == pid){
  52.  
  53. test_connect(); // 1
  54.  
  55. pid_t pid = fork();
  56. if( == pid){
  57. test_connect(); // 2
  58.  
  59. }else if(pid > ){
  60. test_connect(); // 3
  61. }
  62.  
  63. }else if(pid > ){
  64.  
  65. test_connect(); // 4
  66.  
  67. pid_t pid = fork();
  68. if( == pid){
  69. test_connect(); // 5
  70.  
  71. }else if(pid > ){
  72. test_connect(); // 6
  73. }
  74.  
  75. }
  76.  
  77. while();
  78.  
  79. return ;
  80. }

同样是先运行服务器,在运行客户端,服务器 accept()函数前延时了 20 秒, 保证了客户端的 connect() 全部调用完毕后再调用 accept(),运行结果如下:

服务器运行效果图:

客户端运行效果图:

按照 UNP 的说法,连接队列满后(这里设置长度为 2,发了 6 个连接),以后再调用 connect() 应该统统超时失败,但实际上测试结果是:有的 connect()立刻成功返回了,有的经过明显延迟后成功返回了。对于服务器 accpet() 函数也是这样的结果:有的立马成功返回,有的延迟后成功返回。

对于上面服务器的代码,我们把lisen()的第二个参数改为 0 的数,重新运行程序,发现:

客户端 connect() 全部返回连接成功(有些会延时):

服务器 accpet() 函数却不能把连接队列的所有连接都取出来:

对于上面服务器的代码,我们把lisen()的第二个参数改为大于 6 的数(如 10),重新运行程序,发现,客户端 connect() 立马返回连接成功, 服务器 accpet() 函数也立马返回成功。

TCP 的连接队列满后,Linux 不会如书中所说的拒绝连接,只是有些会延时连接,而且accept()未必能把已经建立好的连接全部取出来(如:当队列的长度指定为 0 ),写程序时服务器的 listen() 的第二个参数最好还是根据需要填写,写太大不好(具体可以看cat /proc/sys/net/core/somaxconn,默认最大值限制是 128),浪费资源,写太小也不好,延时建立连接。

【转载】socket 的 connect、listen、accept 和全连接队列、半连接队列的原理的更多相关文章

  1. 非阻塞socket调用connect, epoll和select检查连接情况示例

    转自http://www.cnblogs.com/yuxingfirst/archive/2013/03/08/2950281.html 我们知道,linux下socket编程有常见的几个系统调用: ...

  2. TCP半连接队列和全连接

    概述   如上图所示, 在TCP三次握手中,服务器维护一个半连接队列(sync queue) 和一个全连接队列(accept queue). 当服务端接收到客户端第一次SYN握手请求时,将创建的req ...

  3. 【转】关于TCP 半连接队列和全连接队列

    摘要: # 关于TCP 半连接队列和全连接队列 > 最近碰到一个client端连接异常问题,然后定位分析并查阅各种资料文章,对TCP连接队列有个深入的理解 > > 查资料过程中发现没 ...

  4. [TimLinux] TCP全连接队列满

    0. TCP三次握手 该图来自:TCP SOCKET中backlog参数的用途是什么? syns queue: 半连接队列 accept queue: 全连接队列 控制参数存放在文件:/proc/sy ...

  5. TCP实战二(半连接队列、全连接队列)

    TCP实验一我们利用了tcpdump以及Wireshark对TCP三次握手.四次挥手.流量控制做了深入的分析,今天就让我们一同深入理解TCP三次握手中两个重要的结构:半连接队列.全连接队列. 参考文献 ...

  6. 五分钟带你读懂 TCP全连接队列(图文并茂)

    爱生活,爱编码,微信搜一搜[架构技术专栏]关注这个喜欢分享的地方. 本文 架构技术专栏 已收录,有各种视频.资料以及技术文章. 一.问题 今天有个小伙伴跑过来告诉我有个奇怪的问题需要协助下,问题确实也 ...

  7. TCP 半连接队列和全连接队列满了会发生什么?又该如何应对?

    前言 网上许多博客针对增大 TCP 半连接队列和全连接队列的方式如下: 增大 TCP 半连接队列的方式是增大 /proc/sys/net/ipv4/tcp_max_syn_backlog: 增大 TC ...

  8. socket listen/accept

    listen函数 摘要:listen函数使用主动连接套接口变为被连接套接口,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程.在TCP服务器编程中listen函数把进程变为一个服务器,并指定 ...

  9. tcp通讯中socket套接字accept和listen的关系

    今天看到一个文章,客户端的connect在服务端调用accept之前,突然想到这可以建立正常的连接么?以前从没细细的思考过listen accept connect之前的关系,带着疑问学习了一下,记录 ...

随机推荐

  1. WCF webHttpBinding协议上传接收文件

    一般情况下wcf用webHttpBinding协议最多的场景就是前后端Json交互,会比较轻量级. 接收上传的文件也可以,不过要自己解析处理. 前端HTML很简单: <input type=&q ...

  2. [转]VS2013中使用Git建立源代码管理

    本文转自:https://blog.csdn.net/bodybo/article/details/38976549 第一次在VS2013中使用Git,也是第一次使用Git,各种不熟悉.百度各种使用经 ...

  3. PHP数组基本的操作方法

    1.数组操作的基本函数 数组的键和值: array_values($arr);获得数组的值 array_keys($arr);获得数组的键名 array_flip($arr);数组中的值与键名互换(如 ...

  4. Bootstrap中的datetimepicker插件用法总结(转载)

    datetimepicker用法总结   目录 datetimepicker用法总结 目录 简述 官方文档 选项属性 1 format 格式 2 weekStart 一周从哪一天开始 3 startD ...

  5. MVC初级教程(二)

    演示产品源码下载地址:http://www.jinhusns.com/Products/Download 

  6. vps服务器搭建——Linode VPS 20美元优惠获取教程

    转载:http://www.cuishifeng.cn/linode/index.html?v=2 声明:本文旨在教大家怎么获得linode 20美元优惠,并免费使用4个月vps,请低调薅羊毛!(多张 ...

  7. 把C程序的int main(void)改成static int main(void)会怎样呢?

    如题,把C程序中的主函数int main(void)改成static int main(void)会怎么样呢? 比如把 #include <stdio.h> int main(void) ...

  8. Qt架构图及模块分析介绍

    1.Qt框架图: 2.Qt模块组成 通用软件开发模块 QtCore 核心非图形接口类,为其他模块所调用 QtGui GUI(图形用户接口)功能模块 QtMultimedia 提供低级多媒体功能支持的类 ...

  9. html基础-a标签-img标签-绝对/相对路径(3)

    美好的星期六,今天多写一点,争取早点写js这个有点小无聊. 一.先来讲点网页之间的跳转 (1).<a href=""></a>  href="这里 ...

  10. jquery实现复选框全选,全不选,反选中的问题

    今天试了一下用jquery选择复选框,本来以为很简单的东西却有bug,于是搜索了一下找到了解决方法. html代码如下(这里没有用任何样式,就没有再放css了): <html> <h ...