Reactor网络模型核心思想探秘( 三 )


为了实现上面的效果我们要修改两个地方 , 一个是recv_callback中我们要注册一下EPOLLOUT事件,代码如下:int recv_callback(int fd) {int count = recv(fd, conn_map[fd].rbuf + conn_map[fd].rlen, BUF_LEN - conn_map[fd].rlen, 0);// do somethingmemcpy(conn_map[fd].wbuf, conn_map[fd].rbuf, conn_map[fd].rlen);conn_map[fd].wlen = conn_map[fd].rlen;conn_map[fd].rlen = 0;struct epoll_event ev;ev.events = EPOLLOUT;ev.data.fd = fd;epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);return count;}我们在rbuf拷贝到wbuf之后,给当前fd注册了EPOLLOUT事件,然后我们在主循环中要处理EPOLLOUT事件,代码如下:int main() {...while(1) {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 && sockfd == connfd) {struct sockaddr_in clientaddr;socklen_t len = sizeof(clientaddr);int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);ev.events = EPOLLIN;ev.data.fd = clientaddr;epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);conn_map[clientfd].fd = clientfd;conn_map[clientfd].rlen = 0;conn_map[clientfd].wlen = 0;conn_map[clientfd].recv_call = recv_callback;conn_map[clientfd].send_call = send_callback;memset(conn_map[clientfd].rbuf, 0, BUF_LEN);memset(conn_map[clientfd].wbuf, 0, BUF_LEN);printf("clientfd:%d\n", clientfd);} else if (events[i].events & EPOLLIN) {int count = conn_map[connfd].recv_call(connfd);printf("recv-count:%d\n", count);} else if (events[i].events & EPOLLOUT) { // 处理EPOLLOUT事件int count= conn_map[connfd].send_call(connfd);printf("send-count:%d\n", count);}}} }要注意的是,epfd是在main函数中定义的,而我们在recv_callback中有使用,所以我们可以暂时将epfd声明成一个全局变量,放在外面 。
上面的代码有一个问题,EPOLLOUT事件触发之后你会发现再向当前fd发送数据,就没响应了,这是因为epoll事件被我们修改了,为了解决这个问题我们可以在send_callback执行完之后再设置回去 , 如下:int send_callback(int fd) {int count = send(fd, conn_map[fd].wbuffer, conn_map[fd].wlen, 0);struct epoll_event ev;ev.events = EPOLLIN;ev.data.fd = fd;epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);return count;}这样,我们就将IO操作给屏蔽了 , 在主循环中我们只关注事件,不同的事件调用不同的回调函数 。在对应的回调函数中只做自己该做的,做完之后注册事件通知其它的回调函数 。
但是,上面的代码还不够优雅,对于accept和读事件来讲在epoll中都是EPOLLIN事件,这两个是不是可以合并在一起处理呢?答案是可以的,首先,我们要将accept相关的逻辑给拆出来,拆解之后的代码如下:int accept_callback(int fd) {struct sockaddr_in clientaddr;socklen_t len = sizeof(clientaddr);int clientfd = accept(fd, (struct sockaddr*)&clientaddr, &len);ev.events = EPOLLIN;ev.data.fd = clientaddr;epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);conn_map[clientfd].fd = clientfd;conn_map[clientfd].rlen = 0;conn_map[clientfd].wlen = 0;conn_map[clientfd].recv_call = recv_callback;conn_map[clientfd].send_call = send_callback;memset(conn_map[clientfd].rbuf, 0, BUF_LEN);memset(conn_map[clientfd].wbuf, 0, BUF_LEN);return clientfd;}我们发现,accept_callback和recv_callback以及send_callback的签名是一样的,这样我们可以在conn_channel用一个union,将accept_callback也放到conn_channel中来 。如下:struct conn_channel {int fd;union {callback accept_call;callback recv_call;} call_t;callback send_call;char wbuf[BUF_LEN];int wlen;char rbuf[BUF_LEN];int rlen;};在主循环中,我们就可以先给sockfd注册好accept回调函数,然后我们只需要在主循环中保留两个逻辑就可以了,代码如下:int main() {int sockfd = create_serv(9000);if (sockfd == -1) {


推荐阅读