私の友人が問題を抱えて私に来ました。接続のサーバー側で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でのブロックを回避するためにスレッドを使用する必要があるか、ソケット自体を覗いて切断をチェックする必要があるのは奇妙に思えました。これはバグのようです。または、機能が不足している可能性があります。;)