原文链接: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端口发起的连接。
对计算机基础知识的误解,影响是非常深远的。也许一个误解直接就把你的思维限定在一个死胡同里面。也许一个正确而深刻的理解,就能让你想到一个突破性的解决方案。