您的位置:首页 > 理论基础 > 计算机网络

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

2015-08-19 11:17 507 查看
文章来源:/article/9136142.html

这绝不是标题党!其实这是一篇“科普”文章,讲述了关于“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!因为:

[plain] view
plaincopy

+-------------+ +-------------+

| 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亿+。

让我们来实际验证一下:

[cpp] view
plaincopy

// 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);

}

[cpp] view
plaincopy

// 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端口发起的连接。



对计算机基础知识的误解,影响是非常深远的。也许一个误解直接就把你的思维限定在一个死胡同里面。也许一个正确而深刻的理解,就能让你想到一个突破性的解决方案。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: