zoukankan      html  css  js  c++  java
  • 关于epoll的示例

    下午研究了一下epoll,参考了以下的博客综合写了一个例子。

    http://blog.csdn.net/ljx0305/article/details/4065058

    这篇文章中有一些和我从man上面查到的不相符合的地方,特此指出。

    1)关于epoll_create

    这个函数的size参数已经器用。更推荐使用的是epoll_create1(0)来代替普通的用法。另外epoll_create1(EPOLLCLOEXEC)表示生成的epoll fd具有“执行后关闭”特性。

    2) epoll_ctl

    这个函数在指定EPOLL_CTL_DEL时,为了与linux内核2.6.9之前相兼容,还是要让最后的参数指向一个非null变量。

    另外,events.EPOLLONESHOT确实表示只监听一次事件,但是当我们监听完这次事件之后,如果还需要继续监听这个fd的话,只需要使用EPOLL_CTL_MOD修改event。

    3) 关于实例代码

    实例代码我运行了一下,感觉有点问题。后来参考了这篇文章(http://blog.chinaunix.net/uid-20583479-id-1920065.html)的说法,发现修改之后就可以实行了。关键点有这么几点,

    1. EPOLLET其实比EPOLLLT高级,所以优先用。

    2. 用EPOLLET的时候,按照man的讲法,是必须要使用非阻塞fd,另外,必须要考虑EAGAIN。

    先上服务器代码

      1 #include <iostream>
      2 #include <sys/socket.h>
      3 #include <sys/epoll.h>
      4 #include <netinet/in.h>
      5 #include <arpa/inet.h>
      6 #include <fcntl.h>
      7 #include <unistd.h>
      8 #include <stdio.h>
      9 #include <errno.h>
     10 #include <string.h>
     11 
     12 using namespace std;
     13 
     14 #define MAXLINE 5
     15 #define OPEN_MAX 100
     16 #define LISTENQ 20
     17 #define SERV_PORT 5000
     18 #define INFTIM 1000
     19 
     20 void setnonblocking(int sock)
     21 {
     22     int opts;
     23     opts=fcntl(sock,F_GETFL);
     24     if(opts<0)
     25     {
     26         perror("fcntl(sock,GETFL)");
     27         return;
     28     }
     29     opts = opts|O_NONBLOCK;
     30     if(fcntl(sock,F_SETFL,opts)<0)
     31     {
     32         perror("fcntl(sock,SETFL,opts)");
     33         return;
     34     }
     35 }
     36 
     37 void CloseAndDisable(int sockid, epoll_event ee)
     38 {
     39     close(sockid);
     40     ee.data.fd = -1;
     41 }
     42 
     43 int main()
     44 {
     45     int i, maxi, listenfd, connfd, sockfd,epfd,nfds, portnumber;
     46     char line[MAXLINE];
     47     socklen_t clilen;
     48 
     49     portnumber = 5000;
     50 
     51     //声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件
     52 
     53     struct epoll_event ev,events[20];
     54     //生成用于处理accept的epoll专用的文件描述符
     55 
     56     epfd=epoll_create(256);
     57     struct sockaddr_in clientaddr;
     58     struct sockaddr_in serveraddr;
     59     listenfd = socket(AF_INET, SOCK_STREAM, 0);
     63 
     64     memset(&serveraddr, 0, sizeof(serveraddr));
     65     serveraddr.sin_family = AF_INET;
     66     serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
     67     serveraddr.sin_port=htons(portnumber);
     68 
     69     // bind and listen
     70     bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr));
     71     listen(listenfd, LISTENQ);
     72 
     73     //设置与要处理的事件相关的文件描述符
     74     ev.data.fd=listenfd;
     75     //设置要处理的事件类型
     76     ev.events=EPOLLIN|EPOLLET;
     77     //ev.events=EPOLLIN;
     78 
     79     //注册epoll事件
     80     epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
     81 
     82     maxi = 0;
     83 
     84     int bOut = 0;
     85     for ( ; ; )
     86     {
     87         if (bOut == 1)
     88             break;
     89         //等待epoll事件的发生
     90 
     91         nfds=epoll_wait(epfd,events,20,-1);
     92         //处理所发生的所有事件
     93         cout << "\nepoll_wait returns\n";
     94 
     95         for(i=0;i<nfds;++i)
     96         {
     97             if(events[i].data.fd==listenfd)//如果新监测到一个SOCKET用户连接到了绑定的SOCKET端口,建立新的连接。
     98             {
     99                 connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen);
    100                 if(connfd<0){
    101                     perror("connfd<0");
    102                     return (1);
    103                 }
    104                 
    105 
    106                 char *str = inet_ntoa(clientaddr.sin_addr);
    107                 cout << "accapt a connection from " << str << endl;
    108                 //设置用于读操作的文件描述符
    109 
    110                 setnonblocking(connfd);
    111                 ev.data.fd=connfd;
    112                 //设置用于注测的读操作事件
    113 
    114                 ev.events=EPOLLIN | EPOLLET;
    115                 //ev.events=EPOLLIN;
    116 
    117                 //注册ev
    118                 epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
    119             }
    120             else if(events[i].events & EPOLLIN)//如果是已经连接的用户,并且收到数据,那么进行读入。
    121             {
    122                 cout << "EPOLLIN" << endl;
    123                 if ( (sockfd = events[i].data.fd) < 0)
    124                     continue;
    125 
    126                 char * head = line;
    127                 int recvNum = 0;
    128                 int count = 0;
    129                 bool bReadOk = false;
    130                 while(1)
    131                 {
    132                     // 确保sockfd是nonblocking的
    133                     recvNum = recv(sockfd, head + count, MAXLINE, 0);
    134                     if(recvNum < 0)
    135                     {
    136                         if(errno == EAGAIN)
    137                         {
    138                             // 由于是非阻塞的模式,所以当errno为EAGAIN时,表示当前缓冲区已无数据可读
    139                             // 在这里就当作是该次事件已处理处.
    140                             bReadOk = true;
    141                             break;
    142                         }
    143                         else if (errno == ECONNRESET)
    144                         {
    145                                 // 对方发送了RST
    146                                 CloseAndDisable(sockfd, events[i]);
    147                                 cout << "counterpart send out RST\n";
    148                                 break;
    149                          }
    150                         else if (errno == EINTR)
    151                         {
    152                             // 被信号中断
    153                             continue;
    154                         }
    155                         else
    156                         {
    157                             //其他不可弥补的错误
    158                             CloseAndDisable(sockfd, events[i]);
    159                             cout << "unrecovable error\n";
    160                             break;
    161                         }
    162                    }
    163                    else if( recvNum == 0)
    164                    {
    165                         // 这里表示对端的socket已正常关闭.发送过FIN了。
    166                         CloseAndDisable(sockfd, events[i]);
    167                         cout << "counterpart has shut off\n";
    168                         break;
    169                    }
    170 
    171                    // recvNum > 0
    172                     count += recvNum;
    173                    if ( recvNum == MAXLINE)
    174                    {
    175                        continue;   // 需要再次读取
    176                    }
    177                    else // 0 < recvNum < MAXLINE
    178                    {
    179                        // 安全读完
    180                        bReadOk = true;
    181                        break; // 退出while(1),表示已经全部读完数据
    182                    }
    183                 }
    184 
    185                 if (bReadOk == true)
    186                 {
    187                     // 安全读完了数据
    188                     line[count] = '\0';
    189 
    190                     cout << "we have read from the client : " << line;
    191                     //设置用于写操作的文件描述符
    192 
    193                     ev.data.fd=sockfd;
    194                     //设置用于注测的写操作事件
    195 
    196                     ev.events = EPOLLOUT | EPOLLET;
    197                     //修改sockfd上要处理的事件为EPOLLOUT
    198 
    199                     epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
    200                 }
    201             }
    202             else if(events[i].events & EPOLLOUT) // 如果有数据发送
    203             {
    204                 const char str[] = "hello from epoll : this is a long string which may be cut by the net\n";
    205                 memcpy(line, str, sizeof(str));
    206                 cout << "Write " << line << endl;
    207                 sockfd = events[i].data.fd;
    208 
    209                 bool bWritten = false;
    210                 int writenLen = 0;
    211                 int count = 0;
    212                 char * head = line;
    213                 while(1)
    214                 {
    215                         // 确保sockfd是非阻塞的
    216                         writenLen = send(sockfd, head + count, MAXLINE, 0);
    217                         if (writenLen == -1)
    218                         {
    219                             if (errno == EAGAIN)
    220                             {
    221                                 // 对于nonblocking 的socket而言,这里说明了已经全部发送成功了
    222                                 bWritten = true;
    223                                 break;
    224                             }
    225                             else if(errno == ECONNRESET)
    226                             {
    227                                 // 对端重置,对方发送了RST
    228                                 CloseAndDisable(sockfd, events[i]);
    229                                 cout << "counterpart send out RST\n";
    230                                 break;
    231                             }
    232                             else if (errno == EINTR)
    233                             {
    234                                 // 被信号中断
    235                                 continue;
    236                             }
    237                             else
    238                             {
    239                                 // 其他错误
    240                             }
    241                         }
    242 
    243                         if (writenLen == 0)
    244                         {
    245                             // 这里表示对端的socket已正常关闭.
    246                             CloseAndDisable(sockfd, events[i]);
    247                             cout << "counterpart has shut off\n";
    248                             break;
    249                         }
    250 
    251                         // 以下的情况是writenLen > 0
    252                         count += writenLen;
    253                         if (writenLen == MAXLINE)
    254                         {
    255                             // 可能还没有写完
    256                             continue;
    257                         }
    258                         else // 0 < writenLen < MAXLINE
    259                         {
    260                             // 已经写完了
    261                             bWritten = true;
    262                             break; // 退出while(1)
    263                         }
    264                 }
    265 
    266                 if (bWritten == true)
    267                 {
    268                     //设置用于读操作的文件描述符
    269                     ev.data.fd=sockfd;
    270 
    271                     //设置用于注测的读操作事件
    272                     ev.events=EPOLLIN | EPOLLET;
    273 
    274                     epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
    275                 }
    276             }
    277         }
    278     }
    279     return 0;
    280 }

    注意以下几点:

    1. #14设定为5是故意的,为了测试后续的输入和输出

    2. 整个服务器的功能是先读取字符串,然后向对方写内容。

    3. #110处设置通信socket为非阻塞。

    4. 注意#130~#183的读干净缓冲区的read。

    5. 注意#213~#264的完全写完所需要传送内容的write。

    6. 关于EPOLLET,epoll_wait只有在socket状态发生变化的时候才会返回。所以要对fd进行循环accept,read, write;知直到socket的缓冲区空(read, accept)或者填满(write)为止。

     7. 下面是客户端实验代码

     1 int
     2 main(int argc, char **argv)
     3 {
     4     int                    sockfd;
     5     char                recvline[MAXLINE + 1];
     6     struct sockaddr_in    servaddr;
     7 
     8     if (argc != 2)
     9         err_quit("usage: a.out <IPaddress>");
    10 
    11     if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    12         err_sys("socket error");
    13 
    14     bzero(&servaddr, sizeof(servaddr));
    15     servaddr.sin_family = AF_INET;
    16     servaddr.sin_port   = htons(5000);   
    17     if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
    18         err_quit("inet_pton error for %s", argv[1]);
    19 
    20     if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0)
    21         err_sys("connect error");
    22 
    23     char input[100];
    24     while (fgets(input, 100, stdin) != EOF)
    25     {
    26         write(sockfd, input, strlen(input));
    27 
    28         int n = 0;
    29         int count = 0;
    30         while (1)
    31         {
    32             n = read(sockfd, recvline + count, MAXLINE);
    33             if (n == MAXLINE)
    34             {
    35                 count += n;
    36                 continue;
    37             }
    38             else 
    39                 break;
    40         }
    41         printf("%s\n", recvline);
    42     }
    43     exit(0);
    44 }
  • 相关阅读:
    陶哲轩实分析习题17.1.4
    陶哲轩实分析习题17.1.4
    陶哲轩实分析习题17.1.2
    群给我的直观印象
    MYSQL数据丢失讨论
    提高大型软件项目质量的一些实用型技术分享
    再培养一个扎克伯克:六款适合儿童上手编程的App
    YouTube架构学习体会
    Web系统架构的一些思考
    备受开发者青睐的13款热门开源项目
  • 原文地址:https://www.cnblogs.com/aicro/p/2836170.html
Copyright © 2011-2022 走看看