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

connect()函数

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

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

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

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

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

TCP网络编程中co<em></em>nnect()、listen()和accept()三者之间的关系nerror="javascript:errorimg.call(this);">

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

客户端:

#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;}

三次握手的连接队列

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

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

TCP网络编程中co<em></em>nnect()、listen()和accept()三者之间的关系nerror="javascript:errorimg.call(this);">

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

accept()函数

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

服务器:

#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);	// 等待队列为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;}

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

客户端运行效果图:

TCP网络编程中co<em></em>nnect()、listen()和accept()三者之间的关系nerror="javascript:errorimg.call(this);">

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

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

TCP网络编程中co<em></em>nnect()、listen()和accept()三者之间的关系nerror="javascript:errorimg.call(this);">

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


顶一下()     踩一下()

热门推荐

发表评论
0评