Насколько я понимаю, TcpListener
поставит соединения в очередь, после того как Вы звоните Start()
. Каждый раз Вы звоните AcceptTcpClient
(или BeginAcceptTcpClient
), это исключит один объект из очереди от очереди.
Если мы загружаем тест наш TcpListener
приложение путем отправки 1 000 соединений с ним сразу, очередь создает намного быстрее, чем мы можем очистить его, ведя (в конечном счете) к тайм-аутам от клиента, потому что это не получило ответ, потому что его соединение было все еще в очереди. Однако сервер, кажется, не испытывает много давления, наше приложение не использует много процессорного времени, и другие контролируемые ресурсы на машине не потеют. Такое чувство, что мы не работаем достаточно эффективно прямо сейчас.
Мы звоним BeginAcceptTcpListener
и затем сразу передача к a 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
сделать работу в a ThreadPool
распараллельте и затем перейдите на исключение из очереди следующего TcpClient
в его очереди. Вы рекомендовали бы этот подход или являетесь нашей проблемой, которую мы используем TcpListener
и никакой объем быстрого исключения из очереди не собирается зафиксировать это?
Я создал код, который использует сокеты напрямую, но у меня нет возможности провести нагрузочный тест с 1000 клиентами. Не могли бы вы попытаться проверить, как этот код сравнится с вашим текущим решением? Мне были бы очень интересны результаты, так как сейчас я создаю сервер, который также должен принимать большое количество соединений.
static WaitCallback handleTcpRequest = new WaitCallback(HandleTcpRequest);
static void Main()
{
var e = new SocketAsyncEventArgs();
e.Completed += new EventHandler<SocketAsyncEventArgs>(e_Completed);
var socket = new Socket(
AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(new IPEndPoint(IPAddress.Loopback, 8181));
socket.Listen((int)SocketOptionName.MaxConnections);
socket.AcceptAsync(e);
Console.WriteLine("--ready--");
Console.ReadLine();
socket.Close();
}
static void e_Completed(object sender, SocketAsyncEventArgs e)
{
var socket = (Socket)sender;
ThreadPool.QueueUserWorkItem(handleTcpRequest, e.AcceptSocket);
e.AcceptSocket = null;
socket.AcceptAsync(e);
}
static void HandleTcpRequest(object state)
{
var socket = (Socket)state;
Thread.Sleep(100); // do work
socket.Close();
}
Первое, что нужно задать себе, это «1000 одновременных подключений разумны». Лично я думаю, что вы вряд ли попадете в такую ситуацию. Скорее всего, у вас будет 1000 подключений за короткий период времени.
У меня есть тестовая программа TCP, которую я использую для тестирования моей серверной инфраструктуры, она может делать такие вещи, как X соединений в целом в пакетах по Y с промежутком в Z мс между каждым пакетом; который я лично считаю более реальным, чем «огромное количество сразу». Это бесплатно, это может помочь, вы можете получить его отсюда: http://www.lenholgate.com/blog/2005/11/windows-tcpip-server-performance.html
Как говорили другие, увеличьте время ожидания прослушивания, быстрее обрабатывайте соединения, по возможности используйте асинхронное принятие ...
Если я чего-то не упускаю, вы вызываете BeingAcceptTcpClient, который является асинхронным, но затем вы вызываете WaitOne (), чтобы дождаться завершения асинхронного кода, что фактически делает процесс синхронным. Ваш код может одновременно принимать только одного клиента. Или я совсем сумасшедший? По крайней мере, это кажется бесполезным переключением контекста.
Просто предложение: почему бы не принимать клиентов синхронно (используя AcceptTcpClient
вместо BeginAcceptTcpClient
), а затем обрабатывать клиента в новом потоке? Таким образом, вам не придется ждать, пока клиент будет обработан, прежде чем вы сможете принять следующего.
Об этом уже говорилось в других вопросах, но я бы предложил в методе tcpListener.Start() использовать перегрузку, которая позволяет установить отставание в число, превышающее максимальное количество соединений, ожидаемых за один раз:
public void Start()
{
currentHandledRequests = 0;
tcpListener = new TcpListener(IPAddress.Any, 10000);
try
{
tcpListener.Start(1100); // This is the backlog parameter
while (true)
DoBeginAcceptTcpClient(tcpListener);
}
catch (SocketException)
{
// The TcpListener is shutting down, exit gracefully
CheckBuffer();
return;
}
}
По сути, этот параметр устанавливает, сколько "ожидающих" TCP-соединений разрешено, которые ожидают вызова Accept. Если вы не принимаете соединения достаточно быстро, и эта очередь заполняется, TCP-соединения будут автоматически отклонены, и у вас даже не будет возможности их обработать.
Как уже было сказано другими, другой возможностью является ускорение обработки входящих соединений. Однако вам все равно следует установить более высокое значение отставания, даже если вы можете ускорить время приема.