https://blog.csdn.net/tennysonsky/article/details/45621341

基于 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 = 8000;

  11. int sockfd;

  12. sockfd = socket(AF_INET, SOCK_STREAM, 0);// 创建通信端点:套接字

  13. if(sockfd < 0)

  14. {

  15. perror("socket");

  16. exit(-1);

  17. }

  18. struct sockaddr_in my_addr;

  19. bzero(&my_addr, sizeof(my_addr));

  20. my_addr.sin_family = AF_INET;

  21. my_addr.sin_port = htons(port);

  22. my_addr.sin_addr.s_addr = htonl(INADDR_ANY);

  23. int err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));

  24. if( err_log != 0)

  25. {

  26. perror("binding");

  27. close(sockfd);

  28. exit(-1);

  29. }

  30. err_log = listen(sockfd, 10);

  31. if(err_log != 0)

  32. {

  33. perror("listen");

  34. close(sockfd);

  35. exit(-1);

  36. }

  37. printf("listen client @port=%d...\n",port);

  38. sleep(10); // 延时10s

  39. system("netstat -an | grep 8000"); // 查看连接状态

  40. return 0;

  41. }

客户端:

  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 = 8000; // 服务器的端口号

  11. char *server_ip = "10.221.20.12"; // 服务器ip地址

  12. int sockfd;

  13. sockfd = socket(AF_INET, SOCK_STREAM, 0);// 创建通信端点:套接字

  14. if(sockfd < 0)

  15. {

  16. perror("socket");

  17. exit(-1);

  18. }

  19. struct sockaddr_in server_addr;

  20. bzero(&server_addr,sizeof(server_addr)); // 初始化服务器地址

  21. server_addr.sin_family = AF_INET;

  22. server_addr.sin_port = htons(port);

  23. inet_pton(AF_INET, server_ip, &server_addr.sin_addr);

  24. //server_addr.sin_addr.s_addr=inet_addr(server_ip);

  25. int err_log = connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));

  26. // 主动连接服务器

  27. if(err_log != 0)

  28. {

  29. perror("connect");

  30. close(sockfd);

  31. exit(-1);

  32. }

  33. system("netstat -an | grep 8000"); // 查看连接状态

  34. while(1);

  35. return 0;

  36. }

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

三次握手的连接队列

这里详细的介绍一下 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. int main(int argc, char *argv[])

  9. {

  10. unsigned short port = 8000;

  11. int sockfd = socket(AF_INET, SOCK_STREAM, 0);

  12. if(sockfd < 0)

  13. {

  14. perror("socket");

  15. exit(-1);

  16. }

  17. struct sockaddr_in my_addr;

  18. bzero(&my_addr, sizeof(my_addr));

  19. my_addr.sin_family = AF_INET;

  20. my_addr.sin_port = htons(port);

  21. my_addr.sin_addr.s_addr = htonl(INADDR_ANY);

  22. int err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));

  23. if( err_log != 0)

  24. {

  25. perror("binding");

  26. close(sockfd);

  27. exit(-1);

  28. }

  29. err_log = listen(sockfd, 2); // 等待队列为2

  30. if(err_log != 0)

  31. {

  32. perror("listen");

  33. close(sockfd);

  34. exit(-1);

  35. }

  36. printf("after listen\n");

  37. sleep(20); //延时 20秒

  38. printf("listen client @port=%d...\n",port);

  39. int i = 0;

  40. while(1)

  41. {

  42. struct sockaddr_in client_addr;

  43. char cli_ip[INET_ADDRSTRLEN] = "";

  44. socklen_t cliaddr_len = sizeof(client_addr);

  45. int connfd;

  46. connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);

  47. if(connfd < 0)

  48. {

  49. perror("accept");

  50. continue;

  51. }

  52. inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);

  53. printf("-----------%d------\n", ++i);

  54. printf("client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port));

  55. char recv_buf[512] = {0};

  56. while( recv(connfd, recv_buf, sizeof(recv_buf), 0) > 0 )

  57. {

  58. printf("recv data ==%s\n",recv_buf);

  59. break;

  60. }

  61. close(connfd); //关闭已连接套接字

  62. //printf("client closed!\n");

  63. }

  64. close(sockfd); //关闭监听套接字

  65. return 0;

  66. }

客户端:

  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. void test_connect()

  9. {

  10. unsigned short port = 8000; // 服务器的端口号

  11. char *server_ip = "10.221.20.12"; // 服务器ip地址

  12. int sockfd;

  13. sockfd = socket(AF_INET, SOCK_STREAM, 0);// 创建通信端点:套接字

  14. if(sockfd < 0)

  15. {

  16. perror("socket");

  17. exit(-1);

  18. }

  19. struct sockaddr_in server_addr;

  20. bzero(&server_addr,sizeof(server_addr)); // 初始化服务器地址

  21. server_addr.sin_family = AF_INET;

  22. server_addr.sin_port = htons(port);

  23. inet_pton(AF_INET, server_ip, &server_addr.sin_addr);

  24. int err_log = connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)); // 主动连接服务器

  25. if(err_log != 0)

  26. {

  27. perror("connect");

  28. close(sockfd);

  29. exit(-1);

  30. }

  31. printf("err_log ========= %d\n", err_log);

  32. char send_buf[100]="this is for test";

  33. send(sockfd, send_buf, strlen(send_buf), 0); // 向服务器发送信息

  34. system("netstat -an | grep 8000"); // 查看连接状态

  35. //close(sockfd);

  36. }

  37. int main(int argc, char *argv[])

  38. {

  39. pid_t pid;

  40. pid = fork();

  41. if(0 == pid){

  42. test_connect(); // 1

  43. pid_t pid = fork();

  44. if(0 == pid){

  45. test_connect(); // 2

  46. }else if(pid > 0){

  47. test_connect(); // 3

  48. }

  49. }else if(pid > 0){

  50. test_connect(); // 4

  51. pid_t pid = fork();

  52. if(0 == pid){

  53. test_connect(); // 5

  54. }else if(pid > 0){

  55. test_connect(); // 6

  56. }

  57. }

  58. while(1);

  59. return 0;

  60. }

