2019独角兽企业重金招聘Python工程师标准>>>
php下使用CURL设置超时的问题
问题描述:
考虑以下使用php进行 curl调用的场景:
$ch = curl_init();
//...
$limitTime = $_SERVER['REQUEST_TIME']; //误用当前时间当超时时间
curl_setopt($this->_ch, CURLOPT_TIMEOUT, $limitTime);
$result = curl_exec($ch);
由于误用(可能是业务上)了当时时间为CURL设置超时时间,导致在某些环境下,会一直报Connection time-out,Code为28。但在另外一些环境下,则可以正常请求并响应。
为了解决上面的疑问,分别进行了以下调试排查。
PHP层的调试
通过使用curl_setopt($handle, CURLINFO_HEADER_OUT, true);和curl_getinfo()等函数可获取curl调用的一些相关信息,以便进一步定位问题,其中发现curl_getinfo()获取的内容如下:
Array
(
[url] => http://my.api.com/test
[content_type] =>
[http_code] => 0
[header_size] => 0
[request_size] => 0
[filetime] => -1
[ssl_verify_result] => 0
[redirect_count] => 0
[total_time] => 0
[namelookup_time] => 1.4E-5
[connect_time] => 0
[pretransfer_time] => 0
[size_upload] => 0
[size_download] => 0
[speed_download] => 0
[speed_upload] => 0
[download_content_length] => 0
[upload_content_length] => 0
[starttransfer_time] => 0
[redirect_time] => 0
)
发现其中的connect_time一直为0,也就是根本没有发起请求,所以一直失败,从而报超时。
系统环境及CURL扩展排查
但较为奇怪的是,在另外一个系统环境下,非法超时设置可以得到正常响应。对比这两个系统环境下CURL的配置:
环境一:非法超时设置请求失败的CURL配置
环境二:同样是非法超时设置但可以成功请求的CURL配置
发现,虽然版本一样,但各选项开关配置不同,而且使用的协议也不一样。
STRACE与底层排查
继续使用strace进一步调试,发现:
在非法超时设置请求失败的CURL配置环境下:
gettimeofday({1403098526, 14335}, NULL) = 0
rt_sigaction(SIGALRM, NULL, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGALRM, {0xc36410, [], 0}, NULL, 8) = 0
alarm(1403098525) = 0
rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
time(NULL) = 1403098526
rt_sigaction(SIGALRM, {SIG_DFL, [], 0}, NULL, 8) = 0
alarm(0) = 1403098525
环境一:非法下超时请求strace
rt_sigaction(SIGALRM, NULL, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGALRM, {0xc36410, [], 0}, NULL, 8) = 0
alarm(201) = 0
rt_sigaction(SIGALRM, {SIG_DFL, [], 0}, NULL, 8) = 0
alarm(0) = 201
gettimeofday({1403098701, 975556}, NULL) = 0
gettimeofday({1403098701, 975572}, NULL) = 0
gettimeofday({1403098701, 975587}, NULL) = 0
send(3, "POST /FrontApi.php?service=ApiDa"..., 284, MSG_NOSIGNAL) = 284
gettimeofday({1403098701, 975679}, NULL) = 0
gettimeofday({1403098701, 975695}, NULL) = 0
poll([{fd=3, events=POLLIN}], 1, 1000) = 1 ([{fd=3, revents=POLLIN}])
poll([{fd=3, events=POLLIN}], 1, 0) = 1 ([{fd=3, revents=POLLIN}])
recv(3, "HTTP/1.1 200 OK\r\nServer: nginx/1"..., 16384, MSG_NOSIGNAL) = 242
gettimeofday({1403098701, 996962}, NULL) = 0
环境一:正常下请求strace
对比正常与非法的strace,发现在非法超时设置下果然没有调用 send发起请求。而是调用 了sigprocmask()对进程进行了阻塞。
百度了一下,查看了此函数的说明。
http://baike.baidu.com/link?url=O2jI62g-RwRWOOS53W_m-yQb1vuXsvWQPmvnCzeI0Nw1-g7xe4qSJOWsq0bSnIND2wXBxI1dW8-qYl2-jesNNa
用于改变进程的当前阻塞信号集.
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oldset);
SIG_BLOCK |
该进程新的信号屏蔽字是其当前信号屏蔽字和set指向信号集的并集。set包含了我 们希望阻塞的附加信号。 |
在同样是非法超时设置但可以成功请求的CURL配置环境下:
rt_sigaction(SIGALRM, NULL, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGALRM, {0x2b15e0c642a0, [], SA_RESTORER, 0x34d5a302d0}, NULL, 8) = 0
alarm(201) = 0
rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP) = -1 EAFNOSUPPORT (Address family not supported by protocol)
open("/etc/hosts", O_RDONLY) = 6
fcntl(6, F_GETFD) = 0
fcntl(6, F_SETFD, FD_CLOEXEC) = 0
fstat(6, {st_mode=S_IFREG|0644, st_size=8761, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x2b15e0eb4000
read(6, "#Do not remove the following lin"..., 4096) = 4096
read(6, "7\n192.168.42.146 B2C_DEV_146.cli"..., 4096) = 4096
close(6) = 0
munmap(0x2b15e0eb4000, 4096) = 0
alarm(0) = 201
rt_sigaction(SIGALRM, {SIG_DFL, [], SA_RESTORER, 0x34d5a302d0}, NULL, 8) = 0
clock_gettime(CLOCK_MONOTONIC, {15318582, 771728835}) = 0
clock_gettime(CLOCK_MONOTONIC, {15318582, 771829835}) = 0
socket(PF_INET, SOCK_STREAM, IPPROTO_TCP) = 6
fcntl(6, F_GETFL) = 0x2 (flags O_RDWR)
fcntl(6, F_SETFL, O_RDWR|O_NONBLOCK) = 0
connect(6, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("192.168.42.1")}, 16) = -1 EINPROGRESS (Operation now in progress)
clock_gettime(CLOCK_MONOTONIC, {15318582, 772324835}) = 0
clock_gettime(CLOCK_MONOTONIC, {15318582, 772380835}) = 0
poll([{fd=6, events=POLLOUT}], 1, 1000) = 1 ([{fd=6, revents=POLLOUT}])
clock_gettime(CLOCK_MONOTONIC, {15318582, 772488835}) = 0
getsockopt(6, SOL_SOCKET, SO_ERROR, [5587752452096], [4]) = 0
getpeername(6, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("192.168.42.1")}, [16196877055437045776]) = 0
getsockname(6, {sa_family=AF_INET, sin_port=htons(51687), sin_addr=inet_addr("192.168.42.1")}, [16196877055437045776]) = 0
clock_gettime(CLOCK_MONOTONIC, {15318582, 772750835}) = 0
clock_gettime(CLOCK_MONOTONIC, {15318582, 772838835}) = 0
clock_gettime(CLOCK_MONOTONIC, {15318582, 772908835}) = 0
clock_gettime(CLOCK_MONOTONIC, {15318582, 772999835}) = 0
clock_gettime(CLOCK_MONOTONIC, {15318582, 773058835}) = 0
sendto(6, "POST /test?service=Mconf"..., 283, MSG_NOSIGNAL, NULL, 0) = 283
clock_gettime(CLOCK_MONOTONIC, {15318582, 773350835}) = 0
poll([{fd=6, events=POLLIN|POLLPRI}], 1, 0) = 0 (Timeout)
poll([{fd=6, events=POLLIN|POLLPRI}], 1, 0) = 0 (Timeout)
clock_gettime(CLOCK_MONOTONIC, {15318582, 773501835}) = 0
clock_gettime(CLOCK_MONOTONIC, {15318582, 774095835}) = 0
clock_gettime(CLOCK_MONOTONIC, {15318582, 774170835}) = 0
poll([{fd=6, events=POLLIN|POLLPRI}], 1, 1000) = 1 ([{fd=6, revents=POLLIN}])
poll([{fd=6, events=POLLIN|POLLPRI}], 1, 0) = 1 ([{fd=6, revents=POLLIN}])
recvfrom(6, "HTTP/1.1 200 OK\r\nServer: nginx/1"..., 16384, 0, NULL, NULL) = 242
环境二:非法下超时请求strace
环境二:正常下请求strace
发现,无论正常还是非法的超时设置,环境二下CURL扩展底层都会使用socket进行通信(TCP?),从而保证了请求过程。
关于send,recv,sendto,recvfrom之间的区别,可查看:
http://blog.csdn.net/liangkaiyang/article/details/5931901
总结:
1、对于扩展、底层和第三方,在使用前须明确各调用的原理,及各参数的说明。
2、当遇到问题时,可以通过各种工具进行调试、快速定位问题。
最后,开发过程中,请相信:修复一个BUG,会带来另外一个BUG。而且,你可能永远不知一行代码值多少钱,直到线上发现了问题并导致了损失。