PHP5.4 pfsocketopen函数判断sock是否存活的bug(由memcached引起)

来源:互联网 发布:数据库实用教程孙飞显 编辑:程序博客网 时间:2024/06/02 02:13

之前遇到的一个业务代码非常奇葩的bug,代码中使用pfsocketopen作为http请求,偶尔会不能读取返回结果。

经过排查,发现PHP5.4版本下判断socket是否存活存在一个bug。

解决方案:

1)在这个情况下,可以不使用长链接,改为使用短连接

2)由于PHP5.4已经不再维护了,可以升级PHP版本到5.6

PS:可以看看我在github上的讨论(https://github.com/php-memcached-dev/php-memcached/issues/313)

去除业务逻辑后,代码如下:

<?php$num = 1;while ($num < 5) {    echo ">>>start {$num}", PHP_EOL;    $mc = new Memcached();    $mc->addServer('host.com', 11511);    $data = $mc->get("freg127.0.0.1");    $socket = pfsockopen("host2.com", 80, $errno, $errstr, 30);    echo $socket, PHP_EOL;    stream_set_write_buffer($socket, 0);    socket_set_timeout($socket, 30);    $request = get_post_data();    $len = fputs($socket, $request, strlen($request));    echo "send {$len} data", PHP_EOL;    $read = fgets($socket);    if (preg_match("/^HTTP/", $read) === 0) {        echo "Illegal HTTP server.{$read}", PHP_EOL;        exit;    }    $content_length = 0;    while (!feof($socket)) {        $read = fgets($socket);        if (preg_match("/^Content-Length: (\d+)/", $read, $matches) === 1) {            $content_length = intval($matches[1]);        }        if ($read == "\r\n") {            break;        }    }    $read_len = fread($socket, $content_length);    echo "read content {$content_length} data", PHP_EOL;    echo "sleep 120", PHP_EOL;    sleep(120);    ++$num;}function get_post_data(){    return <<<DATAPOST /users/api.php HTTP/1.1Host: host2.com:80User-Agent: PHPRPC Client 3.0 for PHPConnection: Keep-AliveCache-Control: no-cacheAccept: */*Accept-Encoding: gzip,deflateContent-Type: application/x-www-form-urlencoded; charset=utf-8Content-Length: 586_omit rpc data_DATA;}

代码在cli环境下执行,期望的输出:

>>>start 1Resource id #4send 850 dataread content 84 datasleep 120>>>start 2Resource id #4send 850 dataread content 84 datasleep 120
然而实际的输出为:

>>>start 1Resource id #4send 850 dataread content 84 datasleep 120>>>start 2Resource id #4send 850 dataIllegal HTTP server.
第二次调用pfsocketopen的时候,PHP判断为socket是存活的,因此没有进行重新连接。通过对PHP5.4的源码进行分析,发现这个小小的bug:

main/streams/stream.c:1357

PHPAPI int _php_stream_set_option(php_stream *stream, int option, int value, void *ptrparam TSRMLS_DC){int ret = PHP_STREAM_OPTION_RETURN_NOTIMPL;if (stream->ops->set_option) {ret = stream->ops->set_option(stream, option, value, ptrparam TSRMLS_CC);                //here will return PHP_STREAM_OPTION_RETURN_OK}
and then
main/streams/xp_socke.c:293

if (sock->socket == -1) {    alive = 0;} else if (php_pollfd_for(sock->socket, PHP_POLLREADABLE|POLLPRI, &tv) > 0) {    if (0 >= recv(sock->socket, &buf, sizeof(buf), MSG_PEEK) && php_socket_errno() != EWOULDBLOCK) {        alive = 0;    }}

当上一次php_socket_errno()调用返回EWOULDBLOCK的时候,即使recv调用返回0,仍然会认为socket是存活的,来看看PHP5.6版本的处理:

if (sock->socket == -1) {    alive = 0;} else if (php_pollfd_for(sock->socket, PHP_POLLREADABLE|POLLPRI, &tv) > 0) {#ifdef PHP_WIN32    int ret;#else    ssize_t ret;#endif    int err;    ret = recv(sock->socket, &buf, sizeof(buf), MSG_PEEK);    err = php_socket_errno();    if (0 == ret || /* the counterpart did properly shutdown*/        (0 > ret && err != EWOULDBLOCK && err != EAGAIN)) { /* there was an unrecoverable error */        alive = 0;    }}






0 0
原创粉丝点击