关于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 }

 

 

 

posted @ 2012-12-27 18:09  aicro  阅读(24709)  评论(3编辑  收藏  举报