Я пишу серверное приложение, которое будет получать данные из нескольких 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 обычно считается плохим, поэтому я искал несколько альтернатив. В вопросе, аналогичном этому, мне рекомендовали использовать BeginRead и BeginAccept с использованием WaitHandles, поэтому я написал алгоритм, чтобы сделать то же самое, используя это, и придумал это:
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 клиентов. Я написал ограничение, чтобы не принимать более 64 клиентов, потому что это максимальное количество WaitHandles, которое может принять WaitAny. Я не вижу никакого хорошего способа обойти этот предел, поэтому я в основном не могу поддерживать более 64 соединений, подобных этому. Алгоритм Thread.Sleep отлично работает с более чем 100 соединениями.
Мне также не очень нравится предварительно выделять массив приема произвольного размера, а не выделять его в точном размере полученных данных после получения данных. И я все равно должен дать WaitAny тайм-аут, иначе он не позволит потоку, выполняющему это, присоединиться, когда я закрываю приложение, если нет соединений. И это обычно дольше и сложнее.
Так почему же Thread.Sleep — худшее решение? Есть ли способ, по крайней мере, заставить версию WaitAny обрабатывать более 64 подключений? Есть ли какой-то совершенно другой способ справиться с этим, которого я не вижу?
WaitHandlesсBeginRead. Просто укажитеAsyncCallback. Этот метод будет вызван после завершения чтения. См. пример на stackoverflow.com/q/6023264/56778. Кроме того, проверьте связанные вопросы. - person Jim Mischel   schedule 30.03.2013