同样是先运行服务器,在运行客户端,服务器 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),浪费资源,写太小也不好,延时建立连接。

TCP网络编程中connect()、listen()和accept()三者之间的关系相关推荐

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

    00. 目录 文章目录 00. 目录 01. TCP服务端和客户端流程 02. connect函数 03. listen函数 04. 三次握手 05. accept函数 06. 附录 01. TCP服 ...

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

    基于 TCP 的网络编程开发分为服务器端和客户端两部分,常见的核心步骤和流程如下: connect()函数 对于客户端的 connect() 函数,该函数的功能为客户端主动连接服务器,建立连接是通过三 ...

  3. accept 阻塞_TCP网络编程中connect()、listen()和accept()三者之间的关系

    基于 TCP 的网络编程开发分为服务器端和客户端两部分,常见的核心步骤和流程如下: connect()函数 对于客户端的 connect() 函数,该函数的功能为客户端主动连接服务器,建立连接是通过三 ...

  4. Linux网络编程中出现 listen fail : Socket operation on non-socket错误

    错误代码: int main (int argc,char *argv[]) {int lfd = 0,cfd = 0;/* 定义服务器地址结构 和 客户端地址结构*/struct sockaddr_ ...

  5. 实例解析网络编程中的另类内存泄漏

    本文分享自华为云社区<[网络编程开发系列]一种网络编程中的另类内存泄漏>,作者:架构师李肯. 1 写在前面 最近在排查一个网络通讯的压测问题,最后发现跟"内存泄漏"扯上 ...

  6. muduo学习笔记:net部分之实现TCP网络编程库-Buffer

    文章目录 为什么采用non-blocking网络编程中应用层buffer是必需的? Buffer 设计 Buffer::readFd() 线程安全 Muduo Buffer 的数据结构 Muduo B ...

  7. protobuf在网络编程中的应用思考

    protobuf简介 protobuf是google提供的一个开源序列化框架,类似于XML,JSON这样的数据表示语言,其最大的特点是基于二进制,因此比传统的XML表示高效短小得多.虽然是二进制数据格 ...

  8. C++ tcpip网络编程中listen函数和accept函数详解和区别

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

  9. 【计算机网络】Linux环境中的TCP网络编程

    文章目录 前言 一.TCP Socket API 1. socket 2. bind 3. listen 4. accept 5. connect 二.封装TCPSocket 三.服务端的实现 1. ...

最新文章

  1. 数据结构实验之串三:KMP应用
  2. Python脚本--Apache配置文件
  3. 一个例子带你搞懂python作用域中的global、nonlocal和local
  4. 基于easyui开发Web版Activiti流程定制器详解(六)——Draw2d的扩展(一)
  5. java 该改变request url_如何在Java中使用servlet过滤器来更改传入的servlet请求URL?...
  6. 看似简单的搜索引擎,原来背后的数据结构和算法这么复杂?
  7. element table批量删除_element 表格批量删除
  8. HTTPS 互联网世界的安全基础
  9. 模型审查的4个核心要点
  10. 代码重构 —— 区分代码和数据
  11. android+3.1.2+imagebutton监听,android Button ImageButton 差别
  12. C# OpenCV OpenCVSharp应用实例--LCD屏幕脏污检测
  13. et200s模块接线图讲解_ET200S 模块说明
  14. 会考计算机基本知识点总结,高中计算机会考基本知识点
  15. android水果界面设计,一套小清新的水果元素的APP界面设计欣赏
  16. 图片分析——现代家居风水学[图文]居家必然之奇术
  17. 二叉树 | 二叉树的深度
  18. AriaNg | 一款基于aria2高速图形化界面下载器
  19. ps安装程序检测到计算机重启过程,photoshop cs6安装过程中安装程序遇到错误:请重启计算机,解决办法...
  20. Echarts --- 可视化练习(line04 ---- 堆叠面积图)

热门文章

  1. 中国SaaS死或生之七:死生非大事,利益最相关
  2. 深入理解Golang包导入
  3. spring boot实战(第六篇)加载application资源文件源码分析
  4. redis:list的底层实现--压缩列表
  5. 全面总结:进程与线程
  6. 【Python】青少年蓝桥杯_每日一题_12.27_输出回文数
  7. 人才短缺是数据中心运营商面临的新问题
  8. Java 散点图 数据库 代码_java – 来自数据库的jfreechart中的散点图
  9. python编辑器_初学Python这几款编辑器,推荐你安装
  10. c 语言程序设计教程 沈显君 答案,CD3计算机实践《C/C++语言程序设计》报告模板2.doc...