关于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 }
------------------------------------------------------------------------
email : aicrosoft1104@126.com
吃遍天下。