Каков наилучший способ реализовать эхо-сервер с асинхронным вводом-выводом и IOCP?
Как мы все знаем, эхо-сервер - это сервер, который читает данные из сокета и записывает эти данные в другой сокет.
Так как порты завершения ввода / вывода Windows предоставляют различные способы работы, мне было интересно, как лучше (наиболее эффективный) реализовать эхо-сервер. Я обязательно найду кого-то, кто проверил способы, которые я опишу здесь, и может внести свой вклад.
Мои занятия Stream
который абстрагирует сокет, именованный канал или что-то еще, и IoRequest
который абстрагирует как OVERLAPPED
структура и буфер памяти для ввода / вывода (конечно, подходит как для чтения, так и для записи). Таким образом, когда я выделяю IoRequest
Я просто выделяю память для памяти, буфер для данных + OVERLAPPED
структура в один выстрел, поэтому я называю malloc()
только однажды. В дополнение к этому, я также реализую необычные и полезные вещи в IoRequest
объект, такой как атомный счетчик ссылок и т. д.
Сказал, что давайте рассмотрим способы сделать лучший эхо-сервер:
-------------------------------------------- Метод А. --- ---------------------------------------
1) Сокет "читатель" завершает чтение, обратный вызов IOCP возвращается, и у вас есть IoRequest
только что закончил с буфером памяти.
2) Давайте скопируем только что полученный буфер с "ридером" IoRequest
"писателю" IoRequest
, (это будет включать memcpy()
или что угодно).
3) Давайте снова запустим новое чтение с ReadFile()
в "ридере", с тем же IoRequest
используется для чтения.
4) Давайте запустим новое письмо с WriteFile()
в "писатель".
-------------------------------------------- Метод Б. --- ---------------------------------------
1) Сокет "читатель" завершает чтение, обратный вызов IOCP возвращается, и у вас есть IoRequest
только что закончил с буфером памяти.
2) Вместо того, чтобы копировать данные, передайте это IoRequest
"писателю" для записи, без копирования данных с memcpy()
,
3) "Читателю" теперь нужен новый IoRequest
чтобы продолжить чтение, выделить новый или передать уже выделенный ранее, возможно, только что завершенный для записи до того, как произойдет новое.
Итак, в первом случае каждый Stream
объекты имеют свои IoRequest
, данные копируются с memcpy()
или аналогичные функции, и все работает нормально. Во втором случае 2 Stream
объекты проходят IoRequest
объекты друг друга, без копирования данных, но это немного сложнее, вы должны управлять "обменом" IoRequest
объекты между 2 Stream
объекты, с возможным недостатком, чтобы получить проблемы с синхронизацией (что относительно того завершения происходят в разных потоках?)
Мои вопросы:
Q1) Стоит ли избегать копирования данных!? Копирование 2 буферов с memcpy()
или похожий, очень быстрый, также потому, что кеш процессора используется именно для этой цели. Давайте рассмотрим, что с первым методом у меня есть возможность выводить из гнезда "reader" в несколько сокетов "writer", но со вторым я не могу этого сделать, так как я должен создать N new IoRequest
объекты для каждого N авторов, так как каждый WriteFile()
нужен свой OVERLAPPED
состав.
Q2) Я предполагаю, что когда я запускаю новые N писем для N различных сокетов с WriteFile()
Я должен предоставить N разных OVERLAPPED
Структура И N разных буферов, где читать данные. Или я могу уволить N WriteFile()
звонки с N разные OVERLAPPED
брать данные из того же буфера для N сокетов?
1 ответ
Стоит ли избегать копирования данных!?
Зависит от того, сколько вы копируете. 10 байт, не так много. 10 МБ, то да, стоит избегать копирования!
В этом случае, поскольку у вас уже есть объект, который содержит данные rx и блок OVERLAPPED, копировать его кажется бессмысленным - просто переиздайте его в WSASend() или что-то еще.
but with the second one I can't do that
Вы можете, но вам нужно абстрагировать класс 'IORequest' от класса 'Buffer'. В буфере хранятся данные, атомный подсчет ссылок int и любая другая информация управления для всех вызовов, IOrequest блок OVERLAPPED и указатель на данные и любую другую информацию управления для каждого вызова. Эта информация может иметь атомный int-подсчет ссылок для объекта буфера.
IOrequest - это класс, который используется для каждого звонка отправки. Поскольку он содержит только указатель на буфер, копировать данные не нужно, поэтому он достаточно мал и O(1) соответствует размеру данных.
Когда приходят завершения tx, потоки обработчика получают запрос IOrequest, разыменовывают буфер и переводят атомарный int в него в ноль. Поток, которому удается достичь 0, знает, что буферный объект больше не нужен, и может удалить его (или, что более вероятно, на высокопроизводительном сервере, повторно его переименовать для последующего повторного использования).
Или я могу запустить N вызовов WriteFile() с N различными OVERLAPPED, получая данные из одного и того же буфера для N сокетов?
Да, ты можешь. Смотри выше.
Число рейнольдса многопоточность - конечно, если ваши "данные управления" могут быть получены из нескольких потоков обработчика завершения, тогда да, вы можете защитить их критическим разделом, но атомарный int должен подойти для пересчета буфера.