使用的操作系统:
t$ cat /proc/version
Linux version 4.19.260 (lkmao@ubuntu) (gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.12)) #1 SMP Thu Sep 29 14:19:07 CST 2022
文件句柄的限制
如果不修改连接测试,会报错
一个tcp连接就需要占用一个文件描述符,一旦文件描述符用完,新的连接就会返回给我们错误是:Can’topen so many files。linux系统出于安全角度的考虑,在多个位置都限制了可打开的文件描述符的数量,包括系统级、进程级、用户进程级。
查看某个进程使用文件描述符
lsof -p 进程id |wc -l //文件数量
ls /proc/进程id/fd |wc -l //文件描述符数量
通过ulimit -n 查看一个进程的最多打开个数。
lkmao@ubuntu:~$ ulimit -n
1024
lkmao@ubuntu:~$
修改文件,在该文件尾部添加如下内容:因为要做百万连接测试,所以设置的很大。
sudo tee -a /etc/security/limits.conf << EOF
#
* soft nofile 1020480
* hard nofile 1020480
* soft nproc 1020480
* hard nproc 1020480
EOF
然后重启系统,重新查询
貌似设置成功了。
epoll服务器:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <signal.h>
#include <fcntl.h>
// #define _DEBUG_INFO
#ifdef _DEBUG_INFO
#define DEBUG_INFO(format, ...) printf("%s:%s:%d -- "format"\n" \
,__FILE__,__func__,__LINE__ \
, ##__VA_ARGS__)
#else
#define DEBUG_INFO(format, ...)
#endif
#define _DEBUG_PRINT
#ifdef _DEBUG_PRINT
#define DEBUG_PRINT(format,...) printf(format,##__VA_ARGS__)
#else
#define DEBUG_PRINT(format,...)
#endif
//#define _DEBUG_CORE
#ifdef _DEBUG_CORE
#define DEBUG_CORE(format, ...) printf("%s:%s:%d -- "format"\n" \
,__FILE__,__func__,__LINE__ \
, ##__VA_ARGS__)
#else
#define DEBUG_CORE(format, ...)
#endif
int client_count = 0;
void sig_func(int sig){
DEBUG_INFO("client_count = %d\n",client_count);
sleep(1);
exit(0);
}
struct epoll_event evlist[100000];
static char buf[1024 + 1];
int main(int argc, char **argv)
{
int server_socket = 0;
int epfd;
int res = 0;
struct epoll_event ev;
int disconnect_client_count = 0;
signal(SIGINT,sig_func);
int on = 1;
epfd = epoll_create(1);
if(epfd < 0){
perror("epoll_create");
return -1;
}
server_socket = socket(PF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
if(server_socket == -1){
perror("socket");
exit(1);
}
if(setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR,&on,sizeof(on)) < 0){
perror("setsockopt");
exit(-1);
}
struct sockaddr_in server_addr;
server_addr.sin_port = htons(6600);
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr("192.168.0.11");
res = bind(server_socket,(const struct sockaddr *)&server_addr,sizeof(server_addr));
if(res == -1){
perror("bind");
return -1;
}
res = listen(server_socket,5);
if(res == -1){
perror("listen");
return -1;
}
ev.data.fd = server_socket;
ev.events = EPOLLIN;
epoll_ctl(epfd,EPOLL_CTL_ADD,server_socket,&ev);
DEBUG_INFO("listen:%d",6600);
int flags = 0;
flags = fcntl(server_socket, F_GETFL);
flags |= O_NONBLOCK;
fcntl(server_socket, F_SETFL, flags);
while(1){
int ready = epoll_wait(epfd,evlist,sizeof(evlist)/sizeof(evlist[0]),-1);
if(ready == -1){
if(errno == EINTR){
continue;
}
perror("epoll_wait");
exit(1);
}
DEBUG_INFO("ready %d",ready);
for(int i=0;i<ready;i++){
int fd = evlist[i].data.fd;
if(fd == server_socket){
client_count++;
socklen_t addrlen;
struct sockaddr_in clientaddr;
int newfd;
addrlen = sizeof(clientaddr);
memset(&clientaddr, 0, sizeof(clientaddr));
newfd = accept(server_socket, (struct sockaddr *) &clientaddr, &addrlen);
if (newfd == -1) {
perror("Server accept() error");
} else {
ev.events = EPOLLIN;
ev.data.fd = newfd;
res = epoll_ctl(epfd,EPOLL_CTL_ADD,newfd,&ev);
if(res == -1){
perror("epoll_ctl EPOLL_CTL_ADD");
DEBUG_INFO("epoll_ctl EPOLL_CTL_ADD error");
exit(-1);
}
DEBUG_INFO("New connection from %s:%d on socket %d\n",
inet_ntoa(clientaddr.sin_addr),
clientaddr.sin_port,
newfd);
DEBUG_CORE("client_count = %d\n", client_count);
if(client_count %10 == 0){
DEBUG_PRINT("client_count = %d\n", client_count);
}
}
continue;
}
DEBUG_INFO("fd = %d:events:%s %s %s",fd,
(evlist[i].events & EPOLLIN)?"EPOLLIN":"",
(evlist[i].events & EPOLLHUP)?"EPOLLHUP":"",
(evlist[i].events & EPOLLERR)?"EPOLLERR":"");
int read_len = read(fd,buf,sizeof(buf) - 1);
if(read_len == -1){
if(errno == EINTR){
continue;
}else{
perror("Read");
close(fd);
client_count--;
DEBUG_CORE("client_count = %d\n", client_count);
disconnect_client_count++;
DEBUG_PRINT("disconnect_client_count = %d\n", disconnect_client_count);
continue;
}
}
DEBUG_INFO("read_len: %d",read_len);
if(read_len == 0){
DEBUG_INFO("client disconnected");
close(fd);
client_count--;
DEBUG_CORE("client_count = %d\n", client_count);
disconnect_client_count++;
DEBUG_PRINT("disconnect_client_count = %d\n", disconnect_client_count);
continue;
}
buf[read_len] = '\0';
DEBUG_INFO("buf = %s",buf);
}
}
OUT:
DEBUG_INFO("bye bye");
return 0;
}
4万连接实验
使用前一章写的客户端。
./_build_/client -c 1000 -t 40 -r 192.168.0.11
为了使实验快一点,创建4万个连接,实际上也花了十几分钟呢。
说好的百万连接呢,怎么才测了4万,花时间太久了,下次优化一下,让测试快一点。
小结
为什么会这么慢呢?因为客户端虽然是多线程,但是加了互斥锁,为什么加互斥锁呢?因为在测试过程中,如果不加锁,总是会丢失连接,但是又没有报错。