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;
}


你可能感兴趣的:(开源项目学习)