CURL: CURLE_COULDNT_CONNECT问题探究
摘自:: 存储系统研究: socket connect error 99(Cannot assign request address)
这是最近使用libcurl写http服务的压力测试的时候遇到的一个问题,其直接表象是客户端在发送http请求时失败,最终原因是客户端的TIME_WAIT状态的socket进程过多,导致端口被占满。下面看整个分析过程:
(1) 首先看产生错误的源码:
/* get it! */
res = curl_easy_perform(curl_handle); long http_code = 0;
curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &http_code); /* cleanup curl stuff */
curl_easy_cleanup(curl_handle);
if (res != CURLE_OK || http_code != 200) {
cout << uri << ", res = " << res << ", http_code = " << http_code << endl;
}
return (res == CURLE_OK && http_code == 200);
错误日志如下:
http://10.237.92.30:8746/thumbnail/jpeg/l820/AppStore/b262b95f-95b8-4e0e-b4e0-edc3b76e3c81,
res = 7, http_code = 0
http://10.237.92.30:8746/thumbnail/jpeg/l820/AppStore/a4c37951-d8b5-40ff-af27-4efcd1a58e71,
res = 7, http_code = 0
http://10.237.92.30:8746/thumbnail/jpeg/l820/AppStore/abab08ff-75e1-40da-a113-053789e93686,
res = 7, http_code = 0
查看curllib的错误代码,如下,错误代码为CURLE_COULDNT_CONNECT
CURLE_OK = 0,
CURLE_UNSUPPORTED_PROTOCOL, /* 1 */
CURLE_FAILED_INIT, /* 2 */
CURLE_URL_MALFORMAT, /* 3 */
CURLE_NOT_BUILT_IN, /* 4 - [was obsoleted in August 2007 for
7.17.0, reused in April 2011 for 7.21.5] */
CURLE_COULDNT_RESOLVE_PROXY, /* 5 */
CURLE_COULDNT_RESOLVE_HOST, /* 6 */
CURLE_COULDNT_CONNECT, /* 7 */
CURLE_FTP_WEIRD_SERVER_REPLY, /* 8 */
CURLE_REMOTE_ACCESS_DENIED, /* 9 a service was denied by the server
(2) 分析curl_easy_perform返回错误的原因
最直接的办法采用gdb跟踪客户端的运行情况,发现客户端在connect的时候返回错误,在源文件curl-7.28.1/lib/connect.c的singleipconnect函数中,于是加入日志在connect之后打印errno,代码如下:
if(!isconnected && (conn->socktype == SOCK_STREAM)) {
rc = connect(sockfd, &addr.sa_addr, addr.addrlen);
if(-1 == rc) {
error = SOCKERRNO;
printf("connect failed with errno = %d", errno);
}
conn->connecttime = Curl_tvnow();
if(conn->num_addr > 1)
Curl_expire(data, conn->timeoutms_per_addr);
再次运行测试程序,得到如下输出:
connect failed with errno = 99 http://127.0.0.1:8902/thumbnail/jpeg/l820/AppStore/f8913ca1-
ae5f-4fcc-abc5-cbe9ada1a67d, ret_code: 0, res: 7
connect failed with errno = 99 http://127.0.0.1:8902/thumbnail/jpeg/l820/AppStore/3726a1e2-
057e-402d-b347-61c5a5136cd9, ret_code: 0, res: 7
connect failed with errno = 99 http://127.0.0.1:8902/thumbnail/jpeg/l820/AppStore/c19bad67-
6b7d-4dc6-a17a-f74ea525c32a, ret_code: 0, res: 7
connect failed with errno = 99 http://127.0.0.1:8902/thumbnail/jpeg/l820/AppStore/5d778568-
d873-46a7-9651-ad8ac3810bf4, ret_code: 0, res: 7
可以看到errno = 99,在内核的include/asm-generic/errno.h文件中可以查看errno = 99的解释为” Cannot assign requested address”。
#define EAFNOSUPPORT 97 /* Address family not supported by protocol */
#define EADDRINUSE 98 /* Address already in use */
#define EADDRNOTAVAIL 99 /* Cannot assign requested address */
#define ENETDOWN 100 /* Network is down */
(3) errno = 99的原因;
至于connect系统调用为什么返回失败,就只能看系统调用的实现了。
a) connect系统调用
connect系统调用在net/socket.c中实现,Sys_connect系统调用的调用栈如下:
Sys_connect--->
sock->ops->connect // inet_stream_connect
sk->sk_prot->connect // tcp_v4_connect
tcp_v4_connect的作用主要是完成TCP连接三次握手中的第一个握手,即向服务端发送SYNC = 1和一个32位的序号的连接请求包。要发送SYNC请求包,按照TCP/IP协议,就必须有源IP地址和端口,源IP地址的选择和路由相关,需要查询路由表,在ip_route_connect中实现,源端口的选择在__inet_hash_connect中实现,而且如果找不到一个可用的端口,这个函数会返回-EADDRNOTAVAIL,因此基本上可以确定是这个函数返回错误导致connect失败;
b) __inet_hash_connect
这个函数的主要作用是选择一个可用的端口,其主要的实现步骤如下:
i. 调用inet_get_local_port_range(&low, &high);获取可用的端口链表;
- 调用read_seqbegin(&sysctl_local_ports.lock);得到顺序锁;
- 得到可用端口的low和high:
*low = sysctl_local_ports.range[0];
*high = sysctl_local_ports.range[1];
ii. 对于每一个端口,进行下面的步骤:
- 在inet_hashinfo *hinfo中查找这个端口inet_hashinfo用于保存已经使用的端口信息,每个使用的端口在这个hash表中有一个entry;
- 对端口做hash得到链表头(使用链表解决hash冲突)
- 遍历链表中的每一个entry:
a) 判断是否与这个要使用的端口相同,如果相同转到步骤b,如果不相同则遍历下一个entry
b) 找到这个端口,调用check_established(__inet_check_established)判断这个端口是否可以重用(TIME_WAIT状态下的端口并且net.ipv4.tcp_tw_recycle = 1是端口可以重用)
- 如果在链表中没有找到这个端口,表示端口没有被使用,调用inet_bind_bucket_create在hash表中插入一个entry;
iii. 如果到最后都没有找到一个可用的端口就返回EADDRNOTAVAIL;
从这个函数的实现可以看出,主要是由于可用的端口被占满了,所以找不到一个可用的端口,导致连接失败。运行netstat可以发现确实存在很多TIME_WAIT状态的socket,这些socket将可用端口占满了。
[root@test miuistorage-dev]# netstat -n | awk '/^tcp/ {++state[$NF]} END {for(key in state)
print key,"\t",state[key]}'
TIME_WAIT 26837
ESTABLISHED 30
(4) 解决办法:
要解决端口被TIME_WAIT状态的socket占满的问题,可以有以下的解决办法:
a) 修改可用端口范围
查看当前的端口范围:
root@guojun8-desktop:/linux-2.6.34# sysctl net.ipv4.ip_local_port_range
net.ipv4.ip_local_port_range = 32768 61000
修改端口范围:
root@guojun8-desktop:linux-2.6.34# sysctl net.ipv4.ip_local_port_range="32768 62000"
net.ipv4.ip_local_port_range = 32768 62000
这种办法可能不能解决根本问题,因为如果使用短连接,即使增加可用端口还是会被占满的。
b) 设置net.ipv4.tcp_tw_recycle = 1
这个参数表示系统的TIME-WAIT sockets是否可以快速回收
root@guojun8-desktop:linux-2.6.34# sysctl net.ipv4.tcp_tw_recycle=1
net.ipv4.tcp_tw_recycle = 1
c) 设置net.ipv4.tcp_tw_recycle = 1
这个参数表示是否可以重用TIME_WAIT状态的端口;
root@guojun8-desktop:linux-2.6.34# [root@test thumbnail]# sysctl net.ipv4.tcp_tw_reuse=1
net.ipv4.tcp_tw_reuse = 1
(5) 更深入的探讨:sysctl做了什么
可以用strace跟踪一下sysctl的系统调用:
root@guojun8-desktop:linux-2.6.34# strace sysctl net.ipv4.tcp_tw_recycle=1
execve("/sbin/sysctl", ["sysctl", "net.ipv4.tcp_tw_recycle=1"], [/* 20 vars */]) = 0
brk(0) = 0x952f000
…..
open("/proc/sys/net/ipv4/tcp_tw_recycle", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb788e000
write(3, "1\n", 2) = 2
close(3) = 0
munmap(0xb788e000, 4096) = 0
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 8), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb788e000
write(1, "net.ipv4.tcp_tw_recycle = 1\n", 28net.ipv4.tcp_tw_recycle = 1
) = 28
exit_group(0) = ?
可以看到这个程序打开/proc/sys/net/ipv4/tcp_tw_recycle并向文件中写入1,但是这个设置时怎样其作用的呢?在内核中对/proc/sys目录下的文件的i_fop做了特殊的处理,在proc_sys_make_inode 中设置:inode->i_fop = &proc_sys_file_operationsproc_sys_file_operations的定义如下:
static const struct file_operations proc_sys_file_operations = {
.read = proc_sys_read,
.write = proc_sys_write,
};
proc_sys_write中会修改对应的文件,并且修改内存中的内容,不同的文件有不同的proc_handler,如tcp_tw_recycle对应的处理函数是proc_dointvec,这个函数会修改下面的变量:
tcp_death_row.sysctl_tw_recycle
这个变量在内核中表示TIME_WIAT状态的socket是否可以被快速回收。
CURL: CURLE_COULDNT_CONNECT问题探究的更多相关文章
- Sword libcurl库CURLE_COULDNT_CONNECT错误
CURL: CURLE_COULDNT_CONNECT问题分析 测试环境描述在使用libcurl写http客户端进行压力测试的时候会遇到curl_easy_perform()返回CURLE_COULD ...
- PHP curl报错“Problem (2) in the Chunked-Encoded data”解决方案
$s = curl_init(); curl_setopt($s, CURLOPT_POST, true); curl_setopt($s, CURLOPT_POSTFIELDS, $queryStr ...
- curl返回常见错误码
转自:http://blog.csdn.net/cwj649956781/article/details/8086337 CURLE_OK() 所有罚款.继续像往常一样. CURLE_UNSUPPOR ...
- CURL 错误码 中文翻译
这几天用CURL做下载系统,经常会遇到一些问题,很多的错误还是和CURL的option有关.现在把这些错误码贴过来,方便查看一下. 错误代码列表 CURLE_UNSUPPORTED_PROTOCOL ...
- CURL使用2
一:LibCurl 编程流程1.调用curl_global_init()初始化libcurl2.调用 curl_easy_init()函数得到 easy interface型指针3.调用curl_ea ...
- PHP中cURL错误号对照[转]
PHP cURL curl_errno 在php程序编写中,使用curl函数库的几率还是挺高的,如curl_init().curl_setopt().curl_exec().curl_errno()等 ...
- linux下curl编程
LibCurl是免费的客户端URL传输库,支持FTP,FTPS, HTTP, HTTPS, SCP, SFTP, TFTP, TELNET, DICT, FILE ,LDAP等协议,其主页是http: ...
- linux下c/c++方式访问curl的帮助手册
自:http://blog.chinaunix.net/u1/47395/showart_1768832.html 有个业务需求需要通过curl 代理的方式来访问外网 百度了一把,测试可以正常使用.记 ...
- curl错误码说明
1.得到错误码 $errno=curl_errno($ch); if($errno!=0){ -- } 2.错误码说明 <?php return [ '1'=>'CURLE_UNSUPPO ...
随机推荐
- 李洪强漫谈iOS开发[C语言-004]-开发概述程序设计语言程序编译过程
汇编语言 指令用特定的名字来标记,这就是汇编语言 人比较容易看懂汇编语言 汇编直接和程序一一对应的 有汇编器把程序翻译成机器码 把高级语言编译成计算机识别的语言 程序编译过程 命令行 UNIX 系统中 ...
- lintcode:买卖股票的最佳时机 III
买卖股票的最佳时机 III 假设你有一个数组,它的第i个元素是一支给定的股票在第i天的价格.设计一个算法来找到最大的利润.你最多可以完成两笔交易. 样例 给出一个样例数组 [4,4,6,1,1,4,2 ...
- BeanUtils.copyProperties与PropertyUtils.copyProperties用法及区别
一.简介: BeanUtils提供对Java反射和自省API的包装.其主要目的是利用反射机制对JavaBean的属性进行处理.我们知道,一个JavaBean 通常包含了大量的属性,很多情况下,对Jav ...
- 定制CentOS (Redhat AS 5.1)安装盘
CentOS(Redhat)提供了一套完整的自动化安装机制,利用该机制,我们可以自己定制无人值守的自动安装光盘,也可以进行系统裁减,甚至可以以CentOS为基础制作自己软件系统的系统安装盘.以下全部内 ...
- ajax:$.get()
提要: $.get("异步文件",数值,回调函数); 加载XML文档 a.xml <?xml version="1.0" encoding="U ...
- list、set、map的特点
java 集合(list.set.map)的特点 集合相关的类有一大堆,一般也只用到常用的方法增删改查,而且它它们的方法名也基本一样,所以一直都不知道什么时候用什么集合, 今天趁有空特意从网上整理资料 ...
- CMake入门指南-编译教程
CMake是一个比make更高级的编译配置工具,它可以根据不同平台.不同的编译器,生成相应的Makefile或者vcproj项目.通过编写CMakeLists.txt,可以控制生成的Makefile, ...
- [置顶] 炎炎夏日,给你一次极爽的开发体验!——统一开发环境功能升级优化,正式上线V2.0!
作为中国移动应用运行托管平台(MM应用引擎)的开发部署工具,统一开发环境(UDE)在原HTML5跨平台开发功能基础上优化升级,新增跨平台编译(Android/iOS)和云端托管服务,正式上线2.0版本 ...
- BZOJ 1502 月下柠檬树(simpson积分)
题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=1502 题意:给出如下一棵分层的树,给出每层的高度和每个面的半径.光线是平行的,与地面夹角 ...
- Java好文统计( 引用 )
1. java 关于类的路径及编译问题 windows 版本:http://www.ibm.com/developerworks/cn/java/j-classpath-windows/ unix & ...