6

私は 2 つのソケット クライアントを並べて実行し、http ストリーミング データを収集しています (Twitter ではなく、似たようなものです)。データはチャンク エンコーディングで送信されます。

クライアントの 1 つは curl (php-curl ではなくコマンドライン) で、http と https の両方が正常に機能します。fsockopenもう 1 つは、 と を使用した独自の PHP スクリプトfgetsです。https では正常に動作しますが、http には特定の問題があります。どのように具体的ですか?ストリームが 60 秒間静かになった場合にのみ発生します。静かな時間が 50 秒しかない場合は、正常に動作します。自分のスクリプトで送受信された curl の http ヘッダーを比較して、すべての違いを取り除きました。PHP ソケット、特にチャンク エンコーディングについて知っておくべきことはすべて知っていると思っていましたが、これには困惑したので、謙虚なパイを食べる時が来ました。

そのため、「--trace - --trace-time」を指定して curl を実行すると、60 秒間の休止期間の後に最初のパケットが通過することがわかります。

05:56:57.025023 <= Recv data, 136 bytes (0x88)
0000: 38 32 0d 0a 7b 22 64 61 74 61 66 65 65 64 22 3a 82..{"datafeed":
0010: 22 64 65 6d 6f 2e 31 64 36 2e 31 6d 2e 72 61 6e "demo.1d6.1m.ran
...
0080: 34 22 7d 5d 7d 0a 0d 0a                         4"}]}...

82 は、チャンクのサイズを表す 16 進数です。\r\n は、チャンク サイズの行の終わりを示します。チャンクは「{」から始まります。

PHP 側では、私のループは次のように始まります。

while(true){
  if(feof($fp)){fclose($fp);return "Remote server has closed\n";}
  $chunk_info=trim(fgets($fp)); //First line is hex digits giving us the length
  $len=hexdec($chunk_info);   //$len includes the \r\n at the end of the chunk (despite what wikipedia says)

https を使用する場合、または 60 秒未満のギャップがある場合、これは正常に機能します。$len は 100 またはチャンク サイズが何であれです。しかし、その 60 秒のギャップの後、$chunk_info には次のように表示されます。

datafeed":"demo.1d6.1m.ran...

したがって、最初の 6 バイトが失われたようです。38 32 0d 0a 7b 22

後続のすべてのチャンクは問題なく、curl が受け取るものとまったく同じです。


バージョンの詳細

curl 7.19.7 (x86_64-pc-linux-gnu) libcurl/7.19.7 OpenSSL/0.9.8k zlib/1.2.3.3 libidn/1.15 プロトコル: tftp ftp telnet dict ldap ldaps http file https ftps 機能: GSS ネゴシエート IDN IPv6 Largefile NTLM SSL libz

PHP 5.3.2-1ubuntu4.18 with Suhosin-Patch (cli) (ビルド: 2012 年 9 月 12 日 19:12:47)

サーバー: Apache/2.2.14 (Ubuntu)

(これまでのところ、ローカルホスト接続でのみテストしました。)


残りのループは次のようになります。

$s='';
$len+=2;    //For the \r\n at the end of the chunk
while(!feof($fp)){
    $s.=fread($fp,$len-strlen($s));
    if(strlen($s)>=$len)break;  //TODO: Can never be >$len, only ==$len??
    }
$s=substr($s,0,-2);
if(!$s)continue;
$d=json_decode($s);
//Do something with $d here
}

(余談: これまでにテストした方法では、コードは 60 秒間の休止期間の前に、このループを 1 回だけ通過しました。)


注: 機能させるための回避策は多数あります。たとえば、https の使用を強制するか、curl-php を使用します。この質問は、何が起こっているのか、60 秒後に何が変化しているのか、そしてそれを止める方法を知りたいからです。また、新しいトラブルシューティングのアイデアを学ぶこともできます。血まみれの知的好奇心と考えてください:-)

4

2 に答える 2

1

正直なところ、コードがそのように動作する理由は正確にはわかりませんが、データの読み取り方法は正しくありません。私はそれを次のように書き直します:

$chunkmeta = fgets($f);
// chunked transfer can have extensions (indicated by semi-colon)
// see: http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html
$chunklen = hexdec(current(explode(';', $chunkmeta, 2)));

// chunk length is non-zero
if ($chunklen) {
  $chunk = '';
  // only read up to chunk length
  while (!feof($f) && $chunklen) {
      if ($buf = fread($f, $chunklen)) {
          $chunklen -= strlen($buf);
          $chunk .= $buf;
      } else {
          // uh oh, something bad happened
          die("Could not read chunk");
      }
  }
  if ($chunklen == 0) {
      // read the trailing CRLF
      fread($f, 2); // read CRLF
      // data is ready
  }
}

アップデート

これについては私の直感に沿ったはずです(ただし、上記のコードはどちらの方法でも問題なく機能するはずです)。設定はデフォルトで60秒であるdefault_socket_timeoutため、以降の読み取りでは。を返す必要がありfalseます。それでも、なぜそれが安全なソケットで機能するのか説明していません;-)

于 2012-12-12T07:09:24.517 に答える
1

バグ修正は次のとおりです。

$chunk_info=trim(fgets($fp)); //First line is hex digits giving us the length
if($chunk_info=='')continue; //Usually means the default 60 second time-out on fgets() was reached.
...

fgets($fp) が何かを返す場合、読み取るチャンクがあります。その何かがゼロの場合、処理する空白のチャンクがあります。しかし、何も返さない場合は、fgets がタイムアウトしたことを意味します。tcp:// のデフォルトのタイムアウトは 60 秒のようです。一方、ssl:// のデフォルトのタイムアウトはより長くなります (申し訳ありませんが、それが何であるかはまだ追跡していません。永久にブロックされる可能性があります)。

読み取るものが何もないときにチャンクを処理しようとすると、すべてが同期しなくなりました。したがって、盗まれた6バイト。

トラブルシューティングのヒント:

  1. コードを散らかします:echo "**".date("Y-m-d H:i:s");print_r(stream_get_meta_data($fp));ob_flush();flush();メタ データには、最後のストリーム アクションがタイムアウトになったときを示すエントリがあります。日付スタンプは必須です。
  2. tcpdump -A -i lo port httpコマンドラインからの相互参照。タイムスタンプを PHP のデバッグ行のタイムスタンプと比較すると、疑わしい動作を見つけることができました。
于 2012-12-12T07:46:48.300 に答える