zHz00 Untitled

понедельник, 23 ноября 2020
20:59 Один казус WinSock
Эта библиотека пытается быть совместимой с т.н. "сокетами Беркли", поэтому половина функций названы по прототипам 80-х годов. А что не влезло -- по современным. Это создаёт путаницу. Тогда ещё не было методологии именования функций, в т.ч. системных, поэтому функции приёма и передачи данных называются просто recv и send. А что в программе могут быть другие функции отправки и приёма другими способами -- никого не волнует.

recv просто так не вернёт вам управление, пока не получит данные или пока не сработает таймаут. Да, у меня на приём отдельный поток, но всё равно я хочу иногда чем-нибудь ещё заниматься, а не только ждать, пока мне пришлют запрос. Как же проверить, сколько байт пришло?

А для этого есть специальный флаговый параметр, который должен быть установлен в MSG_PEEK. Тогда мы сразу получим то, что уже готово.

Функция возвращает число типа int. Оно обозначает количество принятых байт. Если нам пока ничего не пришло, сколько байт вернётся? Наверное, ноль.

Но нет. Ноль зарезервирован для случая отключения второй стороны/ошибки связи. А сколько должна вернуть функция, если ничего не пришло, в документации Microsoft не указано!

Поэтому я поставил эксперимент и обнаружил, что в случае, если данные не пришли, recv возвращает -1. Это оказалось неожиданно.

Пожалуйста, ознакомьтесь с комментариями!

@темы: Программирование, Борьба с техникой

URL

24.11.2020 в 12:00

24.11.2020 в 12:00
Всё там прекрасно указано!
If no error occurs, recv returns the number of bytes received and the buffer pointed to by the buf parameter will contain this data received. If the connection has been gracefully closed, the return value is zero.
Otherwise, a value of SOCKET_ERROR is returned, and a specific error code can be retrieved by calling WSAGetLastError.

SOCKET_ERROR это -1. Так что ты любую ошибку воспримешь как "нет новых данных". Различить можно по WSAGetLastError() и скорее всего, это будет WSAEWOULDBLOCK.

А вообще,
> Как же проверить, сколько байт пришло? А для этого есть специальный флаговый параметр
Для этого есть ioctlsocket(FIONREAD), но и он, и PEEK, и select() в лучшем случае гарантируют тебе "что-то пришло", а не точный объём данных.

Там всё устроено примерно так. У TCP есть внутри небольшой буфер, куда он складывает прилетающие байты. И он сообщает отправителю, сколько он ещё готов принимать. Когда буфер заполняется, он говорит стопэ, и отправитель подвисает в своём блокирующем send().
Вот если у тебя сообщение больше этого буфера, ты его одним махом никогда не получишь.

Только два способа разбирать TCP надёжно:
1. Создать буфер нужного размера, передать его в winsock и смиренно ждать, когда его заполнят. (винсок сам будет вынимать байты в твой буфер по мере поступления)
2. Немедленно вынимать из recv() всё, что там становится доступным, и класть в свой собственный буфер, который уже разбирать как тебе нравится.

Второе сделать можно кучей способов: peek + recv (неэффективно: читаешь дважды), FIONREAD+recv (норм), select+FIONREAD+recv (чтобы эффективно спать, пока ничего нет), включить неблокирующие сокеты и пробовать время от времени (плохо), включить неблокирующие сокеты и спать на эвенте, включить неблокирующие сокеты и получать оконные события, включить неблокирующие сокеты и делать overlapped (ты запустил чтение и тебе скажут, когда оно сделалось).

Из всего этого overlapped это самое эффективное, поскольку ты по сути пульнул заявку в режим ядра и ушёл по своим делам, но оно нужно только при сотнях подключений. А самое простое это висеть в recv намертво, если у тебя отдельный поток, а когда поток надо завершать, то сказать ему снаружи crash and burn ( closesocket() + WSACancelBlockingCall() )
URL

24.11.2020 в 12:34

24.11.2020 в 12:34
himself, ультимативный гайд, спасибо!
URL

24.11.2020 в 16:00

24.11.2020 в 16:00
Мммм, сколько здесь знатоков... =)

А я был уверен что tcp передаёт непрерывный поток байт и вот такая постановка вопроса в принципе не верна:
> Вот если у тебя сообщение больше этого буфера, ты его одним махом никогда не получишь.

Это же как читать файл или пайп. ХЗ сколько там ещё байт, никто ничего не гарантирует.
URL

24.11.2020 в 17:12

24.11.2020 в 17:12
Xersareeth, рад, что помог узнать что-то новое :)

Вот ещё полезная ссылка.
URL

24.11.2020 в 18:36

24.11.2020 в 18:36
Эээ, а как tcp может передавать непрерывный поток байт, если там при этом идет проверка контрольных сумм и подтверждения доставки?)
URL

25.11.2020 в 16:51

25.11.2020 в 16:51
deadlymercury, вероятно точно так же, как в файлах может храниться непрерывный поток байт, хотя там кластеры, секторы и чёрти-чо ещё, в том числе и проверка контрольных сумм =)
URL

25.11.2020 в 19:24

25.11.2020 в 19:24
Так tcp это же гораздо более низкоуровневая штука, чем "файл"?)
URL

25.11.2020 в 19:32

25.11.2020 в 19:32
Дыа? И на каком же уровне модели osi находятся файлы?)
URL

25.11.2020 в 21:10

25.11.2020 в 21:10
где-то над фс, который при этом тоже менее низкоуровневая штука, чем tcp ;) Хотя при этом tcp более высокоуровневая штука, чем сектора на диске.
URL