複数の TCP 接続からデータを受信するサーバー アプリケーションを作成しています。最大 200 接続まで拡張できるようにしたいと考えています。このために私が書いた最初のアルゴリズムは次のとおりです。
while (keepListening)
{
foreach (TcpClient client in clientList)
{
if (!client.Connected)
{
client.Close();
deleteList.Add(client);
continue;
}
int dataAvail = client.Available;
if (dataAvail > 0)
{
NetworkStream netstr = client.GetStream();
byte[] arry = new byte[dataAvail];
netstr.Read(arry, 0, dataAvail);
MemoryStream ms = new MemoryStream(arry);
try
{
CommData data = dataDeserializer.Deserialize(ms) as CommData;
beaconTable.BeaconReceived(data);
}
catch
{ }
}
}
foreach (TcpClient clientToDelete in deleteList)
clientList.Remove(clientToDelete);
deleteList.Clear();
while (connectionListener.Pending())
clientList.Add(connectionListener.AcceptTcpClient());
Thread.Sleep(20);
}
これは問題なく動作しますが、Thread.Sleep を追加してループを遅くする必要があることがわかりました。そうしないと、接続の数に関係なく、コア全体が占有されます。Thread.Sleep は一般的に悪いと考えられているとアドバイスされたので、いくつかの代替手段を探しました。これと同様の質問で、WaitHandles を使用して BeginRead と BeginAccept を使用するように勧められたので、それを使用して同じことを行うアルゴリズムを作成し、これを思いつきました。
while (keepListening)
{
int waitResult = WaitHandle.WaitAny(waitList.Select(t => t.AsyncHandle.AsyncWaitHandle).ToArray(), connectionTimeout);
if (waitResult == WaitHandle.WaitTimeout)
continue;
WaitObject waitObject = waitList[waitResult];
Type waitType = waitObject.WaitingObject.GetType();
if (waitType == typeof(TcpListener))
{
TcpClient newClient = (waitObject.WaitingObject as TcpListener).EndAcceptTcpClient(waitObject.AsyncHandle);
waitList.Remove(waitObject);
byte[] newBuffer = new byte[bufferSize];
waitList.Add(new WaitObject(newClient.GetStream().BeginRead(newBuffer, 0, bufferSize, null, null), newClient, newBuffer));
if (waitList.Count < 64)
waitList.Add(new WaitObject(connectionListener.BeginAcceptTcpClient(null, null), connectionListener, null));
else
{
connectionListener.Stop();
listening = false;
}
}
else if (waitType == typeof(TcpClient))
{
TcpClient currentClient = waitObject.WaitingObject as TcpClient;
int bytesRead = currentClient.GetStream().EndRead(waitObject.AsyncHandle);
if (bytesRead > 0)
{
MemoryStream ms = new MemoryStream(waitObject.DataBuffer, 0, bytesRead);
try
{
CommData data = dataDeserializer.Deserialize(ms) as CommData;
beaconTable.BeaconReceived(data);
}
catch
{ }
byte[] newBuffer = new byte[bufferSize];
waitList.Add(new WaitObject(currentClient.GetStream().BeginRead(newBuffer, 0, bufferSize, null, null), currentClient, newBuffer));
}
else
{
currentClient.Close();
}
waitList.Remove(waitObject);
if (!listening && waitList.Count < 64)
{
listening = true;
connectionListener.Start();
waitList.Add(new WaitObject(connectionListener.BeginAcceptTcpClient(null, null), connectionListener, null));
}
}
else
throw new ApplicationException("An unknown type ended up in the wait list somehow: " + waitType.ToString());
}
これも、64 クライアントに到達するまで問題なく動作します。WaitAny が受け入れる WaitHandles の最大数であるため、64 を超えるクライアントを受け入れないように制限を書きました。この制限を回避する良い方法が見当たらないので、基本的にこのように 64 を超える接続を維持することはできません。Thread.Sleep アルゴリズムは、100 以上の接続で問題なく動作します。
また、データを受信した後に受信データの正確なサイズで割り当てるのではなく、任意のサイズの受信配列を事前に割り当てなければならないこともあまり好きではありません。とにかく WaitAny にタイムアウトを与える必要があります。そうしないと、接続がない場合にアプリケーションを閉じたときに、これを実行しているスレッドが参加できなくなります。そして、それは一般的により長く、より複雑です。
では、なぜ Thread.Sleep が悪い解決策なのでしょうか? 少なくとも WaitAny バージョンで 64 を超える接続を処理できるようにする方法はありますか? 私が見ていない、これを処理するまったく異なる方法はありますか?