实践单机实现百万连接
客户端最大连接数的上限?
操作系统采用 <客户端IP : 客户端端口> : <服务端IP : 服务端端口> 四元组来标识一条TCP连接。
所以要想实现百万连接:
-
第一种是服务器端只开启一个进程,然后使用很多个客户端进程绑定不同的客户端 ip 来连接,假设 20个ip * 5w(端口范围是最大65535,这里保守算5w)
-
第二种是服务器开启多个进程,这样客户端就可以只使用一个 ip 即可,原理类似。
但在这之前,需要我们先配置些参数:
文件句柄的限制
一个tcp连接就需要占用一个文件描述符,一旦文件描述符用完,新的连接就会返回给我们错误是:Can’topen so many files。linux系统出于安全角度的考虑,在多个位置都限制了可打开的文件描述符的数量,包括系统级、进程级、用户进程级。
-
fs.file-max:当前系统可打开的最大数量
编辑 /etc/sysctl.conf 文件,加入:fs.file-max=1100000
然后输入:
# sysctl -p
可使用
sysctl -p
使之生效。可新打开个终端使用
sysctl -p
查看,配置是否生效。 -
fs.nr_open:当前系统单个进程可打开的最大数量
编辑 /etc/sysctl.conf 文件,加入:fs.nr_open=70000
然后输入:
# sysctl -p
使配置生效。
可新打开个终端使用
sysctl -p
查看,配置是否生效。 -
nofile:每个用户的进程可打开的最大数量
# ulimit -n
这里输出1024,意味着最多只能打开1024个文件。
-
临时修改:
# ulimit -n 1000000
这种修改只对当前登录用户目前的使用环境有效,系统重启或用户退出后就会失效。
-
永久修改:
编辑 /etc/security/limits.conf 文件,下面修改对所有用户生效。* soft nofile 55000 * hard nofile 55000
注意!!!:hard nofile 一定要比 fs.nr_open 要小,否则可能导致用户无法登陆。
验证程序的配置是否生效,可使用:
image.png# cat /proc/[pid]/limits
-
端口号范围限制
操作系统上端口号1024以下是系统保留的,从1024-65535是用户使用的。但有时Linux 默认设置 端口范围并不是 1024-65535。
-
查看端口限制:
image.png# sysctl net.ipv4.ip_local_port_range
或者:
# cat /proc/sys/net/ipv4/ip_local_port_range
-
暂时性修改
# echo 1024 65535 > /proc/sys/net/ipv4/ip_local_port_range
或者:
# sysctl -w net.ipv4.ip_local_port_range="1024 64000"
-
永久性修改
编辑 /etc/sysctl.conf 文件,在其中加入:net.ipv4.ip_local_port_range = 1024 65535
实践
按照方案一,我们只要实现单个客户端进程5w个连接就可以实现单机百万了。
所以这里实践只按照实现单个客户端进程5w个连接目标来做。
客户端代码:
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#define MAX_CONNECTION_NUM 50000
int buildConnect(const char *lIp, const char *sIp, int sPort)
{
int skFd;
if((skFd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
printf("\n Error : Could not create socket\n");
return 0;
}
struct sockaddr_in cliAddr;
cliAddr.sin_family = AF_INET;
cliAddr.sin_addr.s_addr = inet_addr(lIp);
cliAddr.sin_port = 0;
if(bind(skFd, (struct sockaddr *)&cliAddr, sizeof(cliAddr)) < 0)
{
printf("\n Error : Bind Failed \n");
}
struct sockaddr_in srvAddr;
srvAddr.sin_family = AF_INET;
srvAddr.sin_addr.s_addr = inet_addr(sIp);
srvAddr.sin_port = htons(sPort);
if(connect(skFd, (struct sockaddr *)&srvAddr, sizeof(srvAddr)) < 0)
{
printf("\n Error : Connect Failed \n");
return 0;
}
return skFd;
}
int main(int argc, char *argv[])
{
int i = 0, sPort, fd;
char lIp[16], sIp[16];
if(argc != 4)
{
printf("\n Usage: %s <local ip> <server ip> <server port>\n", argv[0]);
return 1;
}
// 1. 从命令行获取并解析local ip、server ip以及端口
strcpy(lIp, argv[1]);
strcpy(sIp, argv[2]);
sPort = atoi(argv[3]);
// 2. 开始建立连接
int *sockets = (int *)malloc(sizeof(int) * MAX_CONNECTION_NUM);
for(i = 1; i <= MAX_CONNECTION_NUM; i++)
{
if(0 == i % 1000)
{//稍稍停顿一下,避免把服务端的握手队列打满
printf("%s 连接 %s:%d成功了 %d 条!\n", lIp, sIp, sPort, i);
sleep(1);
}
fd = buildConnect(lIp, sIp, sPort);
if(fd > 0)
{
sockets[i-1] = fd;
}else{
return 1;
}
}
sleep(300);
// 3. 释放所有的连接
printf("关闭所有的连接...\n");
for(i = 0; i < MAX_CONNECTION_NUM; i++)
{
close(sockets[i]);
}
return 0;
}
服务端代码
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_CONNECTION_NUM 1100000
int main(int argc, char *argv[])
{
char ip[16];
int lisFd, conFd, port;
struct sockaddr_in servAddr;
if(argc != 3)
{
printf("\n Usage: %s <server ip> <server port>\n", argv[0]);
return 1;
}
// 1. 从命令行获取并解析server ip以及端口
strcpy(ip, argv[1]);
port = atoi(argv[2]);
// 2. 创建server
if((lisFd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
printf("\n Error : Could not create socket\n");
return 0;
}
servAddr.sin_family = AF_INET;
servAddr.sin_addr.s_addr = inet_addr(ip);
servAddr.sin_port = htons(port);
if(bind(lisFd, (struct sockaddr*)&servAddr, sizeof(servAddr)) < 0)
{
printf("\n Error : Bind Failed \n");
}
if((listen(lisFd, 1024)) < 0)
{
printf("\n Error : Listen Failed \n");
}
// 3. 接收连接
int i = 0;
int *sockets = (int *)malloc(sizeof(int) * MAX_CONNECTION_NUM);
while(1)
{
conFd = accept(lisFd, (struct sockaddr*)NULL, NULL);
if(conFd > 0)
{
sockets[i++] = conFd;
printf("%s %d accept success:%d\n", ip, port, i);
}
}
}
找两个机器,测试如下:
运行服务端
# gcc server.c -o server
# ./server 192.168.48.139 8080 # 服务端ip 为 192.168.48.139
运行客户端
# gcc client.c -o client
# ./client 192.168.48.137 192.168.48.139 8080 # 客户端ip为 192.168.48.137