文章来源:http://blog.csdn.net/lostaway/article/details/39451701
这绝不是标题党!其实这是一篇“科普”文章,讲述了关于“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 <iostream>
- #include <cstdio>
- #include <cstdlib>
- #include <cstring>
- #include <sys/socket.h>
- #include <arpa/inet.h>
- #include <netinet/in.h>
- #include <pthread.h>
- 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 <listen_port>\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 <cstdio>
- #include <cstdlib>
- #include <cstring>
- #include <unistd.h>
- #include <sys/socket.h>
- #include <arpa/inet.h>
- int main(int argc, char *argv[])
- {
- if(argc < 4)
- {
- printf("Usage: ./SocketClient <client_port> <server_ip> <server_port>\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端口发起的连接。
对计算机基础知识的误解,影响是非常深远的。也许一个误解直接就把你的思维限定在一个死胡同里面。也许一个正确而深刻的理解,就能让你想到一个突破性的解决方案。