libcurl实现解析(2) - libcurl对poll的使用
1.前言
libcurl中同一时候封装了select以及poll这两种I/O机制。
代码中使用宏HAVE_POLL_FINE对这两者进行分离。假设定义了这个宏,则使用poll,否则使用select。
这两者的使用代码都位于函数curl_poll()中,而此函数定义在文件lib/select.c中。
为了方便分析,阅读,会将select与poll相关的代码分离开来。各自独立分析。
本篇文章主要分析curl_poll()中对poll的封装使用。
2.curl_poll函数分析
先看下使用到的一些数据结构的定义:typedef int curl_socket_t ; #define CURL_SOCKET_BAD -1 struct pollfd { curl_socket_t fd; short events; short revents; }; /*查阅了linux的man手冊,对这三个成员的说明例如以下: The field fd contains a file descriptor for an open file. If this field is negative, then the corresponding events field is ignored and the revents field returns zero. (This provides an easy way of ignoring a file descriptor for a single poll() call: simply negate the fd field.) The field events is an input parameter, a bit mask specifying the events the application is interested in for the file descriptorfd. If this field is specified as zero, then all events are ignored for fd and revents returns zero. The field revents is an output parameter, filled by the kernel with the events that actually occurred. The bits returned inrevents can include any of those specified in events, or one of the values POLLERR, POLLHUP, or POLLNVAL. (These three bits are meaningless in the events field, and will be set in the revents field whenever the corresponding condition is true.) */
以下是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 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()获取当前时间 } do { if ( timeout_ms < 0) //为负数。则poll无限等待 pending_ms = -1; else if (! timeout_ms) //为0,则poll会马上返回,即使没有可用的events。 pending_ms = 0; r = poll( ufds, nfds, pending_ms); //真正调用poll() if (r != -1) //poll()调用成功。则跳出循环。 break; //poll调用失败。返回-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) if ( timeout_ms > 0) { pending_ms = timeout_ms - elapsed_ms; if (pending_ms <= 0) { r = 0; //模拟poll超时的情形 break; } } } while (r == -1); /*如今能够对上面的这个while循环总结一下: 1.假设poll调用成功(即返回值>=0)。则结束循环 2.假设poll调用失败(即返回值==-1),则检測errno是否为EINTR错误。 假设不是EINTR,则结束循环。 假设是EINTR,则检測是否已经超时。
超时则结束循环,没有超时则继续polling。 */ if (r < 0) //poll()调用失败 return -1; if (r == 0) //poll()超时 return 0; for (i = 0; i < nfds; i++) { if ( ufds[i].fd == CURL_SOCKET_BAD) continue; if ( ufds[i].revents & POLLHUP) ufds[i].revents |= POLLIN; //fd仍然可能读 if ( ufds[i].revents & POLLERR) ufds[i].revents |= (POLLIN | POLLOUT); //fd仍然可能读写 } /* POLLERR。POLLHUP以及POLLNVAL的含义例如以下: POLLHUP意味着socket连接已经中断。 在TCP中,意味着已经接收到了FIN而且也已经发出去了。 POLLERR意味着socket发生了一个异步错误。
在TCP中。它通常表示已经接收到了一个RST,或者已经发出去了一个RST。假设fd不是一个socket,则POLLERR可能表示设备不支持polling. 对于上述的这两种标识,fd可能处于open状态。还没有被关闭(可是shutdown()函数可能已经调用了)。 POLLNVAL意味着fd是无效的,不代表不论什么已打开的文件。 */ return r; //返回值。此时必然>0 }
这个函数运行完毕后。第一个输入參数ufds中的成员revents可能会被改动。最后,函数返回poll的实际返回值。
3.curl_wait_ms函数分析
以下是curl_wait_ms()函数的详细实现。也是基于poll或者select来实现的。
这里也仅仅讨论它的poll()实现版本号。
/* 这个函数用于等待特定的时间值。在函数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 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(); //调用gettimeofday()或time()获取当前时间 do { r = poll(NULL, 0, pending_ms); //调用poll() if (r != -1) //poll()调用成功。则跳出循环 break; //poll调用失败。返回-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; //模拟poll超时的情形 break; } } while (r == -1); //确保返回值r仅仅能为-1(超时失败)或者0(超时成功)。
//r不可能大于0。由于传入到poll()函数的fds数组是NULL。假设poll的返回值>0,则说明调用出问题了, //故这里会将r置为-1,即调用超时失败。
if (r) r = -1; return r; }