如果连接保持活动状态,如何读取php直到流结束

如果连接保持活动状态,如何读取php直到流结束,php,http,Php,Http,上述暂停是因为连接:保持活动状态,并使用连接:关闭 如何在不暂停的情况下执行此操作?这取决于响应,如果响应的传输编码是分块的,则您将一直阅读,直到遇到“最后一块”(\r\n0\r\n) 如果内容编码为gzip,则查看内容长度响应标题,读取大量数据,然后将其充气。如果传输编码也设置为chunked,则必须取消对解码响应的chunk 最简单的方法是构建一个简单的状态机,以便在仍然有响应数据的情况下从套接字读取响应 在读取分块数据时,您应该读取第一个分块长度(以及任何分块扩展),然后读取与分块大小相同

上述暂停是因为
连接:保持活动状态
,并使用
连接:关闭


如何在不暂停的情况下执行此操作?

这取决于响应,如果响应的
传输编码
分块的
,则您将一直阅读,直到遇到“最后一块”(
\r\n0\r\n

如果
内容编码
gzip
,则查看
内容长度
响应标题,读取大量数据,然后将其充气。如果
传输编码
也设置为chunked,则必须取消对解码响应的chunk

最简单的方法是构建一个简单的状态机,以便在仍然有响应数据的情况下从套接字读取响应

在读取分块数据时,您应该读取第一个分块长度(以及任何分块扩展),然后读取与分块大小相同的数据,直到最后一个分块

换句话说:

  • 读取HTTP响应头(读取小块数据,直到遇到
    \r\n\r\n
  • 将响应头解析为数组
  • 如果
    传输编码
    被分块,则逐块读取并解块数据
  • 如果设置了
    内容长度
    标题,则可以从套接字读取那么多数据
  • 如果
    内容编码为gzip,则解压缩读取的数据
一旦执行了上述步骤,您应该已经读取了整个响应,现在可以在同一套接字上发送另一个HTTP请求并重复该过程

另一方面,除非您绝对需要保持活动连接,否则只需在请求中设置
connection:close
,您就可以在(!feof($f))
时安全地读取

目前我没有任何PHP代码用于读取和解析HTTP响应(我只使用cURL),但是如果您想查看实际的代码,请告诉我,我可以做一些工作。我还可以让你参考我编写的一些C代码,它们可以完成上述所有功能

编辑:下面是使用
fsockopen
发出HTTP请求的工作代码,并演示如何读取具有分块编码和gzip压缩可能性的保持活动连接。经过测试,但不受折磨-使用风险自负

$f = fsockopen("www....",80,$x,$y);

fwrite("GET request HTTP/1.1\r\nConnection: keep-alive\r\n\r\n");

while($s = fread($f,1024)){
    ...
}

以下代码对我来说没有任何问题:


有趣的是,如果没有保持活力,这个例子对我来说会停滞不前。
你能添加一个可以复制粘贴并显示错误的示例吗?

顺便说一句,你知道你有报价错误,对吗?@Daedalus我可以验证报价错误不是问题所在。我之所以悬赏,是因为我有一个类似的问题,并且已经浪费了太多时间试图解决它:)抓得好,@Daedalus,我没抓到。不过,我支持rdlowrey,这种错误会有所不同。在这种情况下,解释分块传输编码和压缩内容编码是没有问题的。此外,HTTP/1.1特别指出,如果设置了
传输编码
头,则服务器不应发送
内容长度
头。目前的问题是,为什么循环在列出的代码中的前1024个字节之后停止,并且在keep-alive是所需行为时从不返回。我之所以选择悬赏是因为我理解你答案中的所有内容,但我不知道如何使用
Connection:keep alive
…我明白了,如果它停止运行,那么更有可能出现PHP问题,他至少应该能够在
fread
块之前读取整个响应,因为没有更多可用数据。但是需要注意的是,一旦他读取了整个响应,再次调用
fread
将无限期地阻塞(或者直到套接字超时),因此,对于保持活动的连接,您不能在这样的循环中使用
fread
。@rdlowrey:如果连接保持活动,并且您继续您天真的读取循环,PHP将尝试继续读取,直到连接关闭。如果设置了
Connection:close
,则这是正常的。如果设置了
Connection:keep-alive
,您需要进行额外的处理,以知道何时停止读取。@rdlowrey如承诺的那样,我终于添加了代码。我把它分成了一些函数,但仅此而已,我认为现在没有必要把它变成一个类。希望它能帮助你。这将支持keep-alives,可以在同一个连接上发送多个请求,读取分块响应并解压缩gzip。@JayapalChandran它不仅仅是PHP,它是keep-alive连接发送数据的本质。如果在c/c++中调用
recv
,并且它是一个阻塞接收,那么程序将挂起,因为它正在尝试在套接字上没有更多数据时进行recv。您可以通过使用非阻塞recv或设置超时来绕过它,但这并不理想,因为通过读取标题,您可以知道何时读取了整个响应,而不必调用
recv
。响应是错误的请求。我假设在单词connection之前有一个额外的空格。除此之外,在更正空格和循环显示所有数据后,由于fread尝试不读取任何数据,因此它被挂起。
<?php

/**
 * PHP HTTP request demo
 * Makes HTTP requests using PHP and fsockopen
 * Supports chunked transfer encoding, gzip compression, and keep-alive
 *
 * @author drew010 <http://stackoverflow.com/questions/11125463/if-connection-is-keep-alive-how-to-read-until-end-of-stream-php/11812536#11812536>
 * @date 2012-08-05
 * Public domain
 *
 */

error_reporting(E_ALL);
ini_set('display_errors', 1);

$host = 'www.kernel.org';

$sock = fsockopen($host, 80, $errno, $errstr, 30);

if (!$sock) {
    die("Connection failed.  $errno: $errstr\n");
}

request($sock, $host, 'GET', '/');

$headers = readResponseHeaders($sock, $resp, $msg);
$body    = readResponseBody($sock, $headers);

echo "Response status: $resp - $msg\n\n";

echo '<pre>' . var_export($headers, true) . '</pre>';
echo "\n\n";
echo $body;

// if the connection is keep-alive, you can make another request here
// as demonstrated below

request($sock, $host, 'GET', '/kernel.css');
$headers = readResponseHeaders($sock, $resp, $msg);
$body    = readResponseBody($sock, $headers);

echo "Response status: $resp - $msg\n\n";

echo '<pre>' . var_export($headers, true) . '</pre>';
echo "\n\n";
echo $body;


exit;

function request($sock, $host, $method = 'GET', $uri = '/', $params = null)
{
    $method = strtoupper($method);
    if ($method != 'GET' && $method != 'POST') $method = 'GET';

    $request = "$method $uri HTTP/1.1\r\n"
              ."Host: $host\r\n"
              ."Connection: keep-alive\r\n"
              ."Accept-encoding: gzip, deflate\r\n"
              ."\r\n";

    fwrite($sock, $request);
}

function readResponseHeaders($sock, &$response_code, &$response_status)
{
    $headers = '';
    $read    = 0;

    while (true) {
        $headers .= fread($sock, 1);
        $read    += 1;

        if ($read >= 4 && $headers[$read - 1] == "\n" && substr($headers, -4) == "\r\n\r\n") {
            break;
        }
    }

    $headers = parseHeaders($headers, $resp, $msg);

    $response_code   = $resp;
    $response_status = $msg;

    return $headers;
}

function readResponseBody($sock, array $headers)
{
    $responseIsChunked = (isset($headers['transfer-encoding']) && stripos($headers['transfer-encoding'], 'chunked') !== false);
    $contentLength     = (isset($headers['content-length'])) ? $headers['content-length'] : -1;
    $isGzip            = (isset($headers['content-encoding']) && $headers['content-encoding'] == 'gzip') ? true : false;
    $close             = (isset($headers['connection']) && stripos($headers['connection'], 'close') !== false) ? true : false;

    $body = '';

    if ($contentLength >= 0) {
        $read = 0;
        do {
            $buf = fread($sock, $contentLength - $read);
            $read += strlen($buf);
            $body .= $buf;
        } while ($read < $contentLength);

    } else if ($responseIsChunked) {
        $body = readChunked($sock);
    } else if ($close) {
        while (!feof($sock)) {
            $body .= fgets($sock, 1024);
        }
    }

    if ($isGzip) {
        $body = gzinflate(substr($body, 10));
    }

    return $body;
}

function readChunked($sock)
{   
    $body = '';

    while (true) {
        $data = '';

        do {
            $data .= fread($sock, 1);
        } while (strpos($data, "\r\n") === false);

        if (strpos($data, ' ') !== false) {
            list($chunksize, $chunkext) = explode(' ', $data, 2);
        } else {
            $chunksize = $data;
            $chunkext  = '';
        }

        $chunksize = (int)base_convert($chunksize, 16, 10);

        if ($chunksize === 0) {
            fread($sock, 2); // read trailing "\r\n"
            return $body;
        } else {
            $data    = '';
            $datalen = 0;
            while ($datalen < $chunksize + 2) {
                $data .= fread($sock, $chunksize - $datalen + 2);
                $datalen = strlen($data);
            }

            $body .= substr($data, 0, -2); // -2 to remove the "\r\n" before the next chunk
        }
    } // while (true)
}

function parseHeaders($headers, &$response_code = null, &$response_message = null)
{
    $lines  = explode("\r\n", $headers);
    $return = array();

    $response = array_shift($lines);

    if (func_num_args() > 1) {
        list($proto, $code, $message) = explode(' ', $response, 3);

        $response_code    = $code;

        if (func_num_args() > 2) {
            $response_message = $message;
        }
    }

    foreach($lines as $header) {
        if (trim($header) == '') continue;
        list($name, $value) = explode(':', $header, 2);

        $return[strtolower(trim($name))] = trim($value);
    }

    return $return;
}
<?php
$f = fsockopen("www.google.de",80);
fwrite($f,"GET / HTTP/1.1\r\n Connection: keep-alive\r\n\r\n");

while($s = fread($f,1024)){
    echo "got: $s";
}
echo "finished;";
?>