reactor
把epoll返回的fd读写事件进行封装,并为每种事件设置回调函数,把所有关注的fd以及对应的事件存储在一个数据结构里,与epoll内部的红黑树的节点形成一一对应的关系。epoll返回时使用data.ptr得到我们数据结构中对应的entry,再进行处理
对fd的封装如下
typedef int (*NCALLBACK)(int fd, int events, void *arg);
struct ntyevent {
int fd;
int events; //监听的事件:EPOLLIN或EPOLLOUT
void *arg; //传给回调函数的额外参数(reactor指针)
NCALLBACK callback; //回调函数
int used; //当前entry是有有效
char buffer[BUFFER_LENGTH]; //读写共用一个buffer,因为当前监听的事件只能是读或写,不会两个都有
int length; //buffer中有效数据的长度
long last_active; //当前fd上一次活跃的事件
int sticky; //如果置为1,则fd不会因为长时间不活跃而被关闭(用于listenfd)
};
reactor数据结构的定义如下
struct ntyreactor {
int epfd;
int block_num;
struct ntyevent **events; //array of ntyevent *, length = block_num, each block has 1024 ntyevent
};
events是一个指针数组,可扩容。每个元素指向一个1024长度的ntyevent数组
整个服务器大致有如下函数
int accept_cb(int fd, int events, void *arg);
int recv_cb(int fd, int events, void *arg);
int send_cb(int fd, int events, void *arg);
void nty_event_update(struct ntyevent *ev, int fd, NCALLBACK callback, void *arg);
int nty_event_add(int epfd, int events, struct ntyevent *ev);
int nty_event_del(int epfd, struct ntyevent *ev);
int start_listen(short port);
int ntyreactor_init(struct ntyreactor *reactor);
struct ntyevent *ntyreactor_get_event(struct ntyreactor *reactor, int fd);
int ntyreactor_destory(struct ntyreactor *reactor);
int ntyreactor_addlistener(struct ntyreactor *reactor, int listenfd, NCALLBACK acceptor);
int ntyreactor_run(struct ntyreactor *reactor);
先看main函数
#define BUFFER_LENGTH 4096
#define EVENTS_BLOCK_SIZE 1024
#define EVENT_BATCH_SIZE 1024
#define SERVER_PORT 8888
#define PORT_COUNT 100
#define CLIENT_TIMEOUT 15
int main(int argc, char *argv[]) {
unsigned short port = SERVER_PORT;
if (argc == 2) {
port = atoi(argv[1]);
}
struct ntyreactor *reactor = (struct ntyreactor*)calloc(1, sizeof(struct ntyreactor));
ntyreactor_init(reactor);
int i;
for(i = 0; i < PORT_COUNT; i++) {
int listenfd = start_listen(port + i);
ntyreactor_addlistener(reactor, listenfd, accept_cb);
}
ntyreactor_run(reactor);
ntyreactor_destory(reactor);
free(reactor);
return 0;
}
PORT_COUNT设为100时,程序监听从8888开始的100个端口,每个端口至少能接受1万个port,所以连接数能达到一百万。(因为测试的客户端数量比较少,如果不多监听一些端口,会导致(src-ip, src-port, dst-ip, dst-port) 四元组的数量不够用)
其中ntyreactor_run
是程序主循环
int ntyreactor_run(struct ntyreactor *reactor) {
if (reactor == NULL) return -1;