с помощью буст-сокетов мне нужен только один io_service? - PullRequest
9 голосов
/ 02 ноября 2010

с несколькими соединениями в нескольких разных потоках .. Я в основном делаю базовый класс, который использует boost / asio.hpp и все там tcp .. Теперь я читал это: http://www.boost.org/doc/libs/1_44_0/doc/html/boost_asio/tutorial/tutdaytime1.html в нем говорится, что «Все программы, использующие asio, должны иметь хотя бы один объект io_service». поэтому, если мой базовый класс имеет статический io_service (что означает, что для всей программы будет только 1, и все различные потоки и соединения будут использовать один и тот же объект io_service) или сделать каждое соединение своим io_service?

спасибо впереди!

обновление: Хорошо, так что в основном я хочу сделать класс для базового клиента, у которого будет сокет n. Для каждого сокета у меня будет поток, который всегда получает, и отдельный поток, который иногда отправляет пакеты. посмотрев здесь: www.boost.org/doc/libs/1_44_0/doc/html/boost_asio/reference/ip__tcp/socket.html (не могу сделать гиперссылку, так как я здесь новенький ... так что только 1 гиперлинг на пост) я вижу этот класс сокетов не является полностью потокобезопасным ..

так 2 вопроса: 1. Исходя из только что написанного дизайна, нужен ли мне 1 io_service для всех сокетов (то есть сделать его статическим членом класса) или мне нужен один для каждого? 2. Как сделать потокобезопасным? Должен ли я поместить его в «потокобезопасную среду», что означает создание нового класса сокетов, который имеет мьютексы и прочее, что не позволяет отправлять и получать одновременно, или у вас есть другие предложения? 3. Может быть, я должен пойти на асинхронный дизайн? (ofc каждый сокет будет иметь другой поток, но отправка и получение будут в одном потоке?)

просто чтобы уточнить: я делаю TCP-клиент, который подключается ко многим серверам.

Ответы [ 3 ]

11 голосов
/ 02 ноября 2010

Сначала вам нужно решить, какой стиль связи через сокет вы собираетесь использовать:

  1. синхронный - означает, что все низкоуровневые операции блокируются, и обычно вам требуется поток для принятия, а затем потоки (чтение потока или io_service) для обработки каждого клиента.

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

Преимущество подхода 1 состоит в том, что код (??) намного проще, чем 2, однако я считаю, что 2 является наиболее гибким, и фактически с 2, по умолчанию у вас есть однопоточное приложение (внутренне обратные вызовы событий) выполняются в отдельном потоке к основному потоку диспетчеризации), недостатком 2, конечно, является то, что ваша задержка обработки затрагивает следующие операции чтения / записи ... Конечно, вы можете создавать многопоточные приложения с подходом 2, но не наоборот. наоборот (то есть однопоточный с 1) - следовательно, гибкость ...

Так что, в принципе, все зависит от выбора стиля ...

РЕДАКТИРОВАТЬ: обновлено для новой информации, это довольно долго, я не могу потрудиться написать код, есть много в документации Boost, я просто опишу, что происходит для ваша выгода ...

[основная тема] - объявить экземпляр io_service - для каждого сервера, к которому вы подключаетесь (я предполагаю, что эта информация доступна при запуске), создайте класс (скажем, ServerConnection) и в этом классе создайте сокет tcp :: socket, используя тот же экземпляр io_service сверху и в самом конструкторе вызовите async_connect, ПРИМЕЧАНИЕ: этот вызов является планированием запроса на подключение, а не реальной операцией подключения (это не произойдет до позже) - как только все объекты ServerConnection (и их соответствующие async_connects поставлены в очередь), вызовите run() в экземпляре io_service. Теперь основной поток заблокирован, отправляя события в очередь io_service.

[asio thread] В io_service по умолчанию есть поток, в котором вызываются запланированные события, вы не управляете этим потоком, и для реализации «многопоточной» программы вы можете увеличить количество потоков, которые использует io_service. , но на данный момент придерживайтесь одного, это сделает вашу жизнь простой ...

asio будет вызывать методы в вашем классе ServerConnection в зависимости от того, какие события готовы из запланированного списка. Первое событие, которое вы поставили в очередь (до вызова run ()), было async_connect, теперь asio перезвонит вам, когда будет установлено соединение с сервером, обычно вы реализуете метод handle_connect, который будет вызываться (вы передаете метод к вызову async_connect). На handle_connect все, что вам нужно сделать, это запланировать следующий запрос - в этом случае вы хотите прочитать некоторые данные (возможно, из этого сокета), поэтому вы вызываете async_read_some и передаете функцию, которая будет уведомлена, когда есть данные. После этого основной поток рассылки asio продолжит отправку других событий, которые готовы (это могут быть другие запросы на подключение или даже добавленные вами async_read_some).

