Как распределить входящие соединения tcplisener по потокам в .NET?

При использовании Net.Sockets.TcpListener, как лучше всего обрабатывать входящие соединения (.AcceptSocket) в отдельных потоках?

Идея состоит в том, чтобы запустить новый поток, когда будет принято новое входящее соединение, в то время как tcplistener остается доступным для дальнейших входящих соединений (и для каждого нового входящего соединения создается новый поток). Вся связь и завершение с клиентом, который инициировал соединение, будут обрабатываться в потоке.

Приветствуется пример C# кода VB.NET.


person Jorrit Reedijk    schedule 15.09.2008    source источник


Ответы (5)


Код, который я использовал, выглядит так:

class Server
{
  private AutoResetEvent connectionWaitHandle = new AutoResetEvent(false);

  public void Start()
  {
    TcpListener listener = new TcpListener(IPAddress.Any, 5555);
    listener.Start();

    while(true)
    {
      IAsyncResult result =  listener.BeginAcceptTcpClient(HandleAsyncConnection, listener);
      connectionWaitHandle.WaitOne(); // Wait until a client has begun handling an event
      connectionWaitHandle.Reset(); // Reset wait handle or the loop goes as fast as it can (after first request)
    }
  }


  private void HandleAsyncConnection(IAsyncResult result)
  {
    TcpListener listener = (TcpListener)result.AsyncState;
    TcpClient client = listener.EndAcceptTcpClient(result);
    connectionWaitHandle.Set(); //Inform the main thread this connection is now handled

    //... Use your TcpClient here

    client.Close();
  }
}
person Community    schedule 29.10.2008
comment
Спасибо за исходный код, я буду кодировать его так. Новые потоки могут быть дорогими, но, поскольку я не масштабирую более 5 или 6 одновременных входящих подключений, на данный момент это будет нормально. - person Jorrit Reedijk; 29.12.2008
comment
Думаю, слушатель и tcpListener запутались в примере, в остальном хороший код. Нашел это: msdn.microsoft.com/en -us/library/ на основе того, что я здесь нашел. - person Justin Wignall; 07.05.2010
comment
Нет необходимости в этом соединении. Просто позвоните BeginAcceptClient сразу после EndAccept: private void HandleAsyncConnection(IAsyncResult result) { var listener = (TcpListener)result.AsyncState; var client = listener.EndAcceptTcpClient(result); listener.BeginAcceptTcpClient(HandleAsyncConnection, listener); ... - person gatopeich; 28.05.2013

Я полагаю, вы делаете это так же, как и любую другую асинхронную операцию в .NET: вы вызываете версию метода BeginXxx, в данном случае BeginAcceptSocket. Ваш обратный вызов будет выполняться в пуле потоков.

Объединенные в пул потоки обычно масштабируются намного лучше, чем потоки на соединение: как только вы преодолели несколько десятков соединений, система работает гораздо усерднее при переключении между потоками, чем при выполнении фактической работы. Кроме того, у каждого потока есть собственный стек, размер которого обычно составляет 1 МБ (хотя это зависит от флагов ссылки), который должен находиться в виртуальном адресном пространстве размером 2 ГБ (в 32-разрядных системах); на практике это ограничивает вас менее чем 1000 потоков.

Я не уверен, использует ли его в настоящее время пул потоков .NET, но в Windows есть объект ядра, называемый портом завершения ввода-вывода, который помогает в масштабируемом вводе-выводе. Вы можете ассоциировать потоки с этим объектом, и с ним могут быть связаны запросы ввода-вывода (включая прием входящих соединений). Когда ввод-вывод завершается (например, устанавливается соединение), Windows освобождает ожидающий поток, но только в том случае, если количество текущих выполняемых потоков (не заблокированных по какой-либо другой причине) меньше настроенного предела масштабируемости для порта завершения. Обычно вы устанавливаете это значение, кратное количеству ядер.

person Mike Dimmick    schedule 15.09.2008

Я хотел бы предложить другой подход: в моем предложении используются только два потока. * один поток проверяет входящие соединения. * Когда открывается новое соединение, эта информация записывается в общую структуру данных, которая содержит все текущие открытые соединения. * Второй поток перечисляет эту структуру данных и для каждого открытого соединения получает отправленные данные и отправляет ответы.

Это решение является более масштабируемым по потокам и при правильной реализации должно иметь лучшую производительность, чем открытие нового потока для каждого открытого соединения.

person Dror Helper    schedule 20.09.2008
comment
Это я запомню, хотя очень хотелось бы отвечать на каждое входящее соединение сразу. Спасибо за предложение. - person Jorrit Reedijk; 29.12.2008
comment
Привет! @Dror Helper, ваше предложение выглядит хорошо. Итак, как реализовать стратегию повышения эффективности? Можете поделиться, у вас есть фрагмент кода? - person bashkan; 07.09.2014

Отличный пример есть в O'Reilly C# 3.0 Cookbook. Вы можете загрузить прилагаемый исходный код по адресу http://examples.oreilly.com/9780596516109/CSharp3_0CookbookCodeRTM.zip.

person x0n    schedule 15.09.2008

Я бы использовал пул потоков, таким образом вам не придется каждый раз запускать новый поток (поскольку это довольно дорого). Я бы также не стал бесконечно ждать дальнейших соединений, поскольку клиенты могут не закрыть свои соединения. Как вы планируете каждый раз направлять клиента в один и тот же поток?

Извините, нет образца.

person Paul van Brenk    schedule 15.09.2008