1. 程式人生 > >libcurl實現解析(3) - libcurl對select的使用

libcurl實現解析(3) - libcurl對select的使用

結構體 代碼 ets pos 替代 輸入 int microsoft href

1.前言

在本系列的前一篇文章中。介紹了libcurl對poll()的使用。

參考"libcurl原理解析(2) - libcurl對poll的使用"。

本篇文章主要分析curl_poll()中對select()的封裝使用。與前一篇類似,我們僅僅分離出與select相關的代碼。

2.curl_poll函數分析

這個函數中使用到的一些其他的數據結構,能夠參考前一篇文章中的介紹。本篇不再介紹。

/*
這個函數是對poll()的封裝。假設poll()不存在,則使用select()替代。
假設使用的是select(),而且文件描寫敘述符fd太大,超過了FD_SETSIZE,則返回error。
假設傳入的timeout值是一個負數。則會無限的等待。直到沒有有效的fd被提供。

當發生 這樣的情況(沒有有效的fd)時。則負數timeout值會被忽略,且函數會馬上超時。 返回值: -1 = 系統調用錯誤或fd>=FD_SETSIZE. 0 = timeout. N = 返回的pollfd結構體的個數,且當中的revents成員不為0. */ int Curl_poll(struct pollfd ufds[], unsigned int nfds, int timeout_ms) { struct timeval pending_tv; struct timeval *ptimeout; fd_set fds_read; fd_set fds_write; fd_set fds_err; curl_socket_t maxfd; struct timeval initial_tv = { 0, 0 }; bool fds_none = TRUE; //用於驗證傳入的ufds數組是否有效 unsigned int i; int pending_ms = 0; int error; //保存錯誤碼 int r; //檢測全部fd中是否存在有效的fd。 //假設至少存在一個有效的fd,則fds_none置為false。停止檢測 if (ufds) { for (i = 0; i < nfds; i++) { if (ufds[i].fd != CURL_SOCKET_BAD) { fds_none = FALSE; break; } } } //假設全部的fd都是無效的(即bad socket, -1)。則等待一段時間後。直接返回。 if (fds_none) { r = Curl_wait_ms(timeout_ms); //此函數會隨後進行分析 return r; } //當傳入的timeout值是一個負數(堵塞情形)或者0時。則無需衡量elapsed time. //否則,獲取當前時間。

if (timeout_ms > 0) { pending_ms = timeout_ms; initial_tv = curlx_tvnow();//調用gettimeofday()或time()獲取當前時間 } //每次調用select()前都須要又一次初始化fdset,由於它們既是輸入參數又是輸出參數。 FD_ZERO(&fds_read); FD_ZERO(&fds_write); FD_ZERO(&fds_err); maxfd = (curl_socket_t)-1; for (i = 0; i < nfds; i++) { ufds[i].revents = 0; if (ufds[i].fd == CURL_SOCKET_BAD) //跳過無效的fd continue; VERIFY_SOCK(ufds[i].fd); //檢測是否0<=fd<FD_SETSIZE.超出這個範圍。則返回-1. if (ufds[i].events & (POLLIN | POLLOUT | POLLPRI | POLLRDNORM | POLLWRNORM | POLLRDBAND)) { if (ufds[i].fd > maxfd) //獲取到最大的fd,做為select()的第一個參數。 maxfd = ufds[i].fd; if (ufds[i].events & (POLLRDNORM | POLLIN)) FD_SET(ufds[i].fd, &fds_read); if (ufds[i].events & (POLLWRNORM | POLLOUT)) FD_SET(ufds[i].fd, &fds_write); if (ufds[i].events & (POLLRDBAND | POLLPRI)) FD_SET(ufds[i].fd, &fds_err); } } //做為select()的timeout參數 ptimeout = (timeout_ms < 0) ? NULL : &pending_tv; do { if (timeout_ms > 0) { pending_tv.tv_sec = pending_ms / 1000; pending_tv.tv_usec = (pending_ms % 1000) * 1000; } else if (!timeout_ms) { pending_tv.tv_sec = 0; pending_tv.tv_usec = 0; } //真正調用select(). 第2。3,4參數已經在前面初始化(清空)過了。 r = select((int)maxfd + 1, &fds_read, &fds_write, &fds_err, ptimeout); if (r != -1) //select調用成功,結束循環 break; //select調用失敗。返回-1。通過errno能夠獲取到錯誤碼。

error = SOCKERRNO; //宏定義。

#define SOCKERRNO (errno) //以下的error_not_EINTR 是宏定義. //#define error_not_EINTR (0 || error != EINTR) if (error && error_not_EINTR) //檢測是否存在error,且不是EINTR錯誤 break; //沒有出錯或者存在EINTR錯誤。則推斷是否繼續運行select() if (timeout_ms > 0) { //elapsed_ms是宏定義。 //#define elapsed_ms (int)curlx_tvdiff(curlx_tvnow(), initial_tv) pending_ms = timeout_ms - elapsed_ms; if (pending_ms <= 0) { r = 0; //模擬select超時的情形 break; } } } while (r == -1); /*如今能夠對上面的這個while循環總結一下: 1.假設select調用成功(即返回值>=0),則結束循環 2.假設select調用失敗(即返回值==-1),則檢測errno是否為EINTR錯誤。 假設不是EINTR,則結束循環。 假設是EINTR,則檢測是否已經超時。超時則結束循環,沒有超時則繼續select()。 */ if (r < 0) //select()調用失敗 return -1; if (r == 0) //select()超時 return 0; //select()調用成功, 統計當中狀態發生改變的fd的個數,保存至r. r = 0; for (i = 0; i < nfds; i++) { ufds[i].revents = 0; if (ufds[i].fd == CURL_SOCKET_BAD) continue; if (FD_ISSET(ufds[i].fd, &fds_read)) //fd可讀 ufds[i].revents |= POLLIN; if (FD_ISSET(ufds[i].fd, &fds_write)) //fd可寫 ufds[i].revents |= POLLOUT; if (FD_ISSET(ufds[i].fd, &fds_err)) //fd出錯 ufds[i].revents |= POLLPRI; if (ufds[i].revents != 0) r++; } return r; }

這個函數運行完畢後。第一個輸入參數ufds中的成員revents可能會被改動。最後。函數返回select()的實際返回值。

3.curl_wait_ms函數分析

以下是curl_wait_ms()函數的詳細實現。

也是基於poll或者select來實現的。這裏也僅僅討論它的select()實現版本號。


/*
這個函數用於等待特定的時間值。在函數Curl_socket_ready()以及Curl_poll()中被調用。
當沒有提供不論什麽fd來檢測時。則僅僅是等待特定的一段時間。

假設是在windows平臺下,則winsock中的poll()以及select()超時機制,須要一個有效的socket fd. 這個函數不同意無限等待,假設傳入的值是0或者負數。則馬上返回。 超時時間的精度以及最大值。取決於系統。

返回值: -1 = 系統調用錯誤,或無效的輸入值(timeout),或被中斷。 0 = 指定的時間已經超時 */ int Curl_wait_ms(int timeout_ms) { struct timeval pending_tv; struct timeval initial_tv; int pending_ms; int error; int r = 0; if (!timeout_ms) //超時值為0,馬上返回 return 0; if (timeout_ms < 0) //不能為負數 { SET_SOCKERRNO(EINVAL); return -1; } pending_ms = timeout_ms; initial_tv = curlx_tvnow(); do { pending_tv.tv_sec = pending_ms / 1000; pending_tv.tv_usec = (pending_ms % 1000) * 1000; r = select(0, NULL, NULL, NULL, &pending_tv); if (r != -1) //select()調用成功,則跳出循環 break; //select調用失敗。返回-1。通過errno能夠獲取到錯誤碼。 error = SOCKERRNO; //宏定義。

#define SOCKERRNO (errno) //以下的error_not_EINTR 是宏定義. #define error_not_EINTR (0 || error != EINTR) if (error && error_not_EINTR) ////檢測是否存在error,且不是EINTR錯誤 break; //elapsed_ms是宏定義: //#define elapsed_ms (int)curlx_tvdiff(curlx_tvnow(), initial_tv) pending_ms = timeout_ms - elapsed_ms; if (pending_ms <= 0) { r = 0; //模擬select超時的情形 break; } } while (r == -1); //確保返回值r僅僅能為-1(超時失敗)或者0(超時成功)。

//r不可能大於0,由於傳入到select()函數的3個fdset數組所有都是NULL。假設select的返回值>0,則說明調用出問題了。 //故這裏會將r置為-1,即調用超時失敗。 if (r) r = -1; return r; }


libcurl實現解析(3) - libcurl對select的使用