Допустим, вам звонят, потому что на одном из сокетов сервера есть некоторые данные, которые передаются вам через ваш обработчик для async_read_some - вы можете затем обработать эти данные, сделать, как вам нужно, но это самый важный бит - после того, как это сделано, запланируйте следующий async_read_some, таким образом asio будет доставлять больше данных, когда они станут доступны. ОЧЕНЬ ВАЖНОЕ ПРИМЕЧАНИЕ: если вы больше не планируете какие-либо запросы (т.е. выходите из обработчика без постановки в очередь), то в io_service не хватит событий для отправки, и run () (которую вы вызывали в главном потоке) завершится.

Теперь, что касается письма, это немного сложнее.Если все ваши записи выполняются как часть обработки данных из вызова read (то есть в потоке asio), вам не нужно беспокоиться о блокировке (если только у вашего io_service несколько потоков), иначе в вашем методе записи,добавить данные в буфер и запланировать запрос async_write_some (с помощью обработчика write_handler, который будет вызываться при записи в буфер, частично или полностью).Когда asio обрабатывает этот запрос, он вызывает ваш обработчик после записи данных, и у вас есть возможность снова вызвать async_write_some, если в буфере осталось больше данных или если их нет, вам не нужно беспокоиться о планировании записи,На этом этапе я упомяну одну технику, рассмотрим двойную буферизацию - я оставлю это на этом.Если у вас совершенно другой поток, который находится за пределами io_service, и вы хотите написать, вы должны вызвать метод io_service::post и передать метод для выполнения (в вашем классе ServerConnection) вместе с данными, io_service будетзатем вызовите этот метод, когда это возможно, и в этом методе вы можете затем буферизовать данные и при необходимости вызвать async_write_some, если запись в данный момент не выполняется .

Теперь есть одинОЧЕНЬ важная вещь, с которой вы должны быть осторожны, вы НИКОГДА не должны планировать async_read_some или async_write_some, если уже выполняется , то есть, допустим, вы звонили async_read_some насокет, пока asio не вызовет это событие, вы не должны планировать другое async_read_some, иначе у вас будет много дерьма в ваших буферах!

Хорошей отправной точкой является сервер / клиент asio chat, которыйВы найдете в документах Boost, он показывает, как используются методы async_xxx.И имейте это в виду, все вызовы async_xxx возвращаются немедленно (в течение нескольких десятков микросекунд), поэтому блокирующих операций нет, все это происходит асинхронно.http://www.boost.org/doc/libs/1_39_0/doc/html/boost_asio/example/chat/chat_client.cpp, - это пример, на который я ссылался.

Теперь, если вы обнаружите, что производительность этого механизма слишком низкая, и вы хотите иметь многопоточность, все, что вам нужно сделать, это увеличить количество потоков, доступных для основного io_service, и реализовать соответствующую блокировку в вашемметоды чтения / записи в ServerConnection и все готово.

5 голосов
/ 02 ноября 2010

Для асинхронных операций вы должны использовать один объект io_service для всей программы. Независимо от того, является ли он статическим членом класса или созданным в другом месте, решать только вам. Несколько потоков могут вызывать метод run, это описано в Обратном ответе.

Несколько тем могут вызывать io_service :: run () для установки пула потоки, из которых обработчики завершения может быть вызван. Этот подход может также использоваться с io_service :: post () для использования средство для выполнения любых вычислительных задачи в пуле потоков.

Обратите внимание, что все темы, которые присоединились пул io_service считаются эквивалентно, и io_service может распределить работу по ним в произвольная мода.

если у вас есть обработчики, которые не являются потокобезопасными, прочитайте о strands .

Нить определяется как строго последовательный вызов события обработчики (то есть нет одновременного призывание). Использование прядей позволяет выполнение кода в многопоточном программа без необходимости явного блокировка (например, использование мьютексов).

1 голос
/ 02 ноября 2010

io_service - это то, что вызывает все функции-обработчики для ваших соединений.Таким образом, у вас должен быть запущен поток, чтобы распределить работу между потоками.Вот страница объяснения io_service и потоков:

Threads и Boost.Asio

...