本文的基础以及使用的代码模型都继承自上一篇文章。所以请先详细阅读上篇文章。
提示:以下是本篇文章正文内容
如何在上篇文章的基础上使其支持百万并发。主要就是neyreactor结构体如何支持一百万个连接。上篇文章出现的 MAX_EPOLL_EVENTS 宏定义是1024,也就是上篇文章只支持1024个连接,这篇文章我们会将它改造成可以支持到百万连接。
struct ntyreactor {
int epfd;
int blkcnt;
struct eventblock *evblk;
};
把之前的一个ntyevent数组改成一个eventblock,blkcnt存储着链表块的长度。evblk它是一个链表,链表的结点存储这1024个ntyevent,定义如下
struct eventblock {
struct eventblock *next;
struct ntyevent *events;
};
int ntyreactor_alloc(struct ntyreactor *reactor) {
if(reactor == NULL) return -1;
if(reactor->evblk == NULL) return -1;
struct eventblock* evblk = reactor->evblk;
while (evblk->next != NULL)
{
evblk = evblk->next;
}
struct ntyevent *evs = (struct ntyevent *)malloc((MAX_EPOLL_EVENTS) * sizeof(struct ntyevent));
if (evs == NULL) {
printf("ntyreactor_alloc ntyevent failed");
return -2;
}
memset(evs, 0, (MAX_EPOLL_EVENTS) * sizeof(struct ntyevent));
struct eventblock *blk = (struct eventblock *)malloc(sizeof(struct eventblock));
if (blk == NULL) {
printf("ntyreactor_alloc eventblock failed");
return 2;
}
memset(blk, 0, sizeof(struct eventblock));
blk->events = evs;
blk->next = NULL;
evblk->next = blk;
reactor->blkcnt++;
return 1;
}
struct ntyevent* ntyreactor_idx(struct ntyreactor *reactor, int sockfd)
{
int blkidx = sockfd / MAX_EPOLL_EVENTS;
while (blkidx >= reactor->blkcnt)
{
ntyreactor_alloc(reactor);
}
int i = 0;
struct eventblock *blk = reactor->evblk;
while (i++ < blkidx && blk != NULL)
{
blk = blk->next;
}
return &blk->events[sockfd % MAX_EPOLL_EVENTS];
}
代码解读:
其实上面的代码已经将最初的Reactor改造成了一个可以支持百万并发的服务器了,其实它可以支持更多的连接,这取决于服务器的内存。如果内存够大,支持的连接数就越大,如果内存不够大,上面的代码能跑到50w,其实也就说明程序没有什么问题了。
好了,说回正题。如何使用这个链表呢,上篇文章将fd对应的ntyevent写入reactor和通过reactor找到对应fd的ntyevent的方式改为通过ntyreactor_idx函数获取到对应的结点进行写入和读取 。
比如对于将listenfd写入readcor:
int ntyreactor_addlistener(struct ntyreactor *reactor, int sockfd, NCALLBACK *acceptor) {
if (reactor == NULL) return -1;
if (reactor->evblk == NULL) return -1;
struct ntyevent *event = ntyreactor_idx(reactor, sockfd);
nty_event_set(event, sockfd, acceptor, reactor);
nty_event_add(reactor->epfd, EPOLLIN, event);
return 0;
}
recv_cb:
int recv_cb(int fd, int events, void *arg) {
struct ntyreactor *reactor = (struct ntyreactor*)arg;
struct ntyevent *ev = ntyreactor_idx(reactor, fd);
int len = recv(fd, ev->buffer, BUFFER_LENGTH, 0);
nty_event_del(reactor->epfd, ev);
if (len > 0) {
ev->length = len;
ev->buffer[len] = '\0';
printf("C[%d]:%s\n", fd, ev->buffer);
nty_event_set(ev, fd, send_cb, reactor);
nty_event_add(reactor->epfd, EPOLLOUT, ev);
} else if (len == 0) {
close(ev->fd);
//printf("[fd=%d] pos[%ld], closed\n", fd, ev-reactor->events);
} else {
close(ev->fd);
printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno));
}
return len;
}
其他函数可以自己去修改。
通过对之前Reactor模型的修改可以将服务器接受的并发量增加到百万甚至更多。但是仅仅对上面 的修改是不能接受那么多连接的,还需要修改一些地方:
net.ipv4.tcp_mem = 262144 524288 786432
net.ipv4.tcp_wmem = 1024 1024 2048
net.ipv4.tcp_rmem = 1024 1024 2048
fs.file-max = 1048576
将读写缓冲区设置的小一些,这样一个连接使用的内存可以小一些,但是设置完之后仅限于测试百万连接,不然在其他应用中可能会出现问题。然后修改file-max,让进程可以打开的文件描述符数量支持到百万。
然后就尽情的测试吧!good luck。