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

...} else if (events[i].events & EPOLLIN) {char buffer[10] = {0};int count = recv_callback(fd, buffer, 10);if (count == 0) {printf("disconnect\n");epoll_ctl(epfd, EPOLL_CTL_DEL, connfd, NULL);clise(i);continue;}}}} }虽然我们将读和写拆成了两个方法,但读和写并没有分离开,我们在recv_callback中每次收到数据之后调用send_callback将数据原样又发回去,在这里我们希望recv_callback和send_callback各管各的互不干扰,比如像下面这样int recv_callback(int fd, char *buffer, int size) {int count = recv(fd, buffer, size, 0);return count;}int send_callback(int fd, char *buffer, int size) {int count = send(fd, buffer, size, 0);return count;}但这样明显也是有问题的,在recv_callback中读完了之后,如何发送数据呢?这里,我们可以想一下 , 围绕着一个套接字都有哪些部分呢?是不是可以设计出一个类似字典的结构,这个字典的key对应的就是套接字,而value对应的就是围绕套接字相关的各个组件 。
我们将recv_callback和send_callback放在了一个conn_channel结构体中 , 并且设计了两个buffer,一个用来读数据,另一个用来发数据,conn_channel便是这个字典对应的value,代码如下:#define BUF_LEN1024typedef int(*callback)(int fd);struct conn_channel {int fd;callback recv_call;callback send_call;char wbuf[BUF_LEN];int wlen;char rbuf[BUF_LEN];int rlen;};其中,fd表示的是当前客户端套接字 。然后我们定义一个数组来表示套接字到套接字value的映射关系 , 代码如下:struct conn_channel conn_map[1024] = {0};这样 , 我们在主循环中,就可以像下面这样,往conn_map中添加对应的套接字了,代码如下: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) {...}}} }在上面的代码中,每当accept出来一个客户端的套接字,我们就将它放到conn_map中,设置好读写buffer和回调函数 。但如果你细心点会发现,recv_callback、send_callback和conn_channel中的回调函数签名是不一样的 。所以 , 我们要调整一下这两个函数的实现,调整之后代码如下: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;return count;}int send_callback(int fd) {int count = send(fd, conn_map[fd].wbuffer, conn_map[fd].wlen, 0);return count;}因为有了conn_map,所以原来传进来的buffer和size都不需要了,在conn_channel中已经有记录了 。所以只需要一个fd参数就可以了 。我们在recv_callback中模拟了回复消息,强行将读到的数据写到了wbuffer中 。这里补充一下,conn_channel中的rbuffer是用来从套接字中读数据的,wbuffer表示的是将要发送到套接字的数据 。
你可以试着把上面的代码跑起来,然后你会发现 , 并没有按我们的预期执行,send_callback中的send似乎没有起作用 。这是因为我们只是将数据从rbuffer写到了wbuffer中,而send_callback并没有机会调用 。你可以想一想send_callback放在哪里调用比较合适呢?
在上面的例子中,显然放在主循环中执行比较合适,在epoll中,EPOLLOUT表示可写事件,我们可以利用这个事件 。在recv_callback执行完之后我们注册一个EPOLLOUT事件 , 然后在主循环中我们去监听EPOLLOUT事件 。这样 , 当recv_callback将rbuffer的数据复制到wbuffer中之后,send_callback通过EPOLLOUT事件就可以在主循环中得以执行 。


推荐阅读