从epoll构建muduo-2 最简单的epoll

来源:互联网 发布:懒蛋蛋裙子淘宝 编辑:程序博客网 时间:2024/05/11 03:46

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可切换到此版本,在线浏览到这里

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. #define MAX_LINE 100  
  2. #define MAX_EVENTS 500  
  3. #define MAX_LISTENFD 5  
  4.   
  5. int createAndListen()  
  6. {  
  7.     int on = 1;  
  8.     int listenfd = socket(AF_INET, SOCK_STREAM, 0);  
  9.     struct sockaddr_in servaddr;  
  10.     fcntl(listenfd, F_SETFL, O_NONBLOCK); //no-block io  
  11.     setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));  
  12.     servaddr.sin_family = AF_INET;  
  13.     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);  
  14.     servaddr.sin_port = htons(11111);  
  15.       
  16.     if(-1 == bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)))  
  17.     {  
  18.         cout << "bind error, errno:" << errno << endl;   
  19.     }  
  20.   
  21.     if(-1 == listen(listenfd, MAX_LISTENFD))  
  22.     {  
  23.         cout << "listen error, errno:" << errno << endl;   
  24.     }  
  25.     return listenfd;  
  26. }  
  27.   
  28. int main(int args, char** argv)  
  29. {  
  30.     struct epoll_event ev, events[MAX_EVENTS];  
  31.     int listenfd,connfd,sockfd;  
  32.     int readlength;  
  33.     char line[MAX_LINE];  
  34.     struct sockaddr_in cliaddr;  
  35.     socklen_t clilen = sizeof(struct sockaddr_in);  
  36.     int epollfd = epoll_create(1);  
  37.     if (epollfd < 0)  
  38.         cout << "epoll_create error, error:" << epollfd << endl;  
  39.     listenfd = createAndListen();  
  40.     ev.data.fd = listenfd;  
  41.     ev.events = EPOLLIN;  
  42.     epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev);  
  43.   
  44.     for(;;)  
  45.     {  
  46.         int fds = epoll_wait(epollfd, events, MAX_EVENTS, -1);  
  47.         if(fds == -1)  
  48.         {  
  49.             cout << "epoll_wait error, errno:" << errno << endl;   
  50.             break;  
  51.         }  
  52.         for(int i = 0; i < fds; i++)  
  53.         {  
  54.             if(events[i].data.fd == listenfd)  
  55.             {  
  56.                 connfd = accept(listenfd, (sockaddr*)&cliaddr, (socklen_t*)&clilen);  
  57.                 if(connfd > 0)  
  58.                 {  
  59.                     cout << "new connection from "   
  60.                          << "[" << inet_ntoa(cliaddr.sin_addr)   
  61.                          << ":" << ntohs(cliaddr.sin_port) << "]"   
  62.                          << " accept socket fd:" << connfd   
  63.                          << endl;  
  64.                 }  
  65.                 else  
  66.                 {  
  67.                     cout << "accept error, connfd:" << connfd   
  68.                          << " errno:" << errno << endl;   
  69.                 }  
  70.                 fcntl(connfd, F_SETFL, O_NONBLOCK); //no-block io  
  71.                 ev.data.fd = connfd;  
  72.                 ev.events = EPOLLIN;  
  73.                 if( -1 == epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, &ev))  
  74.                     cout << "epoll_ctrl error, errno:" << errno << endl;  
  75.             }  
  76.             else if(events[i].events & EPOLLIN)  
  77.             {  
  78.                 if((sockfd = events[i].data.fd) < 0)  
  79.                 {  
  80.                     cout << "EPOLLIN sockfd < 0 error " << endl;  
  81.                     continue;  
  82.                 }  
  83.                 bzero(line, MAX_LINE);  
  84.                 if((readlength = read(sockfd, line, MAX_LINE)) < 0)  
  85.                 {  
  86.                     if(errno == ECONNRESET)  
  87.                     {  
  88.                         cout << "ECONNREST closed socket fd:" << events[i].data.fd << endl;  
  89.                         close(sockfd);  
  90.                     }   
  91.                 }  
  92.                 else if( readlength == 0)  
  93.                 {  
  94.                     cout << "read 0 closed socket fd:" << events[i].data.fd << endl;  
  95.                     close(sockfd);  
  96.                 }  
  97.                 else  
  98.                 {  
  99.                     if(write(sockfd, line, readlength) != readlength)  
  100.                         cout << "error: not finished one time" << endl;  
  101.                 }  
  102.             }  
  103.         }  
  104.     }  
  105.     return 0;  
  106. }  

程序说明:

使用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描述符上等待事件的发生,并获得事件

epoll编程最基本模型

伪代码

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. epollfd = epoll_create(...)                     //创建epoll描述符  
  2. listenfd = socket(...)                          //创建用于端口监听的socket  
  3. bind(listenfd ...)                              //绑定  
  4. listen(listenfd ...)                            //开始在端口监听  
  5. epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd...)  //将listenfd加入epoll描述符监听  
  6. for(;;)                                         //无限循环  
  7. {  
  8.     n = epoll_wait(...)                         //等待epoll描述符的事件发生  
  9.     for(0 ~ n)                                  //可能有多个读写事件发生,遍历所有事件  
  10.     {  
  11.         if(events[i].data.fd == listenfd)       //通过发生事件的socket描述符确认有新链接,  
  12.         {                                       //而非已经打开的socket的读写事件  
  13.             connfd = accpet(...)                //accept新连接为connfd  
  14.             epoll_ctl(...)                      //connfd加入epoll监听,像上面listenfd一样  
  15.         }  
  16.         else if(events[i].events & EPOLLIN)     //发生事件的socket不是listenfd而是connfd  
  17.         {                                       //且事件集里有EPOLLIN表明有数据要读取  
  18.             n = read(...)                       //读取数据  
  19.             if(n == 0)                          //读到0字节,需要关闭socket  
  20.                 close(connfd)                   //close会自动将connfd从epoll监听中删除  
  21.         }                                       //无需调用epoll_ctl(..EPOLL_CTL_DEL..)  
  22.         else if(...)                            //其他各种事件处理依次处理  
  23.         ...    }                                              
  24. }   
程序非常简陋,虽然能运行,但是问题多多,只处理了读事件,没有输入输出缓冲区,整个代码写在一个巨大的for循环中,这些在后续版本逐一改进。

83行的bzero是从UNP(第一卷P6)里学来的,因为memset的三个参数总是容易搞混。

41行和72行虽然只注册了EPOLLIN事件,但是有另外两个事件会自动被注册:EPOLLERR和EPOLLHUP, man epoll_ctl可以看到相关说明。

54行和76行根据发生事件的socket描述符结合events里的事件判断应该如何进行下一步处理,如果socket描述符为listenfd,就要accpet新连接,否则发生事件的socket就是connfd,需要判断发生了哪些事件,后续调用read还是write,本例只调用了read

89行和95行两处调用了close,后续会详细解释原因

    1 read返回-1,且错误码为ECONNRESET。

    2 read返回0。 

10行和70行将socket设置为no-blocking模式,加入epoll的socket要先设置为no-blocking

11行中用于监听端口的listenfd被设置为SO_REUSEADDR

10 cout如果链式链调用有可能发生线程切换导致输出被分割,不过目前例子是单线程,暂时忽略这个问题。

0 0