Как стать автором
Обновить

Комментарии 14

Полезная информация, хотя я этим никогда и не воспользуюсь из-за убогости виндового апи.
зато есть boost.asio который вроде как их использует
Если boost использует дополнительные возможности ОС, сам при этом являясь кроссплатформенным, то это хорошо.
>Помимо структуры OVERLAPPED вам потребуется передавать в поток еще кое-какую IO-информацию — например, адрес буфера, его длину или даже сам буфер.
Не убогонько ли для масштабируемых серверов? На каждый слабоактивный сокет выделять целый буффер…
В нашем юниксовом селе обычно дело обстоит примерно так(далее про неблокирующие сокеты) — есть у нас один большой вектор с указателями на 4кб буфферы(обычно равен максимальному размеру буффера сокета, чтобы одним сисколом readv всё утащить в юзерспэйс). Ну и когда приходят какие-то пакеты, если они маленькие, то сразу обрабатываются и наш большой векторо-буффер сразу опять в строю, если не можем сразу обработать, то передаём занятые 4кбайтовые буффера во владение данных связаных с клиентом и аллокэйтим ещё чистеньких страничек для следующего readv ;)
p.s. не писал серверов под винду, если не прав — поправьте, тк самому интересно узнать.
Вообще статья о потоках, а не буферах, так что я не мудрувствовал особо. Впрочем, у меня вообще нету отдельных буферов для каждого клиента — все клиенту пишут в одно большое кольцо памяти, которое кстати статично и мне не приходится каждый раз искать место под нового юзера или новое сообщение.
я наверное один из тех кто неправильно понял как работают IOCP :)
не могли бы вы вкратце описать как прочитать из сокета?

ну например на линуксе это выглядело бы так:

создаём сокет
добавляем IN/OUT события для данного сокета
loop:
дожидаемся событий
читаем из сокета
обрабатываем полученные данные
goto loop;

Мне почему-то казалось, что когда используется модель IOCP, то нужно делать асинхронные вызовы и передавать буффер?
как-то так:

создаём сокет
аллокэйтим буффер
loop:
делаем асинхронный запрос на чтение и передаём буффер
дожидаемся событий
обрабатываем полученные данные
goto loop;
То, что вы описали kills the purpose. Я еще в прошлом посте хотел отметить что неблокирующие сокеты (или вообще чтение/запись) это еще не асинхронность.

Суть как раз в том, что после

>делаем асинхронный запрос на чтение и передаём буффер

мы ничего не ждем. Каждый цикл мы смотрим на ситуацию — есть ли чего на отправку или на прием. Если что-то есть, то мы инициируем новую операцию или обрабатываем завершившуюся. Если нет вообще ничего, то поток спит не потребляя CPU.

Вообще касательно линукса: там есть aio. Вот хорошая, на мой взгляд иллюстрация: www.ibm.com/developerworks/linux/library/l-async/figure1.gif
Aio — это фактически оверлаппед вызовы виндовс. Порта завершения в линуксе нет, т.к. это относительно высокоуровневый объект. Я сейчас разбираюсь с механизмом kqueue+aio в FreeBSD, чтобы реализовать аналог iocp.
И да, скоро соберу себя в кучу и переведу статью, откуда эту картинку взял.
так, хочу разобраться :)
у ядра есть свои буфферы для сокетов(которые достаточно хорошо реализованы и кушают память только по потребности), когда они заполняются — нужно оповестить приложение, которое жаждит знать о том что в сокет поступили данные. Так вот зачем нам тут aio? Если неблокирующий сискол на чтение(есть даже какие-то подвижки в сторону zero-copy — vmsplice) сможет сейчас же забрать всё что находится в буффере ядра и продолжить выполнение.
aio же подразумевает то что мы ко всему этому на каждый запрос чтения выделяем буффер, о заполнении которого нас оповещают. Но это же во много хуже по сравнению с неблокирующим вариантом, где мне достаточно одного буффера на тред(при условии что мы сразу обработаем данные, в реальности всё конечно же чуточку сложнее).
в чём преимущество у aio с сетью(не файловый io, там всё сложнее и поэтому вполне разумно)?! я вижу только недостатки у подобного подхода.
ну и по количеству сисколов :)
у меня вектор буфферов(буффер = странице памяти) на тред имеет размер = SO_RCVBUF
и когда меня оповещают о том что у сокета появились какие-то данные в буффере, я почти уверен что мне будет достаточно одного readv :)
При использовании aio я просто не могу себе позволить выделять такие большие буффера на каждый сокет. И сколько тогда раз придётся просить ядро заполнить мне буффер, а потом дожидаться ответа?!
>у ядра есть свои буфферы для сокетов
Да, приложение работает не с этими буферами. Zero-copy это вообще отдельная тема.

>Но это же во много хуже по сравнению с неблокирующим вариантом, где мне достаточно одного буффера на тред

На тред? У вас client-per-thread? Тогда стоит считать не буферы, а треды. При описанной выше схеме у вас всего лишь несколько потоков.

И я не совсем понял насчет
>что мы ко всему этому на каждый запрос чтения выделяем буффер

Со стороны вызовы recv на неблокирующем сокете и WSASend с OVERLAPPED выглядят одинаково. Разница в том, как мы получаем и обрабатываем результат выполнения.
>Да, приложение работает не с этими буферами
Я просто с этого начал, так как хотел подчеркнуть бессмысленость aio в связке с сокетами ;)

>У вас client-per-thread?
нет, у меня много клиентов на тред, но так как я всё равно не смогу одним тредом работать в двух местах :) то зачем мне больше одного буффера(iovec, чтобы было проще отцеплять заполненые и необработаные) на тред…
Если в данный момент времени нехватает данных(пришло пол пакета), то я отцепляю из вектора полузаполненный буффер и отдаю клиенту, а когда нужно опять делать сискол — цепляю его первым в iovec… Нет смысла городить пустые буффера на каждого клиента :)

>И я не совсем понял насчет
>>что мы ко всему этому на каждый запрос чтения выделяем буффер
когда вы делаете WSARead — вы сразу же получаете результат или ждёте пока IOCP не скажет что он заполнен? Если второй вариант, то да — вы создаёте пустой и бессмысленый буффер ;)
У неблокирующего варианта я сразу получаю результат и могу по нему работать. А отработав — вернуть буффер, чтобы им попользовались остальные клиенты ;)
Я понял о чем вы. Тут вы правы. Просто я pre-allocate все буферы при старте сервера, поэтому как то не задумывался об этом.
И в общем-то не сказать, что pre-allocate обязательно хуже.
На практике аллоцированные и не запрошенные страницы памяти обычно не вклеиваются в адресное пространство и не участвуют в планировании физического ресурса.
Может быть проиллюстрировано следующим образом:
// 596 kb used, try to allocate 256MB...
char *mem = (char *)malloc(1024*1024*256);
// 596 kb used!
mem[4096 * 0] = 123; // 596 kb used after
mem[4096 * 1] = 123; // 600 kb used after
mem[4096 * 2] = 123; // 604 kb used after
mem[4096 * 300] = 123; // 612 kb used after
mem[4096 * 400] = 123; // 616 kb used after
mem[4096 * 500] = 123; // 620 kb used after

Ключевое слово в ядре linux на этот счёт «nopage».
А в защиту IOCP, при всей его одиозности (и WINAPI вцелом): в отличии от большинства syscall-ов, у него есть шансы на отсутствие копирования данных «буфер сокета -> пользователь». Это случается если overlapped чтение вызывается до приёма TCP пакета, а буфер сокета пуст… Впрочем, это с лёгкостью может нивелироваться межпоточными издержками…
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории