原文地址: https://blog.csdn.net/lyztyycode/article/details/80463806
今天写程序用到epoll的模型,翻出原先的代码跑了一下,看到原来define的最大的处理用户上限,感觉有些不妥,所以决定测试一下我的ubuntu 16.04,1G内存的单机上究竟可以建立多少个连接。虽然网上有很多这方面的案例,但是我还是决定自己测试一下,印象深刻,对问题场景有更深的印象。
如果文中有错误或者不全面的地方,希望大家指正。
纠正博客中的错误:我的客户端和服务器在单机上进行,所以其实端口是被客户端消耗掉的,所以我本文中说的端口限制只能限制开放的客户端的上限,实际并不能限制服务器处理的连接数的上限,理论上一台服务器处理的连接数在内存足够多的情况下是可以处理无数连接的(2^48理论数量)。
这篇文章中我没有理解的概念是listen的soccketfd和accept返回的socketfd的不同,listen的socket创建的socketfd绑定的端口,这个socketfd是告诉TCP/IP协议栈这个socketfd指向绑定的ip+port,以后有发往这个ip+port的数据包都是发给这个socket,accept返回的connfd是用来标识一个连接五元组的,所以,tcp服务器能处理的连接数实际是由五元组来确定的,更准确的说是由客户端的ip+port来决定连接数的,对于ipv4地址232,port是216,所以理论的连接数最多是2^48。但实际情况中这个和设备的内存,一条tcp连接占用的内存有关,所以,要切记,65535并不是单机服务器处理的连接数上限。65535硬要说是上限,那就是单机开放不同客户端的连接数。实际这也是不确切的,单机情况下,可以通过设置虚拟ip来突破单机65535这个上限。后续我会把我单机测试的服务器最大连接数的测试实例、如何修改内核参数来增大这个连接数的方法发出来。
我测试代码是自己写的,也很简单,就是客户端程序不停的请求连接(这里都是短链接,长连接和需要数据交互的连接请求情况可能和本文的情况不同,长连接就不会有大量的time_wait情况产生),服务器接收了客户端的请求建立连接,这种情况下,客户端不停的创建socket描述符,起初我的系统设置的上限是1024(ulimit -n查看)。
上代码:
server:
#include <sys/types.h> /* basic system data types */
#include <sys/socket.h> /* basic socket definitions */
#include <netinet/in.h> /* sockaddr_in{} and other Internet defns */
#include <arpa/inet.h> /* inet(3) functions */
#include <sys/epoll.h> /* epoll function */
#include <fcntl.h> /* nonblocking */
#include <sys/resource.h> /*setrlimit */
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#define MAXEPOLLSIZE 65535
#define MAXLINE 10
int setnonblocking(int sockfd)
{
if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK) == -1) {
return -1;
}
return 0;
}
static int iCount = 0;
static int iFaild = 0;
int main(void)
{
int servPort = 6888;
int listenq = 65535;
int listenfd, connfd, kdpfd, nfds, n, curfds, acceptCount = 0;
struct sockaddr_in servaddr, cliaddr;
socklen_t socklen = sizeof(struct sockaddr_in);
struct epoll_event ev;
struct epoll_event events[MAXEPOLLSIZE];
struct rlimit rt;
char buf[MAXLINE];
/* 设置每个进程允许打开的最大文件数 */
rt.rlim_max = rt.rlim_cur = MAXEPOLLSIZE;
if (setrlimit(RLIMIT_NOFILE, &rt) == -1){
perror("setrlimit error");
return -1;
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl (INADDR_ANY);
servaddr.sin_port = htons (servPort);
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd == -1) {
perror("can't create socket file");
return -1;
}
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if (setnonblocking(listenfd) < 0) {
perror("setnonblock error");
}
if (bind(listenfd, (struct sockaddr *) &servaddr, sizeof(struct sockaddr)) == -1){
perror("bind error");
return -1;
}
if (listen(listenfd, listenq) == -1){
perror("listen error");
return -1;
}
/* 创建 epoll 句柄,把监听 socket 加入到 epoll 集合里 */
kdpfd = epoll_create(MAXEPOLLSIZE);
//ev.events = EPOLLIN | EPOLLET;
ev.events = EPOLLIN;
ev.data.fd = listenfd;
if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, listenfd, &ev) < 0){
fprintf(stderr, "epoll set insertion error: fd=%d\n", listenfd);
return -1;
}
curfds = 1;
printf("epollserver startup,port %d, max connection is %d, backlog is %d\n", servPort, MAXEPOLLSIZE, listenq);
for (;;) {
/* 等待有事件发生 */
nfds = epoll_wait(kdpfd, events, curfds, -1);
if (nfds == -1){
perror("epoll_wait");
continue;
}
printf("epoll_wait return\n");
/* 处理所有事件 */
for (n = 0; n < nfds; ++n){
if (events[n].data.fd == listenfd){
connfd = accept(listenfd, (struct sockaddr *)&cliaddr,&socklen);
if (connfd < 0){
iFaild ++;
if(iFaild >= 10)
close(listenfd);
perror("accept error");
continue;
}
iCount++;
sprintf(buf, "accept form %s:%d\n", inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port);
printf("%d:%s %d", ++acceptCount, buf, iCount);
close(connfd);
continue;
}
}
}
close(listenfd);
return 0;
}
client:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <errno.h>
#include <arpa/inet.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#define ERR_EXIT(m)\
do{\
perror(m); \
exit(1); \
}while(0)
int main(void)
{
int socketfd, connfd;
while(1)
{
socketfd = socket(AF_INET, SOCK_STREAM, 0);
if(socketfd < 0)
ERR_EXIT("SOCKET");
printf("socketfd = %d\n", socketfd);
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
serveraddr.sin_port = htons(6888);
//无限循环建立连接,不做数据处理
int opt = 1;
int ret2 = setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if(ret2 < 0)
ERR_EXIT("setsockopt");
printf("1\n");
socklen_t socklen = sizeof(serveraddr);
connfd = connect(socketfd, (const struct sockaddr*)&serveraddr, socklen);
if(connfd < 0)
ERR_EXIT("connfd");
}
close(socketfd);
服务器代码采用epoll,逻辑也简单,有必要说明一点,需要注意close(connfd)的位置,连接建立立马断开连接。还有需要设置SO_REUSEADDR。
ulimit -n 1024情况下:
这时候建立的连接数是1021个短链接,客户端请求了1021个socket描述符,012三个描述符被占用。
单机处理1024个连接轻而易举。
我们来看看提高file descriptors的上限,ulimit -n 65535(端口号是16位无符号整数)
我们再来运行,需要一点时间。
结果是:
意思是,没有可用端口了,我们从服务器端看看一共建立了多少次连接
你会不会想这个数据从何而来?我可以告诉你,其实如果你的机器速度快,这个数据在默认情况下应该是28232,为什么这莫说呢?别忘了我们建立的是短链接,我们的服务器是主动关闭连接的一方,熟悉tcp四挥手的你应该想知道主动关闭的一方在调用了close之后会进入TIME_WAIT状态,我之所以说我的这个数据是28386是因为我的运行时间大于linux默认的timewait时间,有的端口可以复用了,所以这个数值大于28233,那么28232是怎么来的呢?
我们运行这一条命令:
sysctl -a | grep port
得到如下结果:
注意这一行:系统给我们的可用端口的范围是32768-60999,我们计算60999-32768+1正好是28232。所以说我们可以修改这个参数范围来提高支持的连接数。上面还提到产生了大量的timewait,我通过命令:
netstat -nt | awk '/^tcp/ {++state[$NF]} END {for(key in state) print key,"\t",state[key]}'
查看,也可以使用这个命令,netstat -a | grep -i TIME_WAIT | wc -l 结果相同。
用netstat -tcp更加直观:
提一下,之前我的测试服务器close(connfd)这一句并不是建立连接立即调用的,而是等客户端关闭然后服务器才调用,所以服务器是被动关闭的一方,你也可以试一试,这时候服务器会产生大量的closewait:
可能CLOSE_WAIT产生的原因:
大多数都是close的问题。