mini-muduo版本传送门
version 0.00 从epoll构建muduo-1 mini-muduo介绍
version 0.01 从epoll构建muduo-2 最简单的epoll
version 0.02 从epoll构建muduo-3 加入第一个类,顺便介绍Reactor
version 0.03 从epoll构建muduo-4 加入Channel
version 0.04 从epoll构建muduo-5 加入Acceptor和TcpConnection
version 0.05 从epoll构建muduo-6 加入EventLoop和Epoll
version 0.06 从epoll构建muduo-7 加入IMuduoUser
version 0.07 从epoll构建muduo-8 加入发送缓冲区和接收缓冲区
version 0.08 从epoll构建muduo-9 加入onWriteComplate回调和Buffer
version 0.09 从epoll构建muduo-10 Timer定时器
version 0.11 从epoll构建muduo-11 单线程Reactor网络模型成型
version 0.12 从epoll构建muduo-12 多线程代码入场
version 0.13 从epoll构建muduo-13 Reactor + ThreadPool 成型
mini-muduo v 0.01版本,这是mini-muduo的第一个版本,整个程序是一个100行的epoll示例
下面粘贴的代码省略了头文件引用,完整可运行的示例可从github下载,使用命令git checkout v0.01可切换到此版本,在线浏览到这里
#define MAX_LINE 100 #define MAX_EVENTS 500 #define MAX_LISTENFD 5 int createAndListen() { int on = 1; int listenfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in servaddr; fcntl(listenfd, F_SETFL, O_NONBLOCK); //no-block io setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(11111); if(-1 == bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr))) { cout << "bind error, errno:" << errno << endl; } if(-1 == listen(listenfd, MAX_LISTENFD)) { cout << "listen error, errno:" << errno << endl; } return listenfd; } int main(int args, char** argv) { struct epoll_event ev, events[MAX_EVENTS]; int listenfd,connfd,sockfd; int readlength; char line[MAX_LINE]; struct sockaddr_in cliaddr; socklen_t clilen = sizeof(struct sockaddr_in); int epollfd = epoll_create(1); if (epollfd < 0) cout << "epoll_create error, error:" << epollfd << endl; listenfd = createAndListen(); ev.data.fd = listenfd; ev.events = EPOLLIN; epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev); for(;;) { int fds = epoll_wait(epollfd, events, MAX_EVENTS, -1); if(fds == -1) { cout << "epoll_wait error, errno:" << errno << endl; break; } for(int i = 0; i < fds; i++) { if(events[i].data.fd == listenfd) { connfd = accept(listenfd, (sockaddr*)&cliaddr, (socklen_t*)&clilen); if(connfd > 0) { cout << "new connection from " << "[" << inet_ntoa(cliaddr.sin_addr) << ":" << ntohs(cliaddr.sin_port) << "]" << " accept socket fd:" << connfd << endl; } else { cout << "accept error, connfd:" << connfd << " errno:" << errno << endl; } fcntl(connfd, F_SETFL, O_NONBLOCK); //no-block io ev.data.fd = connfd; ev.events = EPOLLIN; if( -1 == epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, &ev)) cout << "epoll_ctrl error, errno:" << errno << endl; } else if(events[i].events & EPOLLIN) { if((sockfd = events[i].data.fd) < 0) { cout << "EPOLLIN sockfd < 0 error " << endl; continue; } bzero(line, MAX_LINE); if((readlength = read(sockfd, line, MAX_LINE)) < 0) { if(errno == ECONNRESET) { cout << "ECONNREST closed socket fd:" << events[i].data.fd << endl; close(sockfd); } } else if( readlength == 0) { cout << "read 0 closed socket fd:" << events[i].data.fd << endl; close(sockfd); } else { if(write(sockfd, line, readlength) != readlength) cout << "error: not finished one time" << endl; } } } } return 0; }
程序说明:
1 使用epoll的三个函数
int epoll_create(int size) 创建一个epoll文件描述符
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) 将socket描述符加入/移出epoll监听,修改注册事件
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout) 在epoll描述符上等待事件的发生,并获得事件
2 epoll编程最基本模型
伪代码
epollfd = epoll_create(...) //创建epoll描述符 listenfd = socket(...) //创建用于端口监听的socket bind(listenfd ...) //绑定 listen(listenfd ...) //开始在端口监听 epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd...) //将listenfd加入epoll描述符监听 for(;;) //无限循环 { n = epoll_wait(...) //等待epoll描述符的事件发生 for(0 ~ n) //可能有多个读写事件发生,遍历所有事件 { if(events[i].data.fd == listenfd) //通过发生事件的socket描述符确认有新链接, { //而非已经打开的socket的读写事件 connfd = accpet(...) //accept新连接为connfd epoll_ctl(...) //connfd加入epoll监听,像上面listenfd一样 } else if(events[i].events & EPOLLIN) //发生事件的socket不是listenfd而是connfd { //且事件集里有EPOLLIN表明有数据要读取 n = read(...) //读取数据 if(n == 0) //读到0字节,需要关闭socket close(connfd) //close会自动将connfd从epoll监听中删除 } //无需调用epoll_ctl(..EPOLL_CTL_DEL..) else if(...) //其他各种事件处理依次处理 ... } }3 程序非常简陋,虽然能运行,但是问题多多,只处理了读事件,没有输入输出缓冲区,整个代码写在一个巨大的for循环中,这些在后续版本逐一改进。
4 83行的bzero是从UNP(第一卷P6)里学来的,因为memset的三个参数总是容易搞混。
5 41行和72行虽然只注册了EPOLLIN事件,但是有另外两个事件会自动被注册:EPOLLERR和EPOLLHUP, man epoll_ctl可以看到相关说明。
6 54行和76行根据发生事件的socket描述符结合events里的事件判断应该如何进行下一步处理,如果socket描述符为listenfd,就要accpet新连接,否则发生事件的socket就是connfd,需要判断发生了哪些事件,后续调用read还是write,本例只调用了read
7 89行和95行两处调用了close,后续会详细解释原因
1 read返回-1,且错误码为ECONNRESET。
2 read返回0。
8 10行和70行将socket设置为no-blocking模式,加入epoll的socket要先设置为no-blocking
9 11行中用于监听端口的listenfd被设置为SO_REUSEADDR
10 cout如果链式链调用有可能发生线程切换导致输出被分割,不过目前例子是单线程,暂时忽略这个问题。