Reactor网络模型核心思想探秘

在网络编程系列文章中,我们实现了一个基于epoll的网络框架 , 并在此基础上开发了一个简单的HTTP服务 , 在那个系列文章中我们使用了读、写两个buffer将网络IO和数据的读写进行了分离,它们之间的扭转完全通过epoll事件通知,如果你认真研究过源码,会发现,所有针对网络IO的操作都是由事件触发的 。这种基于事件触发的网络模型通常我们叫做Reactor网络模型 。
由于网络编程系列文章中代码实现相对比较复杂,不太好讲清楚 。所以,我决定单独出几篇文章对那个系列文章进行一些拓展,主要涉及到网络编程思想和性能测试 。
这篇文章我们通过实现一个简单的网络框架,来说明Reactor网络模型实现的一般思路 , 其本质思想和x.NET项目基本上是一样的,只是在代码上做了非常大的精简,理解起来会轻松很多 。
首先,我们来看一段代码#include <sys/socket.h>#include <errno.h>#include <netinet/in.h>#include <stdio.h>#include <string.h>#include <unistd.h>#include <sys/epoll.h>int mAIn() {int sockfd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(struct sockaddr_in));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(2048);if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))) {perror("bind fail");return -1;}listen(sockfd, 10);printf("sock-fd:%dn", sockfd);int epfd = epoll_create(1);struct epoll_event ev;ev.events = EPOLLIN;ev.data.fd = sockfd;epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);struct epoll_event events[1024] = {0};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 | EPOLLET;ev.data.fd = clientfd;epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);printf("clientfd: %dn", clientfd);} else if (events[i].events & EPOLLIN) {char buffer[10] = {0};int count = recv(connfd, buffer, 10, 0);if (count == 0) {printf("discounnectn");epoll_ctl(epfd, EPOLL_CTL_DEL, connfd, NULL);close(i);continue;}send(connfd, buffer, count, 0);printf("clientfd: %d, count: %d, buffer: %sn", connfd, count, buffer);}}}} 熟悉epoll的人应该对上面的代码比较熟悉 , 这段代码的核心在下面的while主循环,如果是当前Server的Socket说明有新的连接进来,调用accept拿到客户端的fd,将其放在epoll的events中,并注册EPOLLIN事件,一般我们理解为可读事件 。
如果不是sockfd,说明是客户端的fd可读,我们将数据读出来再原样发送回去 。
上面的代码存在的主要问题在于,套接字的accept和读写操作我们是直接写在主循环里了,这将会让代码的逻辑变得难以琢磨 。
对于一个套接字,最直接的操作就是读和写 。所以,最容易想到的就是将读和写分离开 。为了实现读和写分离我们封装两个回调函数,如下:int recv_callback(int fd, char *buffer, int size);int send_callback(int fd, char *buffer, int size);你可以想一下,这两个函数应该怎么写?下面是根据原有的逻辑将读和写封装在了recv_callback和send_callback两个函数中,代码如下:int recv_callback(int fd, char *buffer, int size) {int count = recv(fd, buffer, size, 0);send_callback(fd, buffer, count, 0);return count;}int send_callback(int fd, char *buffer, int size) {int count = send(fd, buffer, size, 0);return count;}然后,在主循环中就可以这样使用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) {


推荐阅读