00. 目录

文章目录

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

01. TCP服务端和客户端流程

02. connect函数

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

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

03. listen函数

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

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

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

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

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

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

服务器

#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);// 创建通信端点:套接字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);   // 延时10ssystem("netstat -an | grep 8000");    // 查看连接状态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;               // 服务器的端口号char *server_ip = "10.221.20.12";      // 服务器ip地址int sockfd;sockfd = 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 = 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));      // 主动连接服务器if(err_log != 0){perror("connect");close(sockfd);exit(-1);}system("netstat -an | grep 8000");  // 查看连接状态while(1);return 0;
}

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

04. 三次握手

这里详细的介绍一下 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 服务器中此值显然不够。

05. accept函数

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

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

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

服务器

#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); // 等待队列为2if(err_log != 0){perror("listen");close(sockfd);        exit(-1);}  printf("after listen\n");sleep(20);   //延时 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);     //关闭已连接套接字//printf("client closed!\n");}close(sockfd);         //关闭监听套接字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;               // 服务器的端口号char *server_ip = "10.221.20.12";      // 服务器ip地址int sockfd;sockfd = 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 = 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));      // 主动连接服务器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);   // 向服务器发送信息system("netstat -an | grep 8000");  // 查看连接状态//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;
}

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


客户端运行结果

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

对于上面服务器的代码,我们把lisen()的第二个参数改为 0 的数,重新运行程序,发现客户端 connect() 全部返回连接成功(有些会延时)

服务器 accpet() 函数却不能把连接队列的所有连接都取出来:
图片保存下来直接上传(img-i12tqYDQ-1595920060035)(assets/image-

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

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

06. 附录

5.1【Linux】一步一步学Linux网络编程教程汇总

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

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

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

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

    https://blog.csdn.net/tennysonsky/article/details/45621341 基于 TCP 的网络编程开发分为服务器端和客户端两部分,常见的核心步骤和流程如下: ...

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

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

  4. 套接字编程-TCP网络编程

    文章目录 套接字地址结构 通用套接字地址数据结构 以太网协议的套接字地址数据结构 Netlink协议套接字地址结构 TCP网络编程 套接字初始化socket() domain type protoco ...

  5. Linux下各类TCP网络服务器的实现源代码

    http://www.linuxeden.com/forum/t146870.html 大家都知道各类网络服务器程序的编写步骤,并且都知道网络服务器就两大类:循环服务和并发服务.这里附上源代码来个小结 ...

  6. Linux下各类TCP网络服务器的实现源代码 转

    大家都知道各类网络服务器程序的编写步骤,并且都知道网络服务器就两大类:循环服务和并发服务.这里附上源代码来个小结吧. 首先,循环网络服务器编程实现的步骤是这样的: 这种服务器模型是典型循环服务,如果不 ...

  7. linux 网络服务器 源码下载,linux下 各类tcp网络服务器的实现源代码.doc

    linux下 各类tcp网络服务器的实现源代码.doc 还剩 25页未读, 继续阅读 下载文档到电脑,马上远离加班熬夜! 亲,喜欢就下载吧,价低环保! 内容要点: %s", sock, Ge ...

  8. storm中worker、executor、task之间的关系

    这里做一些补充: worker是一个进程,由supervisor启动,并只负责处理一个topology,所以不会同时处理多个topology. executor是一个线程,由worker启动,是运行t ...

  9. anaconda中的python如何进行关联分析_浅析python,PyCharm,Anaconda三者之间的关系

    一.它们是什么? Python是一种跨平台的计算机程序设计语言,简单来说,python就是类似于C,Java,C++等,一种编程语言. 2.Anaconda Anaconda指的是一个开源的Pytho ...

最新文章

  1. linux下的基础操作
  2. 九个步骤让你成为PHP专家
  3. mysql的水平分表和垂直分表的区别
  4. sv队列和动态数组的区别_Go 刷 LeetCode 系列:经典(7) 设计双端队列
  5. python 可视化项目_python3项目之数据可视化
  6. php使用自定义alert,IOS_iOS自定义alertView提示框实例分享,本文实例为大家分享iOS自定义a - phpStudy...
  7. DataFrame 排序
  8. Myeclipse学习总结(9)——MyEclipse2014安装插件的几种方式(适用于Eclipse或MyEclipse其他版本)
  9. Ubuntu中使用VS Code
  10. qt解决中文乱码问题。总结一下
  11. 彻底理解线性代数; 特征值,特征向量; 线性代数的本质 矩阵的逆矩阵的实质: 行列式值为0的实质: Essense Of Linear Algebra的理解
  12. springboot项目如何查看MP运行日志
  13. 笔记:图解系统(小林coding)
  14. js 进一法取正、四舍五入法取正、舍去法取正
  15. 在MSTR中使用ECharts作为VI模板(1)-- 创建第一个ECharts的VI模板
  16. 神经网络可以解决一切问题吗:一场知乎辩论的整理
  17. Doris集成cloudera Manager
  18. 个人理解,关于python 的__init__.py 以及 __all__ 的用法
  19. 使用国外DynDNS免费动态域名解析 随时访问家中电脑
  20. 计算机的表格如何加入客标,WPS表格如何添加两个纵坐标 WPS表格添加两个纵坐标的技巧...

热门文章

  1. cocoapods Analyzing dependencies 问题的解决方案
  2. SQL SERVER – Beginning of SQL Server Architecture – Terminology – Guest Post
  3. [LCS]LCS2005服务器应用程序
  4. 给定一个年份,判断这一年是不是闰年。
  5. 单片机定时器实验两位倒计时秒表_单片机学习「1」 初始51单片机
  6. Java黑皮书课后题第8章:*8.26(行排序)用下面的方法实现一个二维数组中的行排序。返回新数组,且原数组保持不变。编写一个测试程序,提示用户输入一个3*3的double型矩阵,显示一个排好的矩阵
  7. Java黑皮书课后题第3章:3.7(金融应用:整钱兑零)修改程序清单2-10,使之只显示非零的币值单位,用单词的单数形式显示一个单位,复数形式显示多于一个的单位的值
  8. 170304 地铁修建 ccf
  9. 001.Parted工具使用
  10. 【easy】206. Reverse Linked List 链表反转