上一篇介紹了select的基本用法(見 http://www.linuxidc.com/Linux/2014-03/97442.htm ),接著來學習一下poll和epoll的基本用法。首先來看poll:
#include <sys/poll.h> int poll (struct pollfd *fds, unsigned int nfds, int timeout);
poll() 采用了struct pollfd 結構數組來保存關心的文件描述符,而不是像select一樣使用三個fd_set ,pollfd結構體定義如下:
struct pollfd { int fd; /* file descriptor */ short events; /* requested events to watch */ short revents; /* returned events witnessed */ };
每一個pollfd結構體指定了一個被監視的文件描述符,fds數組中可以存放多個pollfd結構,而且數量不會像select的FD_SETSIZE一樣被限制在1024或者2048 。數組中每個pollfd結構體的events域是監視該文件描述符的事件掩碼,由用戶來設置這個域。revents域是文件描述符的操作結果事件掩碼,系統調用返回時設置這個域。events域中請求的任何事件都可能在revents域中返回。我們可以設置如下事件:
POLLIN:有數據可讀。
POLLRDNORM:有普通數據可讀。
POLLRDBAND:有優先數據可讀。
POLLPRI:有緊迫數據可讀。
------------------------------------------------------------
POLLOUT:寫數據不會導致阻塞。
POLLWRNORM:寫普通數據不會導致阻塞。
POLLWRBAND:寫優先數據不會導致阻塞。
此外,revents域中還可能返回下列事件:
POLLERR:指定的文件描述符發生錯誤。
POLLHUP:指定的文件描述符掛起事件。
POLLNVAL:指定的文件描述符非法。
注意:只能作為描述字的返回結果存儲在revents中,而不能作為測試條件用於events中。
其中POLLIN | POLLPRI等價於select()的讀事件,POLLOUT | POLLWRBAND等價於select()的寫事件。POLLIN等價於POLLRDNORM | POLLRDBAND,而POLLOUT則等價於POLLWRNORM。假如,要同時監視一個文件描述符是否可讀和可寫,我們可以設置events為POLLIN | POLLOUT。在poll返回時,我們可以檢查revents中的標志,對應於文件描述符請求的events結構體。如果POLLIN事件被設置,則文件描述符可以被讀取而不阻塞。如果POLLOUT被設置,則文件描述符可以寫入而不導致阻塞。這些標志並不是互斥的:它們可能被同時設置,表示這個文件描述符的讀取和寫入操作都會正常返回而不阻塞。
timeout參數指定等待的毫秒數,無論I/O是否准備好,超時時間一到poll都會返回。timeout指定為負數值表示無限超時,UNPv1 中使用的INFTIM 宏貌似現在已經廢棄,因此如果要設置無限等待,直接將timeout賦值為-1;timeout為0指示poll調用立即返回並列出准備好I/O的文件描述符,但並不等待其它的事件。
成功時,poll()返回結構體中revents域不為0的文件描述符個數;如果在超時前沒有任何事件發生,poll()返回0;失敗時,poll()返回-1。
//pollEcho.cpp #include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/types.h> #include <vector> #include <string.h> #include <stdlib.h> #include <fcntl.h> #include <errno.h> #include <poll.h> #include <stropts.h> #include <netdb.h> #define PORT 1314 #define MAX_LINE_LEN 1024 int main() { struct sockaddr_in cli_addr, server_addr; socklen_t addr_len; int one,flags,nrcv,nwrite,nready; int listenfd,connfd; char buf[MAX_LINE_LEN],addr_str[INET_ADDRSTRLEN]; std::vector<struct pollfd> pollfdArray; struct pollfd pfd; bzero(&server_addr, sizeof server_addr); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(PORT); server_addr.sin_addr.s_addr = htonl(INADDR_ANY); listenfd = socket(AF_INET, SOCK_STREAM, 0); if( listenfd < 0) { printf("listen error: %s \n", strerror(errno)); exit(1); } one = 1; setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR, &one, sizeof one); flags = fcntl(listenfd,F_GETFL,0); fcntl(listenfd, F_SETFL, flags | O_NONBLOCK); if(bind(listenfd,reinterpret_cast<struct sockaddr *>(&server_addr),sizeof(server_addr)) < 0) { printf("bind error: %s \n", strerror(errno)); exit(1); } listen(listenfd, 100); pfd.fd = listenfd; pfd.events = POLLIN; pollfdArray.push_back(pfd); while(1) { nready = poll(&(*pollfdArray.begin()), pollfdArray.size(), -1); if( nready < 0) { printf("poll error: %s \n", strerror(errno)); } if( pollfdArray[0].revents & POLLIN) { addr_len = sizeof cli_addr; connfd = accept(listenfd, reinterpret_cast<struct sockaddr *>(&cli_addr), &addr_len); if( connfd < 0) { if( errno != ECONNABORTED || errno != EWOULDBLOCK || errno != EINTR) { printf("accept error: %s \n", strerror(errno)); continue; } } printf("recieve from : %s at port %d\n", inet_ntop(AF_INET,&cli_addr.sin_addr,addr_str,INET_ADDRSTRLEN),cli_addr.sin_port); flags = fcntl(connfd, F_GETFL, 0); fcntl(connfd,F_SETFL, flags | O_NONBLOCK); bzero(&pfd, sizeof pfd); pfd.fd = connfd; pfd.events = POLLIN; pollfdArray.push_back(pfd); if(--nready < 0) { continue; } } for( unsigned int i = 1; i < pollfdArray.size(); i++) // i from 1 not 0 { pfd = pollfdArray[i]; if(pfd.revents & (POLLIN | POLLERR)) { memset(buf, 0, MAX_LINE_LEN); if( (nrcv = read(pfd.fd, buf, MAX_LINE_LEN)) < 0) { if(errno != EWOULDBLOCK || errno != EAGAIN || errno != EINTR) { printf("read error: %s\n",strerror(errno)); } } else if( 0 == nrcv) { close(pfd.fd); pollfdArray.erase(pollfdArray.begin() + i); } else { printf("nrcv: %s\n",buf); nwrite = write(pfd.fd, buf, nrcv); if( nwrite < 0) { if(errno != EAGAIN || errno != EWOULDBLOCK) printf("write error: %s\n",strerror(errno)); } printf("nwrite = %d\n",nwrite); } } } } return 0; }
以上代碼操作的文件描述符都設置成為了非阻塞的狀態,這也是為了更好的配合I/O multiplexing 的執行,試想如果read 或者 write 阻塞在某個描述符上,I/O multiplexing 就失去了真正的意義了,因為此時select/poll 函數就無法處理其它描述符產生的事件了。但是只要設置為非阻塞就夠了嗎? 這顯然是還不夠的,後面會專門寫一篇文章對非阻塞的I/O multiplexing 進行完善。