Асинхронный поточный tcp-сервер

Я хочу создать высокопроизводительный сервер на C #, который мог бы обслуживать около 10 тысяч клиентов. Теперь я начал писать TcpServer с C #, и для каждого клиентского соединения я открываю новый поток. Я также использую один поток для приема подключений. Пока все хорошо, работает нормально.

Сервер должен десериализовать входящие объекты AMF, выполнить некоторую логику (например, сохранить позицию игрока) и отправить некоторый объект обратно (сериализация объектов). Я не беспокоюсь о сериализации / десериализации части atm.

Меня больше всего беспокоит то, что у меня будет много потоков с 10k клиентами, и я где-то читал, что ОС может содержать только несколько сотен потоков.

Есть ли какие-нибудь источники / статьи по написанию достойного асинхронного поточного сервера? Есть ли другие возможности или 10к потоков будут работать нормально? Я посмотрел в Google, но не смог найти много информации о шаблонах проектирования или способах, которые это четко объясняют


person mark_dj    schedule 30.03.2010    source источник


Ответы (4)


Вы столкнетесь с рядом проблем.

  1. Вы не можете раскрутить 10 000 потоков по нескольким причинам. Это уничтожит планировщик ядра. Если вы используете 32-разрядную версию, то адресное пространство стека по умолчанию, равное 1 МБ, означает, что 10 тыс. Потоков резервируют около 10 ГБ адресного пространства. Это не удастся.

  2. Вы также не можете использовать простую систему выбора. По сути, выберите O (N) для количества сокетов. С сокетами 10к это плохо.

  3. Вы можете использовать порты завершения ввода-вывода. Это сценарий, для которого они созданы. Насколько мне известно, не существует стабильной управляемой библиотеки портов завершения ввода-вывода. Вам придется написать свой собственный, используя P / Invoke или Managed C ++. Развлекайся.

person Kennet Belenky    schedule 30.03.2010
comment
Хотя я согласен, что 10 тысяч потоков - это не выход. 32-битные окна могут легко создавать 10 тыс. Потоков. blogs.technet.com/markrussinovich/archive/2009/07/ 08 / - person Byron Whitlock; 31.03.2010
comment
Базовый стек ядра составляет 12 КБ в 32-битной Windows и 24 КБ в 64-битной Windows. 14 225 потоков требуют около 170 МБ резидентной доступной памяти, blogs.technet. ru / markrussinovich / archive / 2009/07/08 / - person Byron Whitlock; 31.03.2010
comment
Да, удачи в запуске управляемого потока с практически нулевым стеком. - person Kennet Belenky; 31.03.2010
comment
Я могу ошибаться насчет портов завершения ввода-вывода, требующих P / Invoke. Похоже, что асинхронный ввод-вывод, встроенный в System.Net.Sockets, уже может их использовать. - person Kennet Belenky; 31.03.2010
comment
Что было бы неправильным использовать методы BeginXxx на сокетах? Насколько я понимаю, они используют порты завершения ввода-вывода. Запустите Reflector и найдите класс BaseOverlappedAsyncResult, вы поймете, что я имею в виду. - person Lucero; 31.03.2010
comment
@Bryon: Эта статья, на которую вы ссылаетесь, предназначена для 14000 потоков, которые постоянно находятся в спящем режиме, каждый с пустым пространством стека miniumum, не выполняющих никакой работы вообще. Получить 10000 потоков, действительно выполняющих работу, - это НАМНОГО другая проблема. - person abelenky; 31.03.2010
comment
@Lucero, как указывалось в моем предыдущем комментарии, я считаю, что методы BeginXxx действительно используют порты завершения ввода-вывода (но у меня нет мотивации выяснять наверняка). - person Kennet Belenky; 31.03.2010
comment
@Kennet, я исследовал материал System.Net.Sockets IO Completion, пока вы писали свой комментарий, я видел его только позже, так как оставил браузер открытым во время исследования. - person Lucero; 31.03.2010
comment
Асинхронный ввод-вывод с перекрытием AFAIK реализован с использованием APC (msdn .microsoft.com / en-us / library / ms681951% 28VS.85% 29.aspx), а не IOCP. - person snemarch; 01.04.2010
comment
Я думал о создании одного потока, который теперь принимает все соединения. Создайте пул потоков для обработки входящих запросов Async Recieve и Write. Будет ли это хорошей идеей? - person mark_dj; 02.04.2010
comment
@mark_dj Ага, наверное, сработает. Я уверен, что вы столкнетесь с проблемами, но в этом и заключается суть программирования (обработка 10k TCP-соединений - непростая задача. Google C10K для подробного обсуждения вопросов). Вы можете использовать один поток для обработки Accepts, но в этом нет необходимости. В API есть методы BeginAccept / EndAccept. - person Kennet Belenky; 02.04.2010
comment
Хорошо, разожгли 18k сокетов ... работали нормально, но сокеты мало работали ^ _ ^ '. Как лучше всего определить, отключается ли клиент? BeginWrite / BeginRead запускают их обратный вызов только тогда, когда у них действительно есть данные для чтения / записи. Так что мне нужно будет время от времени отправлять какие-то данные мусора? Я также прошел тест с 6k сокетами, передающими привет моему серверу, что заняло около нескольких секунд. Неплохо .. Я добился этого с помощью BeginRead из TcpClient.GetStream () и ThreadPool.QueueUserWorkItem. - person mark_dj; 02.04.2010

Способ написать эффективный многопоточный сервер - использовать порты завершения ввода-вывода (использование потока для каждого запроса довольно неэффективно, как упоминает @Marcelo).

Если вы используете асинхронную версию класса сокетов .NET, вы получаете это бесплатно. См. этот вопрос, который есть указатели на документацию.

person Timores    schedule 30.03.2010

Вы хотите изучить использование портов завершения ввода-вывода . По сути, у вас есть пул потоков и очередь операций ввода-вывода.

Порты завершения ввода-вывода обеспечивают эффективную модель потоков для обработки нескольких запросов асинхронного ввода-вывода в многопроцессорной системе. Когда процесс создает порт завершения ввода-вывода, система создает связанный объект очереди для запросов, единственной целью которых является обслуживание этих запросов. Процессы, которые обрабатывают множество одновременных запросов асинхронного ввода-вывода, могут делать это быстрее и эффективнее, используя порты завершения ввода-вывода в сочетании с заранее выделенным пулом потоков, чем создавая потоки во время получения ими запроса ввода-вывода.

person Byron Whitlock    schedule 30.03.2010

Вам определенно не нужен поток на запрос. Даже если у вас меньше клиентов, накладные расходы на создание и уничтожение потоков приведут к повреждению сервера, и вы не сможете получить 10 000 потоков; планировщик ОС умрет ужасной смертью задолго до этого.

В Интернете есть множество статей об асинхронном серверном программировании на C # (например, здесь). Просто погуглите немного.

person Marcelo Cantos    schedule 30.03.2010
comment
10k потоков * 1kb stackspace = 10meg, а не 10gig. Очевидно, вы будете округлены до степени детализации страницы - и на самом деле не только это, но и dwAllocationGranularity (64 КБ). Тем не менее, 10 тыс. Потоков с минимальным размером стека составляют ~ 625 мегабайт, а не 10 гигабайт :) (однако это не включает накладные расходы на поток, не связанные с размером стека) - person snemarch; 01.04.2010
comment
+1 @snemarch. Должно быть, было поздно. Я удалил комментарии к размеру стека. - person Marcelo Cantos; 01.04.2010