libcurl实现解析(3) - libcurl对select的使用

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

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-13 01:26:49

libcurl实现解析(3) - libcurl对select的使用的相关文章

libcurl原理解析(2) - libcurl对poll的使用

libcurl同时封装了select以及poll这两种I/O机制.代码中使用宏HAVE_POLL_FINE对这两者进行分离.如果定义了这个宏,则使用poll,否则使用select. 这两者的使用代码都定义在函数curl_poll()中,而函数定义在文件lib/select.c中.为了方便分析,阅读,会将select与poll相关的代码分离开来,各自独立分析. 本篇文章主要分析curl_poll()中对poll的封装使用. 先看下使用到的一些数据结构的定义: typedef int curl_so

libcurl原理解析(1) - 引言

1.引言 在爱立信最近参与的项目是一个数据转发程序,基于http协议.主要使用到了epoll与libcurl这两种技术. 本人负责的是里边的传输模块,也是项目的主要模块.刚好使用到了libcurl这个库,并且用它与epoll进行结合.所以基本上从开始的架构设计,到开发,到性能调试,bug fix,基本贯穿了整个项目开发周期.限于开发进度要求,一直都只是用用libcurl的API,偶尔碰到难题时,会看看官方的document,或者少部分源码.但是,一直都没有好好看看它的内部实现机制.现在项目结束了

关于在Windows XP Server 2003下使用libcurl库的一些问题 附编译好的libcurl下载 和使用libcurl访问百度的例子工程

libcurl编译部分摘抄自别人的文章,后面是我写的的记录 步骤 下载源码 首先去github的对应页面下载curl代码,至于在Windows下你是用的git还是直接下我就不管啦,我直接下的zip. 下载好了之后,解压zip文件. 启动编译工具 在开始菜单中查找: 'Developer Command Prompt for VS <version>' 比如我安装的是VS2015,那么对应的工具就是:'Developer Command Prompt for VS2015',启动它,进入命令行窗

Mybaits 源码解析 (七)----- Select 语句的执行过程分析(下篇)(Mapper方法是如何调用到XML中的SQL的?)全网最详细,没有之一

我们上篇文章讲到了查询方法里面的doQuery方法,这里面就是调用JDBC的API了,其中的逻辑比较复杂,我们这边文章来讲,先看看我们上篇文章分析的地方 SimpleExecutor 1 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLExcep

linux c libcurl的简单使用(转)

curl是Linux下一个非常著名的下载库,通过这个库,可以很简单的实现文件的下载等操作.看一个简单的例子: 1 #include <curl/curl.h> 2 #include <stdio.h> 3 #include <string.h> 4 5 CURL *curl; 6 CURLcode res; 7 8 size_t write_data(void *ptr, size_t size, size_t nmemb, void *stream) 9 { 10 i

libcurl引用(转)

libcurl教程 分类: Open Source C/C++ 网络通信 2009-11-08 22:56 45104人阅读 评论(36) 收藏 举报 ftp服务器http服务器服务器interfacebufferurl 目录(?)[+] 原文地址:http://curl.haxx.se/libcurl/c/libcurl-tutorial.html 译者:JGood(http://blog.csdn.net/JGood ) 译者注:这是一篇介绍如何使用libcurl的入门教程.文档不是逐字逐句

Libcurl细说

libcurl教程 原文地址:http://curl.haxx.se/libcurl/c/libcurl-tutorial.html 译者:JGood(http://blog.csdn.net/JGood ) 译者注:这是一篇介绍如何使用libcurl的入门教程.文档不是逐字逐句按原文翻译,而是根据笔者对libcurl的理解,参考原文写成.文中用到的一些例子,可能不是出自原文,而是笔者在学习过程中,写的一些示例程序(笔者使用的libcurl版本是:7.19.6).出现在这里主要是为了更好的说明l

libcurl教程

名称 libcurl 的编程教程 目标 本文档介绍使用libcurl编程的一般原则和一些基本方法.本文主要是介绍 c 语言的调用接口,同时也可能很好的适用于其他类 c 语言的接口. 跨平台的可移植代码 libcurl库背后的开发人员投入了相当大的努力确保libcurl可以在很多不同的系统和环境里工作. 全局的准备 程序必须初始化一些libcurl的全局函数.这意味着不管你准备使用libcurl多少次,你都应该,且只初始化一次.当你的程序开始的时候,使用 curl_global_init() 这个

C++ 用libcurl库进行http通讯网络编程

http://www.cnblogs.com/moodlxs/archive/2012/10/15/2724318.html 目录索引: 一.LibCurl基本编程框架 二.一些基本的函数 三.curl_easy_setopt函数部分选项介绍 四.curl_easy_perform 函数说明(error 状态码) 五.libcurl使用的HTTP消息头六.获取http应答头信息 七.多线程问题 八.什么时候libcurl无法正常工作 九.关于密码 十.HTTP验证 十一.代码示例 1.基本的ht