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

标签: connectlistenaccept
1193人阅读 评论(1) 收藏 举报
本文章已收录于:
分类:

一、tcp编程的核心步骤

二、connect()函数分析


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

三、listen()函数


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

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  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.   
  12.     int sockfd;  
  13.     sockfd = socket(AF_INET, SOCK_STREAM, 0);// 创建通信端点:套接字  
  14.     if(sockfd < 0)  
  15.     {  
  16.         perror("socket");  
  17.         exit(-1);  
  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 != 0)  
  28.     {  
  29.         perror("binding");  
  30.         close(sockfd);        
  31.         exit(-1);  
  32.     }  
  33.       
  34.     err_log = listen(sockfd, 0);  
  35.     if(err_log != 0)  
  36.     {  
  37.         perror("listen");  
  38.         close(sockfd);        
  39.         exit(-1);  
  40.     }     
  41.       
  42.     printf("listen client @port=%d...\n",port);  
  43.       
  44.     sleep(10);  // 延时10s  
  45.   
  46.     system("netstat -an | grep 8000");  // 查看连接状态  
  47.       
  48.     return 0;  
  49. }  
#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, 0);
	if(err_log != 0)
	{
		perror("listen");
		close(sockfd);		
		exit(-1);
	}	
	
	printf("listen client @port=%d...\n",port);
	
	sleep(10);	// 延时10s

	system("netstat -an | grep 8000");	// 查看连接状态
	
	return 0;
}


客户端的代码:
  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.23";       // 服务器ip地址  
  12.   
  13.     int sockfd;  
  14.     sockfd = socket(AF_INET, SOCK_STREAM, 0);// 创建套接字  
  15.     if(sockfd < 0)  
  16.     {  
  17.         perror("socket");  
  18.         exit(-1);  
  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.s_addr);  
  26.       
  27.     int err_log = connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));      // 主动连接服务器  
  28.     if(err_log != 0)  
  29.     {  
  30.         perror("connect");  
  31.         close(sockfd);  
  32.         exit(-1);  
  33.     }  
  34.       
  35.     system("netstat -an | grep 8000");  // 查看连接状态  
  36.     while(1);  
  37.     return 0;  
  38. }  
#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.23";    	// 服务器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.s_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;
}


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



三次握手的连接队列

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

为了更好的理解 backlog 参数,我们必须认识到内核为任何一个给定的监听套接口维护一个队列,该队列由两部分构成,分别是完成连接接队列未完成连接队列


1、未完成连接队列(incomplete connection queue),当服务器每收到客户端的一个SYN分节,就会将该客户端放入未完成连接队列,而服务器套接口处于 SYN_RCVD 状态。


2、已完成连接队列(completed connection queue),当客户端和服务器彻底完成三次握手过程,客户端将从未完成连接队列升级成已完成连接队列,并从未完成连接队列中清空该客户端,这些套接口处于 ESTABLISHED 状态。




 

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


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


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



四、accept()函数


accept()函数功能是,从连接队列头部取出一个已经完成的连接,如果这个队列没有已经完成的连接,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 = 8000;           
  12.       
  13.     int sockfd = socket(AF_INET, SOCK_STREAM, 0);     
  14.     if(sockfd < 0)  
  15.     {  
  16.         perror("socket");  
  17.         exit(-1);  
  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 != 0)  
  28.     {  
  29.         perror("binding");  
  30.         close(sockfd);        
  31.         exit(-1);  
  32.     }  
  33.       
  34.     err_log = listen(sockfd, 1);    // 等待队列为2  
  35.     if(err_log != 0)  
  36.     {  
  37.         perror("listen");  
  38.         close(sockfd);        
  39.         exit(-1);  
  40.     }     
  41.     printf("after listen\n");  
  42.       
  43.     sleep(20);  //延时 20秒  
  44.       
  45.     printf("listen client @port=%d...\n",port);  
  46.   
  47.     int i = 0;  
  48.       
  49.     while(1)  
  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 < 0)  
  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[512] = {0};  
  69.         while( recv(connfd, recv_buf, sizeof(recv_buf), 0) > 0 )  
  70.         {  
  71.             printf("recv data ==%s\n",recv_buf);  
  72.             break;  
  73.         }  
  74.           
  75.         close(connfd);     //关闭已连接套接字  
  76.         //printf("client closed!\n");  
  77.           
  78.           
  79.     }  
  80.     close(sockfd);         //关闭监听套接字  
  81.     return 0;  
  82. }  
