我正在编写一个服务器应用程序,它将从多个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个客户端。我写了一个限制,不接受超过64个客户端,因为这是WaitAny将接受的最大WaitHandles数。我无法看到任何有关这个限制的好方法,所以我基本上不能维持超过64个这样的连接。 Thread.Sleep算法可以正常运行100多个连接。
我也不太喜欢预先分配任意大小的接收数组,而不是在收到数据后以接收数据的确切大小分配它。而且我必须给WaitAny一个超时,否则当我关闭应用程序时,如果没有连接,它会阻止线程从Join-ing运行。它通常更长,更复杂。
那么为什么Thread.Sleep会变得更糟?有什么方法我至少可以让WaitAny版本处理超过64个连接?是否有一些完全不同的处理方法,我没有看到?
答案 0 :(得分:0)
Jim提出了使用Async回调而不是WaitHandles的明显建议。我最初认为这太复杂了,但是一旦我意识到我可以在状态对象中传递对调用TcpListener或TcpClient的引用,它就会变得更加简单。通过这一点以及线程安全的一些变化,它已经准备好了。它可以通过100多个连接进行良好测试,并且没有任何问题可以完全退出。不过,我还是想要另外一种方法来预先分配数据缓冲区。以下是尝试类似内容的任何人的代码:
public class NetworkReceiver : IDisposable
{
private IReceiver beaconTable;
private XmlSerializer dataDeserializer;
private HashSet<TcpClient> ClientTable;
private TcpListener connectionListener;
private int bufferSize = 1000;
public NetworkReceiver(IReceiver inputTable)
{
beaconTable = inputTable;
dataDeserializer = new XmlSerializer(typeof(CommData));
ClientTable = new HashSet<TcpClient>();
connectionListener = new TcpListener(IPAddress.Any, SharedData.connectionPort);
connectionListener.Start();
connectionListener.BeginAcceptTcpClient(ListenerCallback, connectionListener);
}
private void ListenerCallback(IAsyncResult callbackResult)
{
TcpListener listener = callbackResult.AsyncState as TcpListener;
TcpClient client;
try
{
client = listener.EndAcceptTcpClient(callbackResult);
lock (ClientTable)
ClientTable.Add(client);
ClientObject clientObj = new ClientObject() { AsyncClient = client, Buffer = new byte[bufferSize] };
client.GetStream().BeginRead(clientObj.Buffer, 0, bufferSize, ClientReadCallback, clientObj);
listener.BeginAcceptTcpClient(ListenerCallback, listener);
}
catch (ObjectDisposedException)
{
return;
}
}
private void ClientReadCallback(IAsyncResult callbackResult)
{
ClientObject clientObj = callbackResult.AsyncState as ClientObject;
TcpClient client = clientObj.AsyncClient;
if (!client.Connected)
return;
try
{
int bytesRead = client.GetStream().EndRead(callbackResult);
if (bytesRead > 0)
{
MemoryStream ms = new MemoryStream(clientObj.Buffer, 0, bytesRead);
try
{
CommData data;
lock (dataDeserializer)
data = dataDeserializer.Deserialize(ms) as CommData;
lock (beaconTable)
beaconTable.BeaconReceived(data);
}
catch
{ }
client.GetStream().BeginRead(clientObj.Buffer, 0, bufferSize, ClientReadCallback, clientObj);
}
else
{
client.Close();
lock (ClientTable)
ClientTable.Remove(client);
}
}
catch (Exception ex)
{
if (ex.GetType() == typeof(ObjectDisposedException) || ex.GetType() == typeof(InvalidOperationException))
return;
else
throw;
}
}
class ClientObject
{
public TcpClient AsyncClient;
public byte[] Buffer;
}
public void Dispose()
{
connectionListener.Stop();
foreach (TcpClient client in ClientTable)
client.Close();
}
}