如何从一台客户机向服务机发起40亿TCP长连接?

原文链接:http://blog.yeyuzhen.cn/?p=193

       这绝不是标题党!其实这是一篇“科普”文章,讲述了关于“socket=((client_ip:client_port)-(server_ip:server_port),pocotol)”的一个普遍的误解。

       因为带着这样的误解,我将曾经的一个测试任务变成了“社交活动”。为了测试新开发出来的TCP长连接服务器的性能,我们需要让一台测试服务机保持100W+的长连接。一台客户机能发起的长连接数最大为65535(这是常识!)。经过小组讨论,以一台客户机发起6W长连接来算,我们需要找到 17 台测试客户机。于是我们将测试服务器搭在了公司内网仅有的一台服务器上。写了一个windows下的benchmark程序,通过各种“社交渠道”分发了个同事。功夫不负有心人,总算压到了100W+以上的TCP长连接。so happy~~~

       回头来总结那次测试任务——我们带着对socket的误解,集体傻B了一回。

       实际上仔细推敲一下socket的定义,我们就可以发现一台客户机向一台服务机能发起的最大TCP长连接数应该是40亿+(65535*65535),而不是普遍认为的65535!因为:

+-------------+         +-------------+
| Client Host |         | Server Host |
+-------------+         +-------------+
|             |    A    |             |
|             | /-------port 54000    |
|    port 59000/        |             |
|             |\        |             |
|             | \-------port 54001    |
|             |    B    |             |
+-------------+         +-------------+

从socket的定义“socket=((client_ip:client_port)-(server_ip:server_port),pocotol)”我们可以知道A和B实际上是不同的连接,这说明了一个客户机的端口是可以连接到服务机的不同端口的。如果 Server Host 开启 65535 个服务端监听端口,那么一个 Client Host 的端口就可以发起 65535 个连接。Client Host 总计有 65535 个端口,所以理论上客户机能够向服务机发起的最大连接数应该是 65535*65535≈40亿+。

让我们来实际验证一下:

// Socket Server
#include 
#include 
#include 
#include 

#include 
#include 
#include 

#include 

using std::memset;

void *SocketHandle(void *_socket)
{
	int client_socket = *((int *)_socket);

	struct sockaddr_in sa_client;
	unsigned int sa_client_len = sizeof(sa_client);
	memset(&sa_client, 0x00, sa_client_len);

	getpeername(client_socket, (struct sockaddr *)&sa_client, (socklen_t *)&sa_client_len);
	char ip[INET_ADDRSTRLEN + 1] = {0};
	memset((void *)ip, 0x00, sizeof(ip));
	inet_ntop(AF_INET, (void *)&(sa_client.sin_addr), ip, INET_ADDRSTRLEN);
	unsigned short int port = ntohs(sa_client.sin_port);
	printf("Client %s:%d\n", ip, port);

	char recv_buf[2048] = {0};
	memset(&recv_buf, 0x00, sizeof(recv_buf));
	int recv_size = 0;

	while(true)
	{
		if((recv_size = recv(client_socket, recv_buf, 2040, 0)) > 0)
		{
			printf("Recv from [%s:%d]:%s\n", ip, port, recv_buf);
			memset(&recv_buf, 0x00, sizeof(recv_buf));
			if(!strcmp(recv_buf, "close"))
			{
				break;
			}
		}
		else if(0 == recv_size)
		{
			printf("Client %s:%d disconnected.\n", ip, port);
			fflush(stdout);
			break;
		}
		else // -1
		{
			printf("Recv failed.\n");
			break;
		}
	}
	shutdown(client_socket, SHUT_RDWR);

	return (NULL);
}

