连接PHP套接字时出现奇怪的超时

时间:2012-07-24 19:46:47

标签: php linux apache sockets networking

系统

我为一个(商业)客户端编写了一个基于PHP和Apache的简单消息分发系统。数据流的简化版本如下所示:通过HTTP将JSON消息发送到在Apache服务器上运行的PHP脚本,该服务器对其进行一些处理,然后再次使用HTTP将其分发给多个客户端,大约。 10-20。这些消息以不同的间隔发生,每秒最多5条消息,但有时不常见为每分钟一条消息。

使用的服务器是一台功能强大的Ubuntu机器,内存为8gB,网卡位于连接良好的数据仓库中。

由于客户端对响应时间有非常严格的要求(对于期望回复的消息,响应时间必须低于200ms),我已经实现了自己的HttpRequest类,以允许对各种超时和执行进行非常精细的控制持续时间。服务器使用此类将消息转发给其客户端。有趣的部分看起来像这样:

class HttpRequest {
    private $host;
    private $port;
    private $path;

    private $connection = null;
    private $headers = array();

    [...]

    const CONTENT_TYPE = 'application/json';
    const ENCODING = 'utf-8';
    const TIMEOUT = 0.2;

    const MAX_RESPONSE_SIZE = 0xFFFF; // 64 kB

    public function __construct($url, $callback = null) {
        if (empty($url)) {
            throw new HttpException("url cannot be empty");
        }

        $url_parts = parse_url($url);

        $this->host = $url_parts['host'];
        $this->port = (empty($url_parts['port']) ? 80 : $url_parts['port']);
        $this->path = $url_parts['path'];

        $this->headers['Host'] = $this->host;
        $this->headers['Connection'] = 'close';
        $this->headers['Cache-Control'] = 'no-cache';
    }

    public function __destruct() {
        try {
            $this->disconnect();
        } catch (Exception $e) {
        }
    }

    private function connect() {
        if ($this->connection != null) {
            // already connected, simply return
            return;
        }

        $errno = '';
        $errstr = '';
        $_timeout = self::TIMEOUT;

        $this->connection = @fsockopen($this->host, $this->port, $errno, $errstr, $_timeout);

        if ($this->connection === false) {
            throw new HttpException("error during connect: $errstr", ($errno == SOCKET_ETIMEDOUT));
        }

        stream_set_timeout($this->connection, (int)(floor($_timeout)), (int)(($_timeout - floor($_timeout)) * 1000000));

        [variable assignments]
    }

    private function disconnect() {
        if ($this->connection == null) {
            // already disconnected, simply return
            return;
        }

        @fclose($this->connection);
        $this->connection = null;
    }

    public function post($data, $fetch_response = true, $path = null) {
        if (empty($data)) {
            throw new HttpException("no data given", false);
        }

        $contenttype = 'application/x-www-form-urlencoded';

        if (is_string($data)) {
            $data = urlencode($data);
        } else if (is_array($data)) {
            $data = http_build_query($data);
        } else if ($data instanceof Message) {
            $data = json_encode($data);

            $contenttype = 'application/json';
        }

        if (!is_string($data)) {
            throw new HttpException("wrong datatype", false);
        }

        $encoding = mb_detect_encoding($data);

        if ($encoding != self::ENCODING) {
            $data = mb_convert_encoding($data, self::ENCODING, $encoding);
        }

        if (empty($path)) {
            $path = $this->path;
        }

        // set header values
        $this->headers['Content-Type'] = $contenttype . '; charset=' . self::ENCODING;
        $this->headers['Content-Length'] = mb_strlen($data);

        // build request
        $request = "POST $path HTTP/1.1\r\n";

        foreach ($this->headers as $header => $value) {
            $request .= "$header: $value\r\n";
        }

        $request .= "\r\n$data";

        // and send it
        $this->sendRequest($request, $fetch_response);

        if ($fetch_response) {
            // fetch and parse response
            $resp = $this->receiveResponse();

            return $resp;
        }
    }

    public function get($path = null) {
        [build and execute http query]
    }

    private function sendRequest($request, $keep_connected = true) {
        // connect the socket
        $this->connect();

        [timer1]

        // write out data
        $result = @fwrite($this->connection, $request);

        [timer2]

        if ($result === false || $result != mb_strlen($request)) {
            $this->disconnect();
            throw new HttpException("write to socket failed", false);
        }

        if (!$keep_connected) {
            $this->disconnect();
        }
    }

    private function receiveResponse() {
        [fetch response using stream_select() and fgets() while strictly observing the timeout]
    }

    private function parseLine($msg) {
        [process http response, used in receiveResponse()]
    }
}

这个类通常使用如下:

$request = new HttpRequest($url);
$request->post($data);

问题

每隔一段时间,一些消息会在超过五秒的超时时间内到期。仅从代码中就不可能,因为每次调用与IO相关的函数都应该在这段时间之前很久就会超时。

分析语句(在代码中表示为[timer1]和[timer2])表明对HttpRequest-> connect()的调用是发生此延迟的地方。我最好的猜测是,fsockopen()由于某种原因忽略了传递给它的超时。

有趣的是,每当超时超过给定限制时,它们通常只超过5秒,这让我相信在网络代码的较低层中某处存在5秒延迟(例如,套接字资源耗尽) 。可能会排除与DNS相关的问题,因为使用IP地址指定主机时也会发生此行为。

当将消息分发给许多客户端时,通常会出现问题,这意味着许多请求将快速连续发送,但我在向一个客户端发送单个消息时也注意到了这一点。当对Apache有多个同时请求时,通常会发生这种情况,但不一定会发生。

有没有人遇到过类似的问题?这些互联网并不是非常详尽,也没有通过PHP源代码。关于如何处理这个问题的任何指示?

0 个答案:

没有答案