用epoll實現異步的Echo服務器
2010年05月07日 星期五 下午 06:48
epoll是Kernel 2.6后新加入的事件機制,在高并發條件下,遠優于select.
用個硬件中的例子吧,可能不太恰當:epoll相當于I/O中斷(有的時候才相應),而select相當于輪詢(總要反復查詢)。 其實epoll比slect好用很多,主要一下幾個用法。 struct epoll_event ; epoll事件體,事件發生時候你可以得到一個它。其中epoll_event.data.fd可以存儲關聯的句柄,epoll_event.event 是監聽標志,常用的有EPOLLIN (有數據,可以讀)、EPOLLOUT(有數據,可以寫)EPOLLET(有事件,通用); (1)創建epoll句柄 int epFd = epoll_create(EPOLL_SIZE); (2)加入一個句柄到epoll的監聽隊列 ev.data.fd = serverFd; ev.events = EPOLLIN | EPOLLET; epoll_ctl(epFd, EPOLL_CTL_ADD, serverFd, &ev); 上面的fd是你要綁定給事件發生時候使用的fd,到時候只能操作這個,下面是事件類型。 使用epoll_ctl添加到之中,EPOLL_CTL_ADD是epoll控制類型,這里監聽的fd和給event的fd一般相同。 (3)等待event返回 int nfds = epoll_wait(epFd, evs, EVENT_ARR, -1); 傳入的evs是epoll_event的數組,EVENT_ARR應當是不超過這個數組的長度。返回nfds的是不超過EVENT_ARR的數值,表示本次等待到了幾個事件。 (4)遍歷事件 注意,這里遍歷的事件是肯定已經發生了的,而select中遍歷的是每個fd,而fd不一定在FDSET中(即不一定有讀事件發生)!這是效率最大的差別所在! for (int i = 0; i < nfds; i++) { //do something } (5)其他技巧 對事件是否是serverFd判斷,如果是,進行accept并加入epoll監聽隊列,要設置異步讀取。 如果evts[i]&EPOLLIN,表示可讀,使用read進行試探,如果>0表示連接沒有關閉,否則連接已經關閉(出發事件又讀取不到東西,表示socket關閉!)。如果<0出錯。如果>0,需要繼續讀取直到為0,但是注意這里的為0是在第一次read不為0的前提下,畢竟我們設置了異步讀取,暫時沒有數據可以讀就返回0了!而如果第一次返回0,那么就是關閉吧! 注意關閉要移出出epoll并且close(clientFd) 羅嗦了好多,看代碼! 查看源代碼 打印幫助 001 /* 002 * main.cc 003 * 004 * Created on: 2009-11-30 005 * Author: liheyuan 006 * Describe: epoll實現阻塞模式服務器(Echo服務器) 007 * 008 * Last Date: 2009-11-30 009 * CopyRight: 2009 @ ICT LiHeyuan 010 */ 011 #include <stdio.h> 012 #include <stdlib.h> 013 #include <unistd.h> 014 #include <fcntl.h> 015 #include <arpa/inet.h> 016 #include <netinet/in.h> 017 #include <sys/epoll.h> 018 #include <errno.h> 019 020 #define EPOLL_SIZE 10 021 #define EVENT_ARR 20 022 #define BACK_QUEUE 10 023 #define PORT 18001 024 #define BUF_SIZE 16 025 026 void setnonblocking(int sockFd) { 027 int opt; 028 029 //獲取sock原來的flag 030 opt = fcntl(sockFd, F_GETFL); 031 if (opt < 0) { 032 printf("fcntl(F_GETFL) fail."); 033 exit(-1); 034 } 035 036 //設置新的flag,非阻塞 037 opt |= O_NONBLOCK; 038 if (fcntl(sockFd, F_SETFL, opt) < 0) { 039 printf("fcntl(F_SETFL) fail."); 040 exit(-1); 041 } 042 } 043 044 int main() { 045 046 int serverFd; 047 048 //創建服務器fd 049 serverFd = socket(AF_INET, SOCK_STREAM, 0); 050 setnonblocking(serverFd); 051 052 //創建epoll,并把 serverFd放入監聽隊列 053 int epFd = epoll_create(EPOLL_SIZE); 054 struct epoll_event ev, evs[EVENT_ARR]; 055 ev.data.fd = serverFd; 056 ev.events = EPOLLIN | EPOLLET; 057 epoll_ctl(epFd, EPOLL_CTL_ADD, serverFd, &ev); 058 059 //綁定服務器端口 060 struct sockaddr_in serverAddr; 061 socklen_t serverLen = sizeof(struct sockaddr_in); 062 serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); 063 serverAddr.sin_port = htons(PORT); 064 if (bind(serverFd, (struct sockaddr *) &serverAddr, serverLen)) { 065 printf("bind() fail.\n"); 066 exit(-1); 067 } 068 069 //打開監聽 070 if (listen(serverFd, BACK_QUEUE)) { 071 printf("Listen fail.\n"); 072 exit(-1); 073 } 074 075 //死循環處理 076 int clientFd; 077 sockaddr_in clientAddr; 078 socklen_t clientLen; 079 char buf[BUF_SIZE]; 080 while (1) { 081 //等待epoll事件的到來,最多取EVENT_ARR個事件 082 int nfds = epoll_wait(epFd, evs, EVENT_ARR, -1); 083 //處理事件 084 for (int i = 0; i < nfds; i++) { 085 if (evs[i].data.fd == serverFd && evs[i].data.fd & EPOLLIN) { 086 //如果是serverFd,表明有新連接連入 087 if ((clientFd = accept(serverFd, 088 (struct sockaddr *) &clientAddr, &clientLen)) < 0) { 089 printf("accept fail.\n"); 090 } 091 printf("Connect from %s:%d\n", inet_ntoa(clientAddr.sin_addr), 092 htons(clientAddr.sin_port)); 093 setnonblocking(clientFd); 094 //注冊accept()到的連接 095 ev.data.fd = clientFd; 096 ev.events = EPOLLIN | EPOLLET; 097 epoll_ctl(epFd, EPOLL_CTL_ADD, clientFd, &ev); 098 } else if (evs[i].events & EPOLLIN) { 099 //如果不是serverFd,則是client的可讀 100 if ((clientFd = evs[i].data.fd) > 0) { 101 //先進行試探性讀取 102 int len = read(clientFd, buf, BUF_SIZE); 103 if (len > 0) { 104 //有數據可以讀,Echo寫入 105 do { 106 if (write(clientFd, buf, len) < 0) { 107 printf("write() fail.\n"); 108 } 109 len = read(clientFd, buf, BUF_SIZE); 110 } while (len > 0); 111 } else if (len == 0) { 112 //出發了EPOLLIN事件,卻沒有可以讀取的,表示斷線 113 printf("Client closed at %d\n", clientFd); 114 epoll_ctl(epFd, EPOLL_CTL_DEL, clientFd, &ev); 115 close(clientFd); 116 evs[i].data.fd = -1; 117 break; 118 } else if (len == EAGAIN) { 119 continue; 120 } else { 121 //client讀取出錯 122 printf("read() fail."); 123 } 124 } 125 } else { 126 printf("other event.\n"); 127 } 128 } 129 } 130 131 return 0; 132 } |