Насколько я понимаю, TcpListener
будет ставить в очередь соединения, как только вы позвоните Start()
. Каждый раз, когда вы звоните AcceptTcpClient
(или BeginAcceptTcpClient
), он удаляет один элемент из очереди.
Если мы нагрузочно протестируем наше приложение TcpListener
, отправив к нему 1000 подключений за один раз, очередь будет построена гораздо быстрее, чем мы можем очистить, что приведет (в конечном итоге) к тайм-аутам от клиента, поскольку оно не получило ответ, его связь все еще была в очереди. Однако сервер, кажется, не находится под большим давлением, наше приложение не потребляет много процессорного времени, а другие отслеживаемые ресурсы на машине не потеют. Такое ощущение, что сейчас мы работаем недостаточно эффективно.
Мы вызываем BeginAcceptTcpListener
, а затем немедленно передаем поток ThreadPool
для фактического выполнения работы, затем снова вызываем BeginAcceptTcpClient
. Похоже, что выполняемая работа не оказывает какого-либо давления на машину, это всего лишь 3-секундный сон, за которым следует поиск по словарю, а затем запись в 100 байт в поток TcpClient
.
Вот код TcpListener
, который мы используем:
// Thread signal.
private static ManualResetEvent tcpClientConnected = new ManualResetEvent(false);
public void DoBeginAcceptTcpClient(TcpListener listener)
{
// Set the event to nonsignaled state.
tcpClientConnected.Reset();
listener.BeginAcceptTcpClient(
new AsyncCallback(DoAcceptTcpClientCallback),
listener);
// Wait for signal
tcpClientConnected.WaitOne();
}
public void DoAcceptTcpClientCallback(IAsyncResult ar)
{
// Get the listener that handles the client request, and the TcpClient
TcpListener listener = (TcpListener)ar.AsyncState;
TcpClient client = listener.EndAcceptTcpClient(ar);
if (inProduction)
ThreadPool.QueueUserWorkItem(state => HandleTcpRequest(client, serverCertificate)); // With SSL
else
ThreadPool.QueueUserWorkItem(state => HandleTcpRequest(client)); // Without SSL
// Signal the calling thread to continue.
tcpClientConnected.Set();
}
public void Start()
{
currentHandledRequests = 0;
tcpListener = new TcpListener(IPAddress.Any, 10000);
try
{
tcpListener.Start();
while (true)
DoBeginAcceptTcpClient(tcpListener);
}
catch (SocketException)
{
// The TcpListener is shutting down, exit gracefully
CheckBuffer();
return;
}
}
Я предполагаю, что ответ будет связан с использованием Sockets
вместо TcpListener
или, по крайней мере, с использованием TcpListener.AcceptSocket
, но мне было интересно, как мы будем это делать?
Одна из идей, которая у нас возникла, состояла в том, чтобы вызвать AcceptTcpClient
и сразу Enqueue
TcpClient
в один из множества Queue<TcpClient>
объектов. Таким образом, мы можем опрашивать эти очереди в отдельных потоках (по одной очереди на поток), не сталкиваясь с мониторами, которые могут блокировать поток во время ожидания других операций Dequeue
. Каждый поток очереди может затем использовать ThreadPool.QueueUserWorkItem
, чтобы выполнить работу в потоке ThreadPool
, а затем перейти к снятию с очереди следующего TcpClient
в своей очереди. Вы бы порекомендовали этот подход, или наша проблема в том, что мы используем TcpListener
, и быстрое снятие очереди не поможет это исправить?