Обращение к задающим вопросы по сокетам.
От: Maxim S. Shatskih Россия  
Дата: 12.06.04 17:16
Оценка: 271 (28) +3 -2
#Имя: FAQ.sockets.answers
Изучать сокеты имело бы смысл в такой последовательности.

Сначала Berkeley, то есть вызовы, написанные сплошь маленькими буквами, и без использования неблокирующего IO. Это неважно, что вы любите Си++ и врапперы — напишите врапперы вокруг Berkeley. Berkeley вполне себе многонитевы, и, если несколько нитей к одному сокету на лазают — то вполне себе решение.

Колоссальное количество задач решается на них. Без использования FIONBIO. Без использования микрософтных наворотов типа WSARecv или AcceptEx.

Лучшие ИМХО документы по Berkeley sockets — MSDN Library и маны от FreeBSD.

Потом можно изучать nonblocking. Кстати, FIONREAD — он же СиШарпный socket.Available — нужен практически только для nonblocking моды.

Для изучения микрософтных наворотов нужно в первую очередь понимать, как вообще работает async IO в Win32.

Преимуществ async IO по сути немного, учитывая, что ОС многонитевая, и можно просто еще одну нитку создать.

а) можно использовать IOCP, что хорошо для некоторых сценариев.
б) меньше раз зовется memcpy() внутри AFD и TCP стека. Если на сокете постоянно висит overlapped read — то AFD копирует данные сразу из сетевых пакетов в user buffer, связанный с этим readом. Если же не висит — то AFD приходится использовать свой внутренний буфер, и получается на одно memcpy() больше. Теперь сравним select() и сценарий с кучей overlapped IO на куче сокетов, завязанных на один IOCP. Во время останова в select() на сокетах не висят операции чтения, потому приходится использовать лишнюю буферизацию.

IO completion port — хорошая штука, но реально бессмысленна для сценариев, где на нем будет ждать 1 нитка. Например, если по нитке на сокет. Тогда уж лучше использовать APC, а в нитке вместо GetQueuedCompletionStatus написать SleepEx(0, TRUE). Еще есть сообщения Windows, мне они не нравятся, но тоже механизм для этого случая, особенно, если та же нитка и UI крутит, и с сокетом работает.

Еще момент. Zero-copy send в Windows. Ставится SO_SNDBUF в нуль. После чего все send() и прочие WSASend() и WriteFile() гонят DMA сетевой карты прямо по этому буферу, ничего не копируя. Недостаток — операция завершится, только когда придут все ACKи на эти данные. Потому в этом случае лучше не слать помалу, а слать эдак по 64 кило, и слать сразу кучей overlapped sends.

У меня был код, который легко насыщал 100мегабитную сеть. Протокол, смахивающий на HTTP. Для нормальной работы пришлось а) ставить SO_SNDBUF в большое число б) слать короткий заголовок в) ставить SO_SNDBUF в нуль г) слать длинное тело. Такой, и только такой паттерн мог практически насытить и сеть, и дисковый IO для чтения тела из файла.

Еще момент. Производительность TCP на паттерне "короткий вопрос — короткий ответ" очень часто зависит от алгоритма Nagle. Причем тормоза в ответах. Немножко упрощаю, но примерно так — ответ на первый вопрос пройдет на ура, а ответ на второй затормозится до прихода ACK во встречном потоке — реально это произойдет только в третьем вопросе, или же после задержки в полсекунды. В итоге, если вопрошающая сторона наглухо стоит, ожидая ответа — третьего вопроса не будет, и мы получаем по транзакции в полсекунды, независимо от скорости сети

Примерно то же самое возникает, если ответ состоит из нескольких мелких sendов — например, если делать ответ HTTP, печатая каждую строку функцией fprintf прямо в сокет. Первая строка прошла, вторая ждет ACKа, а ACK придет в данному случае только как delayed, потому как встречного потока нет — вторая сторона крутится в цикле приема заголовка ответа. В линуксе была опция TCP_CORK специально для такого паттерна, во FreeBSD то же самое достигалось, но чуть иначе, в Windows вроде как вообще нет.

Короче, реально написать код, у которого лимит скорости будет упираться в полсекунды на delayed ACK.

Далеко не всегда это хорошо. Именно потому X11 — у которого такой паттерн — выключает TCP_NODELAY. Недостаток — сильная фрагментация данных, по сети едут совсем мелкие пакеты с большими заголовками. Еще можно переделать протокол так, чтобы ответ шел не на каждый вопрос, а на пачку. Примерно так сделано в RDP.

К чему это все написано. Полно начинающих, которые задают вопросы по коду, в котором используется лишний для задачи функционал. Например, не к месту асинхронный сокет. Не к месту nonblocking IO. Эта задача решалась бы тривиально на самом тупом Berkeley API.

Крайне не рекомендую для начинающих MFCшный класс CAsyncSocket. Именно по этим причинам.
Занимайтесь LoveCraftом, а не WarCraftом!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.