下午研究了一下epoll,参考了以下的博客综合写了一个例子。
这篇文章中有一些和我从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) 关于实例代码
实例代码我运行了一下,感觉有点问题。后来参考了这篇文章()的说法,发现修改之后就可以实行了。关键点有这么几点,
1. EPOLLET其实比EPOLLLT高级,所以优先用。
2. 用EPOLLET的时候,按照man的讲法,是必须要使用非阻塞fd,另外,必须要考虑EAGAIN。
先上服务器代码
1 #include2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 #include 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 < 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 // 对方发送了RST146 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 else156 {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 > 0172 count += recvNum;173 if ( recvNum == MAXLINE)174 {175 continue; // 需要再次读取176 }177 else // 0 < recvNum < MAXLINE178 {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上要处理的事件为EPOLLOUT198 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 // 对端重置,对方发送了RST228 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 else238 {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 > 0252 count += writenLen;253 if (writenLen == MAXLINE)254 {255 // 可能还没有写完256 continue;257 }258 else // 0 < writenLen < MAXLINE259 {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");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 }