理想下载站 手游攻略 新游动态 TCP网络编程中connect、listen、accept的关系函数accrint

TCP网络编程中connect、listen、accept的关系函数accrint

时间:2024-07-09 20:09:31 来源:网络整理 浏览:0

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

02. connect 函数

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

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

03. listen 函数

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

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

这里需要注意的是,listen()函数不会阻塞,它的主要作用是告诉Linux内核这个socket以及这个socket对应的连接队列的长度,然后listen()函数结束。

这种情况下,当有客户端主动连接(connect())时,Linux内核会自动完成TCP三次握手,并自动将建立好的链接存放到队列中,并重复这个过程。

因此,只要TCP服务器调用listen(),客户端就可以通过connect()与服务器建立连接,连接过程由内核完成。

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

服务器:

#包括

#包括

#包括

#包括

#包括

#包括

#包括

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

无符号短端口 = 8000;

int sockfd;

sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建通信端点:socket

如果(sockfd < 0)

perror(“插座”);

退出(-1);

结构sockaddr_in my_addr;

bzero(&my_addr,sizeof(my_addr));

我的地址.sin_family = AF_INET;

my_addr.sin_port = htons(端口);

复制代码

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

如果(err_log!= 0)

perror(“绑定”);

关闭(sockfd);

退出(-1);

err_log = 监听(sockfd, 10);

如果 (err_log != 0)

perror(“听”);

关闭(sockfd);

退出(-1);

printf("监听客户端@port=%d…\n",port);

sleep(10); // 延迟 10 秒

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

返回0;

客户:

#包括

#包括

#包括

#包括

#包括

#包括

#包括

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); // 创建通信端点:socket

如果(sockfd < 0)

perror(“插座”);

退出(-1);

结构sockaddr_in server_addr;

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

服务器地址.sin_family = AF_INET;

服务器地址.sin_port = htons(端口);

inet_pton(AF_INET,server_ip,&服务器地址.sin_addr);

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

如果 (err_log != 0)

perror(“连接”);

关闭(sockfd);

退出(-1);

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

虽然(1);

返回0;

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

04.三次握手

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

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

1、未完成连接队列,每个SYN段对应其中的一个:已经由某个客户端发送,并到达服务器,而服务器正在等待相应的TCP三次握手过程的完成。这些套接字处于SYN_RCVD状态。

2.已完成连接队列,每个完成TCP三次握手过程的客户端都对应其中一个。这些套接字处于ESTABLISHED状态。

当来自客户端的 SYN 到达时,TCP 会在未完成连接队列中创建一个新项,然后用三次握手的第二段进行响应:服务器的 SYN 响应,该响应带有对客户端 SYN 的 ACK(即 SYN+ACK)。此项将保留在未完成连接队列中,直到三次握手的第三段(客户端对服务器 SYN 的 ACK)到达或此项超时(曾经源自伯克利的实现将这些未完成连接项的超时值设置为 75 秒)。

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

backlog 参数历史上一直被定义为上面两个队列大小的总和。大多数实现的默认值是 5。当服务器从已完成的连接队列中取出一个连接时,这个队列中的另一个位置就会空出来,从而实现来回的动态平衡。但在高并发的 Web 服务器中,这个值显然是不够的。

05.接受函数

accept()函数在建立状态时从连接队列的头部取出一个已完成的连接,如果队列中没有已完成的连接,则accept()函数会阻塞,直到取出队列中已完成的用户连接为止。

如果服务器不能及时调用 accept() 来取走队列中已完成的连接,当队列满了时会发生什么? UNP(Unix 网络编程)告诉我们,当服务器的连接队列满了时,服务器将不会响应新的连接的 syn 请求,因此客户端的 connect 将返回 ETIMEDOUT。但事实上,在 Linux 中并非如此!

以下是测试代码,服务端 listen() 函数只指定了队列长度为 2,客户端有 6 个不同的 socket 主动连接服务端,同时保证在服务端的 accpet() 被调用之前,客户端的 6 个 connect() 函数全部被调用。

服务器

#包括

#包括

#包括

#包括

#包括

#包括

#包括

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

无符号短端口 = 8000;

int sockfd = 套接字(AF_INET, SOCK_STREAM, 0);

如果(sockfd < 0)

perror(“插座”);

退出(-1);

结构sockaddr_in my_addr;

bzero(&my_addr,sizeof(my_addr));

我的地址.sin_family = AF_INET;

my_addr.sin_port = htons(端口);

复制代码

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

如果(err_log!= 0)

perror(“绑定”);

