Неблокирующий Tcp сервер - PullRequest
       21

Неблокирующий Tcp сервер

4 голосов
/ 25 апреля 2010

Это не вопрос на самом деле, я просто ищу некоторые рекомендации :) В настоящее время я пишу некоторый абстрактный tcp-сервер, который должен использовать как можно меньше потоков.

В настоящее время это работает так. У меня есть поток, слушающий и несколько рабочих потоков. Поток слушателя просто сидит и ждет подключения клиентов. Я ожидаю, что для каждого экземпляра сервера будет создан отдельный поток слушателя. Рабочие потоки выполняют всю работу по чтению / записи / обработке на клиентском сокете.

Итак, моя проблема в создании эффективного рабочего процесса. И я пришел к некоторой проблеме, которую пока не могу решить. Рабочий код выглядит примерно так (код действительно прост, чтобы показать место, где у меня есть проблема):

List<Socket> readSockets = new List<Socket>();
List<Socket> writeSockets = new List<Socket>();
List<Socket> errorSockets = new List<Socket>();

while( true ){
    Socket.Select( readSockets, writeSockets, errorSockets, 10 );

    foreach( readSocket in readSockets ){
        // do reading here
    }

    foreach( writeSocket in writeSockets ){
        // do writing here
    }

    // POINT2 and here's the problem i will describe below 
}

все работает без проблем при 100% загрузке ЦП, поскольку цикл while повторяется снова и снова, если мои клиенты выполняют подпрограмму send-> receive-> disconnect, это не так уж больно, но если я пытаюсь поддерживать работу отправить-> получить-> отправить-> получить сначала это действительно съедает весь процессор. Итак, моя первая идея заключалась в том, чтобы усыпить там, я проверяю, все ли сокеты отправляют свои данные, и затем помещаю Thread.Sleep в POINT2 всего на 10 мс, но спустя 10 мс получается огромная задержка для этих 10 мс, когда я хочу получить следующий команда из клиентского сокета. Например, если я не пытаюсь «поддерживать активность», команды выполняются в течение 10-15 мс, а при сохранении это становится хуже по крайней мере на 10 мс: (

Может быть, это просто плохая архитектура? Что можно сделать, чтобы мой процессор не получил 100% загрузки и мой сервер реагировал на что-то, появляющееся в клиентском сокете как можно скорее? Может быть, кто-нибудь может привести хороший пример неблокирующего сервера и архитектуры, которую он должен поддерживать?

Ответы [ 4 ]

4 голосов
/ 25 апреля 2010

Сначала посмотрите на класс TcpListener . У него есть метод BeginAccept, который не будет блокировать, и будет вызывать одну из ваших функций при подключении.

Также взгляните на класс Socket и его методы Begin. Они работают так же. Одна из ваших функций (callback function) вызывается всякий раз, когда происходит определенное событие, и вы можете обработать это событие. Все методы Begin являются асинхронными, поэтому они не будут блокироваться и не должны также использовать 100% CPU. По сути, вы хотите BeginReceive для чтения и BeginSend для записи, я полагаю.

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

Таким образом, вам не нужен бесконечный цикл, все зависит от событий.

1 голос
/ 04 октября 2011

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

http://msdn.microsoft.com/en-us/library/fx6588te.aspx

Логика программы выглядит примерно так. Есть один поток, который вызывает listener.BeginAccept, а затем блокирует allDone.WaitOne. BeginAccept - это асинхронный вызов, который выгружается в пул потоков и обрабатывается операционной системой. Когда приходит новое соединение, ОС вызывает метод обратного вызова, переданный из BeginAccept. Этот метод переворачивает allDone, чтобы основной прослушивающий поток знал, что он может прослушивать еще раз. Метод обратного вызова является просто переходным методом и продолжает вызывать еще один асинхронный вызов для получения данных.

Предоставленный метод обратного вызова, ReadCallback, является основным рабочим "циклом" (эффективно рекурсивными асинхронными вызовами) для асинхронных вызовов. Я свободно использую термин «цикл», потому что каждый вызов метода фактически завершается, но не до вызова следующего асинхронного метода. По сути, у вас есть куча асинхронных вызовов, вызывающих друг друга, и вы передаете свой объект «состояние». Этот объект является вашим собственным объектом, и вы можете делать с ним все, что захотите.

Каждый метод обратного вызова возвращает только две вещи, когда ОС вызывает ваш метод:

1) Сокет Объект, представляющий соединение

2) Состояние объекта, с которым вы используете свою логику

С вашим объектом состояния и объектом сокета вы можете эффективно обрабатывать ваши "соединения" асинхронно. ОС очень хороша в этом.

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

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

P.P.S. Я не нашел способа закрыть соединение для прослушивания, кроме как просто избавиться от «слушателя». Поскольку вызывается асинхронный вызов beginListen, этот метод никогда не вернется до тех пор, пока не будет установлено соединение, а это значит, что я не могу сказать ему прекратить, пока оно не вернется. Я думаю, что я отправлю вопрос на MSDN об этом. и ссылку, если я получу хороший ответ.

1 голос
/ 28 апреля 2010

Вы создаете одноранговое приложение или приложение клиент-сервер? Вы должны учитывать, сколько данных вы кладете через сокеты.

Асинхронный BeginSend и BeginReceive - это путь, вам нужно реализовать события, но это быстро, как только вы все сделаете правильно.

Возможно, вы не хотите устанавливать слишком большие тайм-ауты для отправки и получения, но должен быть тайм-аут, чтобы, если по истечении определенного времени ничего не было получено, оно вышло из блока и вы могли с ним справиться ,

0 голосов
/ 23 апреля 2015

Все хорошо, ваш код, кроме значения тайм-аута. Вы устанавливаете его на 10 микросекунд (10 * 10 ^ -6), поэтому ваша подпрограмма while очень часто повторяется. Вы должны установить и адекватное значение (например, 10 секунд), и ваш код не съест 100% CPU.

List<Socket> readSockets = new List<Socket>();
List<Socket> writeSockets = new List<Socket>();
List<Socket> errorSockets = new List<Socket>();

while( true ){
    Socket.Select( readSockets, writeSockets, errorSockets, 10*1000*1000 );

    foreach( readSocket in readSockets ){
        // do reading here
    }

    foreach( writeSocket in writeSockets ){
        // do writing here
    }

    // POINT2 and here's the problem i will describe below 
}
...