2012-06-20 38 views
10
$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)){ 
    ... 
} 

Các quầy hàng trên vì Connection: keep-alive và hoạt động với Connection: close.nếu kết nối vẫn hoạt động như thế nào để đọc cho đến khi kết thúc luồng php

Bạn làm điều đó như thế nào mà không bị trì hoãn?

+0

Là một sang một bên, bạn nhận ra bạn có lỗi báo giá, phải không? – Daedalus

+1

@Daedalus Tôi có thể xác minh rằng lỗi báo giá không phải là vấn đề. Tôi đặt tiền thưởng bởi vì tôi có một vấn đề tương tự và đã lãng phí quá nhiều thời gian cố gắng sửa chữa nó :) – rdlowrey

+0

Tốt bắt, @Daedalus, tôi đã không bắt được điều đó. Tôi với rdlowrey mặc dù, loại lỗi sẽ có được khác nhau. –

Trả lời

15

Phụ thuộc vào phản hồi, nếu số transfer-encoding của phản hồi là chunked, thì bạn đọc cho đến khi bạn gặp "đoạn cuối cùng" (\r\n0\r\n).

Nếu content-encodinggzip, thì bạn hãy xem tiêu đề phản hồi content-length và đọc nhiều dữ liệu đó rồi thổi phồng nó. Nếu transfer-encoding cũng được đặt thành chunked, sau đó bạn phải dechunk các phản ứng giải mã.

Điều dễ nhất là xây dựng một máy trạng thái đơn giản để đọc phản hồi từ ổ cắm trong khi vẫn còn dữ liệu cho phản hồi.

Khi đọc dữ liệu chunked, bạn nên đọc chiều dài đoạn đầu tiên (và bất kỳ phần mở rộng chunked) và sau đó đọc dữ liệu nhiều như kích thước đoạn, và làm như vậy cho đến đoạn cuối cùng.

Nói cách khác:

  • đọc các tiêu đề HTTP response (đọc khối dữ liệu nhỏ cho đến khi bạn gặp \r\n\r\n)
  • Phân tích các tiêu đề phản ứng vào một mảng
  • Nếu transfer-encoding được chunked, đọc và dechunk mảnh dữ liệu từng mảnh.
  • Nếu header content-length được thiết lập, bạn có thể đọc dữ liệu mà nhiều từ các ổ cắm
  • Nếu content-encoding là gzip, giải nén các dữ liệu đọc

Một khi bạn đã thực hiện các bước trên, bạn nên đã đọc toàn bộ phản hồi và bây giờ bạn có thể gửi một yêu cầu HTTP khác trên cùng một socket và lặp lại quy trình.

Mặt khác, trừ khi bạn có nhu cầu tuyệt đối về kết nối duy trì sự sống, chỉ cần đặt Connection: close trong yêu cầu và bạn có thể đọc an toàn while (!feof($f)).

Tôi không có bất kỳ mã PHP nào để đọc và phân tích cú pháp phản hồi HTTP tại thời điểm này (tôi chỉ sử dụng cURL) nhưng nếu bạn muốn xem mã thực tế, hãy cho tôi biết và tôi có thể làm việc gì đó. Tôi cũng có thể giới thiệu cho bạn một số mã C# mà tôi đã thực hiện để thực hiện tất cả những điều trên.

EDIT: Đây là mã làm việc sử dụng fsockopen để phát hành yêu cầu HTTP và chứng minh việc đọc kết nối liên tục với khả năng nén mã hóa và nén gzip. Thử nghiệm, nhưng không bị tra tấn - sử dụng có nguy cơ của riêng bạn !!!

<?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; 
} 
+0

Trong trường hợp này, việc giải thích mã hóa chuyển đoạn chunked và mã hóa nội dung nén không phải là vấn đề. Ngoài ra, HTTP/1.1 đặc biệt phác thảo các máy chủ KHÔNG NÊN gửi tiêu đề 'Content-Length' nếu tiêu đề' Transfer-Encoding' được đặt. Vấn đề ở bàn tay là lý do tại sao vòng lặp dừng lại sau 1024 byte đầu tiên trong mã được liệt kê và không bao giờ trả về * khi tiếp tục sống là hành vi mong muốn *. Tôi đặt tiền thưởng bởi vì tôi hiểu tất cả những thứ trong câu trả lời của bạn, nhưng tôi không thể tìm ra cách làm cho nó hoạt động với 'Kết nối: giữ-sống' ... – rdlowrey

+0

Tôi thấy, sau đó nhiều khả năng có một vấn đề PHP nếu sự trì trệ của nó, anh ta có thể đọc ít nhất toàn bộ phản ứng trước các khối 'fread' bởi vì không có sẵn nhiều dữ liệu hơn. Nhưng cần lưu ý rằng một khi anh ta đã đọc toàn bộ câu trả lời, gọi 'fread' một lần nữa sẽ chặn vô thời hạn (hoặc cho đến khi socket hết thời gian), do đó, với kết nối liên tục, bạn không thể sử dụng' fread' trong vòng lặp như thế. – drew010

+1

@rdlowrey: Nếu kết nối được giữ nguyên và bạn tiếp tục vòng lặp đọc ngây thơ của bạn, PHP sẽ cố gắng tiếp tục đọc cho đến khi kết nối được đóng lại. Nếu 'Connection: close' được thiết lập, điều này là OK. Nếu 'Connection: keep-alive' được thiết lập, bạn cần phải thực hiện xử lý bổ sung để biết khi nào nên dừng đọc. – icktoofay

0

Các mã sau đây hoạt động mà không có vấn đề gì đối với tôi:

<?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;"; 
?> 

Điều buồn cười là mà không giữ-sống ví dụ này quầy hàng cho tôi. Bạn có thể thêm một ví dụ có thể chỉ cần sao chép & được dán và hiển thị lỗi của bạn không?

+2

Phản hồi là yêu cầu không hợp lệ. tôi giả sử rằng bạn có thêm không gian trước khi kết nối từ. bên cạnh sau khi chỉnh sửa không gian và sau khi vòng lặp hiển thị tất cả dữ liệu thì nó đã bị đình chỉ gây ra fread đang cố gắng để đọc không có dữ liệu. –

Các vấn đề liên quan