poll
poll或select為大部分Unix/Linux程序員所熟悉,這倆個東西原理類似,性能上也不存在明顯差異,但select對所監控的文件描述符數量有限制,所以這里選用poll做說明。
1. 頭文件
# include < sys/ poll. h>
2. 參數說明
int poll ( struct pollfd * fds, unsigned int nfds, int timeout);
和select()不一樣,poll()沒有使用低效的三個基於位的文件描述符set,而是采用了一個單獨的結構體pollfd數組,由fds指針指向這個組。pollfd結構體定義如下:
struct pollfd
{
int fd; /* 文件描述符 */
short events; /* 等待的事件 */
short revents; /* 實際發生了的事件 */
} ;
typedef unsigned long nfds_t;
struct pollfd * fds:是一個struct pollfd結構類型的數組,用於存放需要檢測其狀態的socket描述符;每當調用這個函數之后,系統不需要清空這個數組,操作起來比較方便;特別是對於 socket連接比較多的情況下,在一定程度上可以提高處理的效率;這一點與select()函數不同,調用select()函數之后,select() 函數需要清空它所檢測的socket描述符集合,導致每次調用select()之前都必須把socket描述符重新加入到待檢測的集合中;因此,select()函數適合於只檢測少量socket描述符的情況,而poll()函數適合於大量socket描述符的情況;
如果待檢測的socket描述符為負值,則對這個描述符的檢測就會被忽略,也就是不會對成員變量events進行檢測,在events上注冊的事件也會被忽略,poll()函數返回的時候,會把成員變量revents設置為0,表示沒有事件發生;
經常檢測的事件標記:
POLLIN/POLLRDNORM(可讀)、
POLLOUT/POLLWRNORM(可寫)、
POLLERR(出錯)
合法的事件如下:
POLLIN 有數據可讀。
POLLRDNORM 有普通數據可讀。
POLLRDBAND 有優先數據可讀。
POLLPRI 有緊迫數據可讀。
POLLOUT 寫數據不會導致阻塞。
POLLWRNORM 寫普通數據不會導致阻塞。
POLLWRBAND 寫優先數據不會導致阻塞。
POLLMSG SIGPOLL 消息可用。
此外,revents域中還可能返回下列事件:
POLLER 指定的文件描述符發生錯誤。
POLLHUP 指定的文件描述符掛起事件。
POLLNVAL 指定的文件描述符非法。
這些事件在events域中無意義,因為它們在合適的時候總是會從revents中返回。使用poll()和select()不一樣,你不需要顯式地請求異常情況報告。
POLLIN | POLLPRI等價於select()的讀事件,
POLLOUT |POLLWRBAND等價於select()的寫事件。
POLLIN等價於POLLRDNORM |POLLRDBAND,
而POLLOUT則等價於POLLWRNORM。
如果是對一個描述符上的多個事件感興趣的話,可以把這些常量標記之間進行按位或運算就可以了;
比如:對socket描述符fd上的讀、寫、異常事件感興趣,就可以這樣做:
struct pollfd fds;
fds[nIndex].events=POLLIN | POLLOUT | POLLERR;
當 poll()函數返回時,要判斷所檢測的socket描述符上發生的事件,可以這樣做:
struct pollfd fds;
檢測可讀TCP連接請求:
if((fds[nIndex].revents & POLLIN) == POLLIN){//接收數據/調用accept()接收連接請求}
檢測可寫:
if((fds[nIndex].revents & POLLOUT) == POLLOUT){//發送數據}
檢測異常:
if((fds[nIndex].revents & POLLERR) == POLLERR){//異常處理}
nfds_t nfds:用於標記數組fds中的結構體元素的總數量;
timeout:是poll函數調用阻塞的時間,單位:毫秒;
如果timeout==0,那么 poll() 函數立即返回而不阻塞,
如果timeout==INFTIM,那么poll() 函數會一直阻塞下去,直到所檢測的socket描述符上的感興趣的事件發 生是才返回,如果感興趣的事件永遠不發生,那么poll()就會永遠阻塞下去;
3. 返回值:
>0:數組fds中准備好讀、寫或出錯狀態的那些socket描述符的總數量;
==0:數組fds中沒有任何socket描述符准備好讀、寫,或出錯;此時poll超時,超時時間是timeout毫秒;換句話說,如果所檢測的 socket描述符上沒有任何事件發生的話,那么poll()函數會阻塞timeout所指定的毫秒時間長度之后返回,
-1: poll函數調用失敗,同時會自動設置全局變量errno;errno為下列值之一:
4. 錯誤代碼
EBADF 一個或多個結構體中指定的文件描述符無效。
EFAULTfds 指針指向的地址超出進程的地址空間。
EINTR 請求的事件之前產生一個信號,調用可以重新發起。
EINVALnfds 參數超出PLIMIT_NOFILE值。
ENOMEM 可用內存不足,無法完成請求。
5. 實現機制
poll是一個系統調用,其內核入口函數為sys_poll,sys_poll幾乎不做任何處理直接調用do_sys_poll,do_sys_poll的執行過程可以分為三個部分:
1),將用戶傳入的pollfd數組拷貝到內核空間,因此拷貝操作和數組長度相關,時間上這是一個O(n)操作,這一步的代碼在do_sys_poll中包括從函數開始到調用do_poll前的部分。
2),查詢每個文件描述符對應設備的狀態,如果該設備尚未就緒,則在該設備的等待隊列中加入一項並繼續查詢下一設備的狀態。查詢完所有設備后如果沒有一個設備就緒,這時則需要掛起當前進程等待,直到設備就緒或者超時,掛起操作是通過調用schedule_timeout執行的。設備就緒后進程被通知繼續運行,這時再次遍歷所有設備,以查找就緒設備。這一步因為兩次遍歷所有設備,時間復雜度也是O(n),這里面不包括等待時間。相關代碼在do_poll函數中。
3),將獲得的數據傳送到用戶空間並執行釋放內存和剝離等待隊列等善后工作,向用戶空間拷貝數據與剝離等待隊列等操作的的時間復雜度同樣是O(n),具體代碼包括do_sys_poll函數中調用do_poll后到結束的部分。
6. 注意事項
1). poll() 函數不會受到socket描述符上的O_NDELAY標記和O_NONBLOCK標記的影響和制約,也就是說,不管socket是阻塞的還是非阻塞 的,poll()函數都不會收到影響;
2). poll()函數則只有個別的的操作系統提供支持(如:SunOS、Solaris、AIX、HP提供 支持,但是Linux不提供支持),可移植性差;
7. 范例
7.1. 例一
#include <string.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #include <poll.h> /* int poll(struct pollfd *fds, nfds_t nfds, int timeout); */ /* struct pollfd { int fd; // file descriptor short events; // requested events short revents; // returned events }; */ /* The bits that may be set/returned in events and revents are defined in <poll.h>: POLLIN: There is data to read.(數據可讀) POLLOUT:Writing now will not block.(數據可讀) */ #define OPEN_FLAGS O_RDWR|O_CREAT #define OPEN_MODE 00777 #define W_DATA "howaylee" int main(int argc, char* argv[]) { int ret = -1; int fd1 = -1; int fd2 = -1; char r_buf[12] = {0}; struct pollfd fds[2] = {0}; //open fd1 fd1 = open(argv[1], OPEN_FLAGS, OPEN_MODE); if (-1 == fd1) { perror("open fd1 failed: "); return -1; } //write fd1 ret = write(fd1, W_DATA, sizeof(W_DATA)); if(-1 == ret) { perror("write fd1 failed: "); goto _OUT; } //lseek fd1 head ret = lseek(fd1, 0, SEEK_SET); if(-1 == ret) { perror("lseek fd1 failed: "); goto _OUT; } //open fd2 fd2 = open(argv[2], OPEN_FLAGS, OPEN_MODE); if (-1 == fd2) { perror("open fd2 failed: "); return -1; } /*阻塞,等待程序讀寫操作*/ while(1) { //初始化pollfd fds[0].fd = fd1; //可讀 fds[0].events = POLLIN; fds[1].fd = fd2; //可寫 fds[1].events = POLLOUT; //poll ret = poll(fds, sizeof(fds)/sizeof(fds[0]), -1); if(-1 == ret) { perror("poll failed: "); goto _OUT; } //read fd1 if(fds[0].revents & POLLIN ) { //清空緩存 //memset(r_buf, 0, sizeof(r_buf)); ret = read(fd1, r_buf, sizeof(r_buf)); if(-1 == ret) { perror("poll read failed: "); goto _OUT; } printf("read = %s\n", r_buf); } //write fd2 if(fds[1].revents & POLLOUT ) { ret = write(fd2, r_buf, sizeof(r_buf)); if(-1 == ret) { perror("poll write failed: "); goto _OUT; } printf("write = %s\n", r_buf); } } //close fd1 fd2 close(fd1); close(fd2); _OUT: return ret; }
7.2. 例二
int read_time(int fd, void *buf, int size, int ms) { ssize_t len = 0; int ttl = 16 ; struct pollfd pfd; int i; while (1) { pfd.fd = fd; pfd.events = POLLIN | POLLERR | POLLHUP | POLLNVAL; errno = 0; i = poll(&pfd, 1, ms ); if (i < 0) { if (errno == EINTR) { if (--ttl<=0) { ls_log(LOG_LEVEL_ERROR,"[readn_ex]readn poll ttl overflow, errno:%d size:%d" , errno, size); return -6; } continue; } ls_log(LOG_LEVEL_ERROR,"[readn_ex]readn errno:%d" , errno ); return -1; // error } else if (0==i) { ls_log(LOG_LEVEL_ERROR,"[readn_ex]readn poll timeout" ); return -2; } if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) { ls_log(LOG_LEVEL_ERROR,"[readn_ex]readn err revents:%x errno:%d len:%d" , pfd.revents, errno, size); return -3; } len = read(fd, (char*)buf, size); if (len < 0) { if (errno == EINTR) { if (--ttl<=0) { ls_log(LOG_LEVEL_ERROR,"[readn_ex]readn poll ttl overflow, errno:%d size:%d" , errno, size); return -6; } continue; } return -4; } if (len == 0) //EOF ,is return -5 right??? { ls_log(LOG_LEVEL_INFO,"[readn_ex] EOF"); return -5; } return len; } return -1; }
注:本文並非原創,乃是對多篇網絡資料的整理!