#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, 1);	// 等待队列为2
	if(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;
}


客户端:
  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(int data)  
  10. {  
  11.     unsigned short port = 8000;             // 服务器的端口号  
  12.     char *server_ip = "10.221.20.23";       // 服务器ip地址  
  13.       
  14.     int sockfd;  
  15.     sockfd = socket(AF_INET, SOCK_STREAM, 0);// 创建通信端点:套接字  
  16.     if(sockfd < 0)  
  17.     {  
  18.         perror("socket");  
  19.         exit(-1);  
  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 != 0)  
  30.     {  
  31.         perror("connect");  
  32.         close(sockfd);  
  33.         exit(-1);  
  34.     }  
  35.     printf("---data=%d---\n",data);  
  36.     system("netstat -an | grep 8000");  // 查看连接状态  
  37.       
  38.     char send_buf[100]="this is for test";  
  39.     send(sockfd, send_buf, strlen(send_buf), 0);   // 向服务器发送信息  
  40.   
  41.     //close(sockfd);  
  42. }  
  43.   
  44. int main(int argc, char *argv[])  
  45. {  
  46.     pid_t pid;  
  47.     pid = fork();  
  48.       
  49.     if(0 == pid){  
  50.   
  51.         test_connect(1);        // 1  
  52.           
  53.         pid_t pid = fork();  
  54.         if(0 == pid){  
  55.             test_connect(2);    // 2  
  56.         }else if(pid > 0){  
  57.             test_connect(3);    // 3  
  58.         }  
  59.           
  60.     }else if(pid > 0){  
  61.           
  62.         test_connect(4);    // 4  
  63.           
  64.         pid_t pid = fork();  
  65.         if(0 == pid){  
  66.             test_connect(5);    // 5  
  67.               
  68.         }else if(pid > 0){  
  69.             test_connect(6);    // 6  
  70.         }  
  71.       
  72.     }  
  73.     while(1);  
  74.       
  75.     return 0;  
  76. }  
#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(int data)
{
	unsigned short port = 8000;        		// 服务器的端口号
	char *server_ip = "10.221.20.23";    	// 服务器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("---data=%d---\n",data);
	system("netstat -an | grep 8000");  // 查看连接状态
	
	char send_buf[100]="this is for test";
	send(sockfd, send_buf, strlen(send_buf), 0);   // 向服务器发送信息

	//close(sockfd);
}

int main(int argc, char *argv[])
{
	pid_t pid;
	pid = fork();
	
	if(0 == pid){

		test_connect(1);		// 1
		
		pid_t pid = fork();
		if(0 == pid){
			test_connect(2);	// 2
		}else if(pid > 0){
			test_connect(3);	// 3
		}
		
	}else if(pid > 0){
		
		test_connect(4);	// 4
		
		pid_t pid = fork();
		if(0 == pid){
			test_connect(5);	// 5
			
		}else if(pid > 0){
			test_connect(6);	// 6
		}
	
	}
	while(1);
	
	return 0;
}

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

客户端结果图:


服务器结果图:



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


TCP 的连接队列满后,Linux 不会如书中所说的拒绝连接,只是有些会延时连接,写程序时服务器的 listen() 的第二个参数最好还是根据需要填写,写太大不好(具体可以看cat /proc/sys/net/core/somaxconn,默认最大值限制是 128),浪费资源,写太小也不好,延时建立连接。




3
2
 
 
我的同类文章
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值