int main(int argc, char *argv[])
{
	if(argc < 2)
	{
		printf("Usage: ./SocketServer \n");
		return (1);
	}

	int listen_port = atoi(argv[1]);
	int server_socket = socket(AF_INET, SOCK_STREAM, 0);
	if(-1 == server_socket)
	{
		printf("Could not create server socket.\n");
	}
	int reuse = 1;
	setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

	struct sockaddr_in sa_server;
	memset(&sa_server, 0x00, sizeof(sa_server));
	sa_server.sin_family = AF_INET;
	sa_server.sin_addr.s_addr = INADDR_ANY;
	sa_server.sin_port = htons(listen_port);

	if(bind(server_socket, (struct sockaddr *)&sa_server, sizeof(sa_server)) < 0)
	{
		printf("Bind server socket fail!\n");
		return (1);
	}

	listen(server_socket, 10);

	struct sockaddr_in sa_client;
	int sa_client_len = sizeof(sa_client);
	memset(&sa_client, 0x00, sa_client_len);

	int client_socket = 0;
	while(true)
	{
		client_socket = accept(server_socket, (struct sockaddr *)&sa_client, (socklen_t *)&sa_client_len);
		if(client_socket < 0)
		{
			printf("Accept fail!");
			return (1);
		}

		pthread_t thread_id;
		pthread_attr_t thread_attr;
		pthread_attr_init(&thread_attr);
		pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);
		pthread_create(&thread_id, &thread_attr, SocketHandle, (void *)&client_socket);
		pthread_attr_destroy(&thread_attr);
	}

	return (0);
}
// Socket Client
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char *argv[])
{
	if(argc < 4)
	{
		printf("Usage: ./SocketClient   \n");
		return (1);
	}
	unsigned short int client_port = atoi(argv[1]);
	unsigned short int server_port = atoi(argv[3]);

	int client_socket = socket(AF_INET, SOCK_STREAM, 0);
	if(-1 == client_socket)
	{
		printf("Create client socket fail.\n");
	}
	int reuse = 1;
	setsockopt(client_socket, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

	struct sockaddr_in sa_client;
	memset((void *)&sa_client, 0x00, sizeof(sa_client));
	sa_client.sin_family = AF_INET;
	sa_client.sin_port = htons(client_port);

	if(bind(client_socket, (struct sockaddr *)&sa_client, sizeof(sa_client)) < 0)
	{
		printf("bind client socket fail!\n");
		return (1);
	}

	struct sockaddr_in sa_server;
	memset(&sa_server, 0x00, sizeof(sa_server));
	sa_server.sin_addr.s_addr = inet_addr(argv[2]);
	sa_server.sin_family = AF_INET;
	sa_server.sin_port = htons(server_port);

	if(connect(client_socket, (struct sockaddr *)&sa_server, (socklen_t)sizeof(sa_server)) < 0)
	{
		printf("Connecting to server fail.\n");
		return (1);
	}

	char send_buf[1024] = {0};
	memset(&send_buf, 0x00, sizeof(send_buf));
	char recv_buf[2048] = {0};
	memset(&recv_buf, 0x00, sizeof(recv_buf));

	strcpy(send_buf, "Hello socket server.");

	while(true)
	{
		if(send(client_socket, send_buf, sizeof(send_buf), 0) < 0)
		{
			printf("Send fail.\n");
			fflush(stdout);
			break;
		}

		int recv_size = 0;
		if((recv_size = recv(client_socket, recv_buf, 2040, 0)) > 0)
		{
			printf("Recv:%s\n", recv_buf);
			memset(&recv_buf, 0x00, sizeof(recv_buf));
		}
		else if(0 == recv_size)
		{
			printf("Client socket disconnected.\n");
			fflush(stdout);
			break;
		}
		else // -1
		{
			printf("Recv failed.\n");
			break;
		}
	}

	shutdown(client_socket, SHUT_RDWR);
	return (0);
}

验证程序很简单,关键就是 client 端要做 bind 操作,使多个客户端使用相同的的端口发起连接。

从日志运行结果可以看到,54000和54001服务端都收到了从59000端口发起的连接。

对计算机基础知识的误解,影响是非常深远的。也许一个误解直接就把你的思维限定在一个死胡同里面。也许一个正确而深刻的理解,就能让你想到一个突破性的解决方案。

你可能感兴趣的:(Linux)