1、基本概念和测试代码
百万并发(这里只讲网络连接):
前提是:能够同时承载100w客户端的数量(2的20次方)
测试程序代码:
#define MAX_BUFFER 128
#define MAX_EPOLLSIZE (384*1024)
#define MAX_PORT 1
#define TIME_SUB_MS(tv1, tv2) ((tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000)
int isContinue = 0;
static int ntySetNonblock(int fd) {
int flags;
flags = fcntl(fd, F_GETFL, 0);
if (flags < 0) return flags;
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) < 0) return -1;
return 0;
}
static int ntySetReUseAddr(int fd) {
int reuse = 1;
return setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse));
}
int main(int argc, char **argv) {
if (argc <= 2) {
printf("Usage: %s ip port\n", argv[0]);
exit(0);
}
const char *ip = argv[1];
int port = atoi(argv[2]);
int connections = 0;
char buffer[128] = {0};
int i = 0, index = 0;
struct epoll_event events[MAX_EPOLLSIZE];
int epoll_fd = epoll_create(MAX_EPOLLSIZE);
strcpy(buffer, " Data From MulClient\n");
struct sockaddr_in addr;
memset(&addr, 0, sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(ip);
struct timeval tv_begin;
gettimeofday(&tv_begin, NULL);
while (1) {
if (++index >= MAX_PORT) index = 0;
struct epoll_event ev;
int sockfd = 0;
if (connections < 340000 && !isContinue) {
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("socket");
goto err;
}
//ntySetReUseAddr(sockfd);
addr.sin_port = htons(port+index);
if (connect(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) {
perror("connect");
goto err;
}
ntySetNonblock(sockfd);
ntySetReUseAddr(sockfd);
sprintf(buffer, "Hello Server: client --> %d\n", connections);
send(sockfd, buffer, strlen(buffer), 0);
ev.data.fd = sockfd;
ev.events = EPOLLIN | EPOLLOUT;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sockfd, &ev);
connections ++;
}
//connections ++;
if (connections % 1000 == 999 || connections >= 340000) {
struct timeval tv_cur;
memcpy(&tv_cur, &tv_begin, sizeof(struct timeval));
gettimeofday(&tv_begin, NULL);
int time_used = TIME_SUB_MS(tv_begin, tv_cur);
printf("connections: %d, sockfd:%d, time_used:%d\n", connections, sockfd, time_used);
int nfds = epoll_wait(epoll_fd, events, connections, 100);
for (i = 0;i < nfds;i ++) {
int clientfd = events[i].data.fd;
if (events[i].events & EPOLLOUT) {
sprintf(buffer, "data from %d\n", clientfd);
send(sockfd, buffer, strlen(buffer), 0);
} else if (events[i].events & EPOLLIN) {
char rBuffer[MAX_BUFFER] = {0};
ssize_t length = recv(sockfd, rBuffer, MAX_BUFFER, 0);
if (length > 0) {
printf(" RecvBuffer:%s\n", rBuffer);
if (!strcmp(rBuffer, "quit")) {
isContinue = 0;
}
} else if (length == 0) {
printf(" Disconnect clientfd:%d\n", clientfd);
connections --;
close(clientfd);
} else {
if (errno == EINTR) continue;
printf(" Error clientfd:%d, errno:%d\n", clientfd, errno);
close(clientfd);
}
} else {
printf(" clientfd:%d, errno:%d\n", clientfd, errno);
close(clientfd);
}
}
}
usleep(500);
}
return 0;
err:
printf("error : %s\n", strerror(errno));
return 0;
}
运行进行测试,结果如下:
命令行命令: ./mul_port client epoll 192.168.243.131 2048
输出: connections:999,sockfd:1002,time used:1200
服务器代码,利用reactor理念设计服务器:
#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/poll.h>
#include <sys/epoll.h>
#include <sys/time.h>
#define BUFFER_LENGTH 512
typedef int (*RCALLBACK)(int fd);
// listenfd
// EPOLLIN -->
int accept_cb(int fd);
// clientfd
//
int recv_cb(int fd);
int send_cb(int fd);
// conn, fd, buffer, callback
struct conn_item {
int fd;
char rbuffer[BUFFER_LENGTH];
int rlen;
char wbuffer[BUFFER_LENGTH];
int wlen;
union {
RCALLBACK accept_callback;
RCALLBACK recv_callback;
} recv_t;
RCALLBACK send_callback;
};
// libevent -->
int epfd = 0;
struct conn_item connlist[1048576] = {0}; // 1024 2G 2 * 512 * 1024 * 1024
// list
struct timeval zvoice_king;
//
// 1000000
#define TIME_SUB_MS(tv1, tv2) ((tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000)
int set_event(int fd, int event, int flag) {
if (flag) { // 1 add, 0 mod
struct epoll_event ev;
ev.events = event ;
ev.data.fd = fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
} else {
struct epoll_event ev;
ev.events = event;
ev.data.fd = fd;
epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
}
}
int accept_cb(int fd) {
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int clientfd = accept(fd, (struct sockaddr*)&clientaddr, &len);
if (clientfd < 0) {
return -1;
}
set_event(clientfd, EPOLLIN, 1);
connlist[clientfd].fd = clientfd;
memset(connlist[clientfd].rbuffer, 0, BUFFER_LENGTH);
connlist[clientfd].rlen = 0;
memset(connlist[clientfd].wbuffer, 0, BUFFER_LENGTH);
connlist[clientfd].wlen = 0;
connlist[clientfd].recv_t.recv_callback = recv_cb;
connlist[clientfd].send_callback = send_cb;
if ((clientfd % 1000) == 999) {
struct timeval tv_cur;
gettimeofday(&tv_cur, NULL);
int time_used = TIME_SUB_MS(tv_cur, zvoice_king);
memcpy(&zvoice_king, &tv_cur, sizeof(struct timeval));
printf("clientfd : %d, time_used: %d\n", clientfd, time_used);
}
return clientfd;
}
int recv_cb(int fd) { // fd --> EPOLLIN
char *buffer = connlist[fd].rbuffer;
int idx = connlist[fd].rlen;
int count = recv(fd, buffer+idx, BUFFER_LENGTH-idx, 0);
if (count == 0) {
printf("disconnect\n");
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
close(fd);
return -1;
}
connlist[fd].rlen += count;
#if 1 //echo: need to send
memcpy(connlist[fd].wbuffer, connlist[fd].rbuffer, connlist[fd].rlen);
connlist[fd].wlen = connlist[fd].rlen;
connlist[fd].rlen -= connlist[fd].rlen;
#else
//http_request(&connlist[fd]);
//http_response(&connlist[fd]);
#endif
set_event(fd, EPOLLOUT, 0);
return count;
}
int send_cb(int fd) {
char *buffer = connlist[fd].wbuffer;
int idx = connlist[fd].wlen;
int count = send(fd, buffer, idx, 0);
set_event(fd, EPOLLIN, 0);
return count;
}
int init_server(unsigned short port) {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(struct sockaddr_in));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons(port);
if (-1 == bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr))) {
perror("bind");
return -1;
}
listen(sockfd, 10);
return sockfd;
}
// tcp
int main() {
int port_count = 20;
unsigned short port = 2048;
int i = 0;
epfd = epoll_create(1); // int size
for (i = 0;i < port_count;i ++) {
int sockfd = init_server(port + i); // 2048, 2049, 2050, 2051 ... 2057
connlist[sockfd].fd = sockfd;
connlist[sockfd].recv_t.accept_callback = accept_cb;
set_event(sockfd, EPOLLIN, 1);
}
gettimeofday(&zvoice_king, NULL);
struct epoll_event events[1024] = {0};
while (1) { // mainloop();
int nready = epoll_wait(epfd, events, 1024, -1); //
int i = 0;
for (i = 0;i < nready;i ++) {
int connfd = events[i].data.fd;
if (events[i].events & EPOLLIN) { //
int count = connlist[connfd].recv_t.recv_callback(connfd);
//printf("recv count: %d <-- buffer: %s\n", count, connlist[connfd].rbuffer);
} else if (events[i].events & EPOLLOUT) {
// printf("send --> buffer: %s\n", connlist[connfd].wbuffer);
int count = connlist[connfd].send_callback(connfd);
}
}
}
getchar();
//close(clientfd);
}
2、问题总结:
(1)cinnections连接数到1000左右,测试程序结束了
原因:服务器对每个进程打开的文件句柄数量的限制,通过ulimit -n 1048576
book@book-virtual-machine:~/study/zero$ ulimit -a
real-time non-blocking time (microseconds, -R) unlimited
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 12915
max locked memory (kbytes, -l) 495744
max memory size (kbytes, -m) unlimited
open files (-n) 1048576(2的20次方) //这个需要大于1000,000
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 12915
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
同时还需要修改可打开文件句柄的数量最大值:
sudo vim /etc/sysctl.conf
//修改值为1048576
fs.file-max = 1048576
//令修改生效
sudo sysctl -p
(2)运行出现segmentation fault(core dumped)错误,本质就是地址越界了
查看测试程序连接数据是否可以接受,被测试代码引入如下原因:
struct conn_item connlist[1024] = {0};
这里可以将其改为1048576,可以进行测试,但是问题不是这么解决的!!可以考虑用动态数组进行优化。
(3)随着连接数上升,出现地址不够用问题
connections:63999,sockfd:64002,time used:1862
connect:cannot assign requested address
Cannot assign requested addresserror
详细解析:一个tcp意味着一个tcp网络连接,对应tcp control block(tcb),tcb中包含五个信息(sip, dip, sport, dport, proto);在单计算机上运行测试程序,其端口数量最多是65535(源端口号是short类型,前1024个为系统用),源端口数量不足,所以到64000个左右。
若出现20000-30000之间停止,通过sudo vim /etc/sysctl.conf中添加设置,修改完运行sudo sysctl -p令其生效。
net.ipv4.ip_local_port_range = 1024 65535
解决方法:
1)用多个机器上运行测试程序
2)增加目的端口数量
需要改动测试代码(原来是1):
#define MAX_PORT 20
(4)connection refused 服务器程序退出了
(5)在连接几十万客户端情况下,出现下面的结果:
1)服务器端用时突然成倍增加后稳定
2)服务器到90多万自动关闭了。
原因:操作系统会对内存占用达到一定比例的进程强制kill
解决方法:
sudo vim /etc/sysctl.conf
//用户空间tcp缓冲区大小 这个单位是页(1G 2G 3G) 可以都乘上2
net.ipv4.tcp_mem = 262144 524288 786432
//内核空间写缓冲区大小
net.ipv4.tcp_wmem = 1024 1024 2048
//内核空间读缓冲区大小
net.ipv4.tcp_rmem = 1024 1024 2048
//令修改生效
sudo sysctl -p
//如果出现下面问题
sysctl: cannot stat /proc/sys/net/nf_conntrack_max: no such file or directory
//执行
sudo modprobe ip_conntrack
3)某个客户端测试时突然退出,服务器也跟着终止了
原因:该客户端已经产生了大量的连接,突然断掉需要服务端close大量的clientfd,导致服务器雪崩
解决方法:直接原因就是客户端的内存不够了,最直接的方法就是扩大内存,可以通过htop查看电脑内存等使用情况
另一种方法就是扩大
net.ipv4.tcp_mem = 262144 524288 786432
中第二个数,将他提高到比内存还要大的数。