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

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

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

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

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


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


Ответы (4)


arrow_upward
4
arrow_downward

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

  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

arrow_upward
3
arrow_downward

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

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

person Timores    schedule 30.03.2010

arrow_upward
1
arrow_downward

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

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

person Byron Whitlock    schedule 30.03.2010

arrow_upward
1
arrow_downward

Вам определенно не нужен поток на запрос. Даже если у вас меньше клиентов, накладные расходы на создание и уничтожение потоков приведут к повреждению сервера, и вы не сможете получить 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