关闭(sockfd);

退出(-1);

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

如果 (err_log != 0)

perror(“听”);

关闭(sockfd);

退出(-1);

printf("监听结束后\n");

sleep(20); //延迟20秒

printf("监听客户端@port=%d…\n",port);

int i = 0;

while(1)

结构sockaddr_in客户端地址;

char cli_ip[INET_ADDRSTRLEN] = “”;

socklen_t cliaddr_len = sizeof(client_addr);

int confd;

connfd = 接受(sockfd,(struct sockaddr*)&client_addr,&cliaddr_len);

如果 (connfd < 0)

perror(“接受”);

继续;

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

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

printf("客户端ip=%s,端口=%d\n",cli_ip,ntohs(client_addr.sin_port));

字符recv_buf[512] = {0};

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

printf("接收数据==%s\n",recv_buf);

休息;

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

//printf("客户端已关闭!\n");

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

返回0;

客户

#包括

#包括

#包括

#包括

#包括

#包括

#包括

无效测试_连接()

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

char *server_ip = "10.221.20.12"; //服务器IP地址

int sockfd;

sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建通信端点:socket

如果(sockfd < 0)

perror(“插座”);

退出(-1);

结构sockaddr_in server_addr;

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

服务器地址.sin_family = AF_INET;

服务器地址.sin_port = htons(端口);

inet_pton(AF_INET,server_ip,&服务器地址.sin_addr);

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

如果 (err_log != 0)

perror(“连接”);

关闭(sockfd);

退出(-1);

printf("错误日志 ========= %d\n", 错误日志);

char send_buf[100]="这是用于测试";

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

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

//关闭(sockfd);

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

pid_t pid;

进程ID = fork();

如果(0 == pid){

测试连接(); // 1

pid_t pid = fork();

如果(0 == pid){

测试连接(); // 2

}否则,如果(pid> 0){

测试连接(); // 3

}否则,如果(pid> 0){

测试连接(); // 4

pid_t pid = fork();

如果(0 == pid){

测试连接(); // 5

}否则,如果(pid> 0){

测试连接(); // 6

虽然(1);

返回0;

先运行服务器,再运行客户端。服务器延迟20秒后再调用accept()函数,以保证在客户端的所有connect()调用完成后才调用accept()。结果如下:

客户端运行结果

根据 UNP 的说法,当连接队列已满时(此处将长度设置为 2,发送了 6 个连接),所有后续的 connect() 调用都应该超时并失败。但实际测试结果表明,有些 connect() 调用立即成功返回,而有些则在延迟相当长的时间后才成功返回。服务器 accpet() 函数也是如此:有些立即成功返回,而有些则在延迟后成功返回。

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

服务器 accpet() 函数无法将所有连接从连接队列中取出:

保存图片并直接上传(img-i12tqYDQ-35)(assets/image-

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

当TCP连接队列满了的时候,Linux不会像书上说的那样拒绝连接,但是有些连接会延迟,accept()也可能无法把所有建立的连接都取出来(比如当指定队列长度为0的时候)。在写程序的时候,最好根据需要填写服务器的listen()的第二个参数,不宜写得太大(具体见cat /proc/sys/net/core/somaxconn,默认最大值限制为128),浪费资源,也不宜写得太小,延迟连接的建立。

标题:TCP网络编程中connect、listen、accept的关系函数accrint
链接:https://www.ltthb.com/news/xydt/122043.html
版权:文章转载自网络,如有侵权,请联系删除!
资讯推荐
更多
ToonMe怎么取消自动续费?自动续费关闭方法

ToonMe怎么取消自动续费?自动续费关闭方法[多图],ToonMe中的迪士尼滤镜很火爆,有不少小伙伴都喜欢,不过在使用

2024-07-09
航海王热血航线藏宝图位置在哪?全部藏宝图位置坐标大全

航海王热血航线藏宝图位置在哪?全部藏宝图位置坐标大全[多图],航海王热血航线藏宝图在哪里?怎么样才能找到藏

2024-07-09
cf手游云悠悠角色怎么获得?云悠悠什么时候上线

cf手游云悠悠角色怎么获得?云悠悠什么时候上线[多图],cf手游云悠悠角色什么时候出?云悠悠角色获得的方法是什

2024-07-09
英雄联盟联动优衣库活动详情一览:LOL联动优衣库T恤购买地址入口

英雄联盟联动优衣库活动详情一览:LOL联动优衣库T恤购买地址入口[多图],英雄联盟联动优衣库T恤衫什么时候发售

2024-07-09