在本系列的前一篇文章中,介绍了libcurl对poll()的使用。参考"libcurl原理解析(2) - libcurl对poll的使用"。
本篇文章主要分析curl_poll()中对select()的封装使用。与前一篇类似,我们只分离出与select相关的代码。
这个函数中使用到的一些其它的数据结构,可以参考前一篇文章中的介绍。本篇不再介绍。
/*
这个函数是对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()的实际返回值。
下面是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的使用
原文:http://blog.csdn.net/shltsh/article/details/46868249