9

このコードの欠陥を指摘できる人はいますか? TcpClient で HTML を取得しています。IIS サーバーとの通信中に NetworkStream.Read() が終了しないようです。代わりに Fiddler プロキシを使用すると問題なく動作しますが、ターゲット サーバーと直接通信すると、「リモート サーバーが接続を閉じました」などのエラーで接続例外が発生するまで、.read() ループは終了しません。

internal TcpClient Client { get; set; }

/// bunch of other code here...

try
{

NetworkStream ns = Client.GetStream();
StreamWriter sw = new StreamWriter(ns);

sw.Write(request);
sw.Flush();

byte[] buffer = new byte[1024];

int read=0;

try
{
    while ((read = ns.Read(buffer, 0, buffer.Length)) > 0)
    {
        response.AppendFormat("{0}", Encoding.ASCII.GetString(buffer, 0, read));
    }
}
catch //(SocketException se)
{

}
finally
{
    Close();
}

アップデート

デバッガーでは、応答全体がすぐに送信され、StringBuilder (応答) に追加されていることがわかります。サーバーが応答の送信を完了したとき、または私のコードがそれを検出していないときに、接続が閉じられていないように見えます。

結論 ここで述べたように、トランザクションがいつ完了するかを判断するには、プロトコル (HTTP の場合は Content-Length ヘッダー) の機能を利用するのが最善です。ただし、すべてのページに content-length が設定されているわけではないことがわかりました。だから、私は今ハイブリッドソリューションを使用しています:

  1. すべてのトランザクションの場合、リクエストのConnectionヘッダーを「close」に設定して、サーバーがソケットを開いたままにしないようにします。これにより、サーバーがリクエストへの応答中に接続を閉じる可能性が向上します。

  2. が設定されている場合Content-Lengthは、それを使用してリクエストがいつ完了するかを判断します。

  3. それ以外の場合は、NetworkStream の RequestTimeout プロパティを 1 秒などの大きな、しかし妥当な値に設定します。NetworkStream.Read()次に、 a) タイムアウトが発生するか、b) 要求したよりも少ないバイト数を読み取るまでループします。

すばらしい詳細な回答をくださった皆さんに感謝します。

4

5 に答える 5

10

NetworkStream.Readのドキュメントが意味することとは反対に、 a から取得したストリームは、利用可能なデータがTcpClientない場合に読み取ったバイト数に対して単に 0 を返すのではなく、ブロックします

のドキュメントをTcpClient見ると、次の行が表示されます。

TcpClient クラスは、同期ブロッキング モードでネットワーク経由でストリーム データを接続、送信、および受信するための単純なメソッドを提供します。

私の推測では、あなたのRead呼び出しがブロックされているのは、サーバーがデータを送り返さないことを決定したためだと思います。これはおそらく、最初のリクエストが適切に処理されていないためです。

私の最初の提案はStreamWriter、考えられる原因 (つまり、バッファリング/エンコードのニュアンス) を排除し、NetworkStream.Writeメソッドを使用してストリームに直接書き込むことです。それが機能する場合は、 に正しいパラメーターを使用していることを確認してくださいStreamWriter

Read2 つ目の提案は、呼び出しの結果に依存してループを中断しないことです。このNetworkStreamクラスには、DataAvailableこのために設計されたプロパティがあります。受信ループを記述する正しい方法は次のとおりです。

NetworkStream netStream = client.GetStream();
int read = 0;
byte[] buffer = new byte[1024];
StringBuilder response = new StringBuilder();
do
{
    read = netStream.Read(buffer, 0, buffer.Length);
    response.Append(Encoding.ASCII.GetString(buffer, 0, read));
}
while (netStream.DataAvailable);
于 2010-02-03T17:37:56.333 に答える
3

二重の CRLF に到達するまで応答を読みます。これで、応答ヘッダーができました。ヘッダーを解析して、応答に残っているバイト数である Content-Length ヘッダーを読み取ります。

Content-Length ヘッダーをキャッチできる正規表現を次に示します。

David の更新された正規表現

Content-Length: (?<1>\d+)\r\n

コンテンツの長さ

ノート

サーバーがこのヘッダーを適切に設定しない場合、私はそれを使用しません。

于 2010-02-03T17:35:30.927 に答える
2

これが役立つかどうかはわかりませんが、HTTP 1.1 では、サーバーへの基になる接続が閉じられない可能性があるため、ストリームも閉じられないのでしょうか? 接続を再利用して新しいリクエストを送信できるという考えです。コンテンツの長さを使用する必要があると思います。または、代わりに WebClient または WebRequest クラスを使用してください。

于 2010-02-03T19:08:29.687 に答える
1

私は間違っているかもしれませんが、あなたの呼び出しWriteは(ボンネットの下で)ストリームにns(経由でStreamWriter)書き込んでいるようです。後で、同じストリーム ( ns) から読み取ります。なぜこんなことをしているのかよくわかりません。

Seekとにかく、読み始めたい場所に移動するには、ストリームで使用する必要があるかもしれません。書いた後は最後まで探していると思います。しかし、私が言ったように、これが有用な答えであるかどうかはよくわかりません!

于 2010-02-03T17:36:55.587 に答える
0

2つの提案...

  1. NetworkStream の DataAvailable プロパティを使用してみましたか? ストリームから読み取るデータがある場合は true を返す必要があります。

    while (ns.DataAvailable)
    {
     //Do stuff here
    }
  1. 別のオプションは、ReadTimeOut を低い値に変更して、長時間ブロックしないようにすることです。次のように実行できます。

    ns.ReadTimeOut=100;
于 2010-02-03T18:01:26.343 に答える