2

私の友人が問題を抱えて私に来ました。接続のサーバー側でNetworkStreamクラスを使用しているときに、クライアントが切断すると、NetworkStreamはそれを検出できません。

分解すると、彼のC#コードは次のようになりました。

List<TcpClient> connections = new List<TcpClient>();
TcpListener listener = new TcpListener(7777);
listener.Start();

while(true)
{
    if (listener.Pending())
    {
        connections.Add(listener.AcceptTcpClient());
    }
    TcpClient deadClient = null;
    foreach (TcpClient client in connections)
    {
        if (!client.Connected)
        {
            deadClient = client;
            break;
        }
        NetworkStream ns = client.GetStream();
        if (ns.DataAvailable)
        {
            BinaryFormatter bf = new BinaryFormatter();
            object o = bf.Deserialize(ns);
            ReceiveMyObject(o);
        }
    }
    if (deadClient != null)
    {
        deadClient.Close();
        connections.Remove(deadClient);
    }
    Thread.Sleep(0);
}

クライアントが正常に接続でき、サーバーが送信されたデータを読み取ることができるという点で、コードは機能します。ただし、リモートクライアントがtcpClient.Close()を呼び出す場合、サーバーは切断を検出しません。client.Connectedはtrueのままであり、ns.DataAvailableはfalseです。

Stack Overflowを検索すると答えが得られました。Socket.Receiveが呼び出されていないため、ソケットは切断を検出していません。けっこうだ。これを回避できます。

foreach (TcpClient client in connections)
{
    client.ReceiveTimeout = 0;
    if (client.Client.Poll(0, SelectMode.SelectRead))
    {
        int bytesPeeked = 0;
        byte[] buffer = new byte[1];
        bytesPeeked = client.Client.Receive(buffer, SocketFlags.Peek);
        if (bytesPeeked == 0)
        {
            deadClient = client;
            break;
        }
        else
        {
            NetworkStream ns = client.GetStream();
            if (ns.DataAvailable)
            {
                BinaryFormatter bf = new BinaryFormatter();
                object o = bf.Deserialize(ns);
                ReceiveMyObject(o);
            }
        }
    }
}

(簡潔にするために、例外処理コードは省略しました。)

このコードは機能しますが、私はこのソリューションを「エレガント」とは呼びません。私が知っている問題に対する他の洗練された解決策は、TcpClientごとにスレッドを生成し、BinaryFormatter.Deserialize(nee NetworkStream.Read)呼び出しをブロックに許可することです。これにより、切断が正しく検出されます。ただし、これには、クライアントごとにスレッドを作成および維持するオーバーヘッドがあります。

元のコードの明快さを維持する秘密の素晴らしい答えが欠けているように感じますが、非同期読み取りを実行するために追加のスレッドを使用することは避けてください。ただし、おそらく、NetworkStreamクラスはこの種の使用法のために設計されたことはありません。誰かが光を当てることができますか?

更新: .NET FrameworkにNetworkStreamのこの使用(つまり、ポーリングブロッキングの回避)をカバーするソリューションがあるかどうかを確認することに関心があることを明確にしたいだけです-明らかにそれは可能です。NetworkStreamは、機能を提供するサポートクラスに簡単にラップできます。フレームワークでは、NetworkStream.Readでのブロックを回避するためにスレッドを使用する必要があるか、ソケット自体を覗いて切断をチェックする必要があるのは奇妙に思えました。これはバグのようです。または、機能が不足している可能性があります。;)

4

2 に答える 2

2

ええ、でもサイズを取得する前に接続が失われた場合はどうなりますか?つまり、次の行の直前:

// message framing. First, read the #bytes to expect. 

int objectSize = br.ReadInt32(); 

ReadInt32()スレッドを無期限にブロックします。

于 2010-08-14T12:37:10.583 に答える
2

サーバーは、同じ接続を介して複数のオブジェクトが送信されることを期待していますか?その場合、最初のオブジェクトが開始し、次のオブジェクトが終了する場所を示す区切り文字が送信されないため、このコードがどのように機能するかわかりません。

1つのオブジェクトのみが送信され、その後接続が閉じられた場合、元のコードは機能します。

接続がまだアクティブであるかどうかを確認するには、ネットワーク操作を開始する必要があります。私がすることは、ネットワークストリームから直接デシリアライズする代わりに、MemoryStreamにバッファリングすることです。これにより、接続が失われたことを検出できます。また、メッセージフレーミングを使用して、ストリーム上の複数の応答を区切ります。

        MemoryStream ms = new MemoryStream();

        NetworkStream ns = client.GetStream();
        BinaryReader br = new BinaryReader(ns);

        // message framing. First, read the #bytes to expect.
        int objectSize = br.ReadInt32();

        if (objectSize == 0)
              break; // client disconnected

        byte [] buffer = new byte[objectSize];
        int index = 0;

        int read = ns.Read(buffer, index, Math.Min(objectSize, 1024);
        while (read > 0)
        {
             objectSize -= read;
             index += read;
             read = ns.Read(buffer, index, Math.Min(objectSize, 1024);
        }

        if (objectSize > 0)
        {
             // client aborted connection in the middle of stream;
             break;
        } 
        else
        {
            BinaryFormatter bf = new BinaryFormatter();
            using(MemoryStream ms = new MemoryStream(buffer))
            {
                 object o = bf.Deserialize(ns);
                 ReceiveMyObject(o);
            }
        }
于 2009-12-13T15:45:26.267 に答える