20:59
Один казус WinSock
Эта библиотека пытается быть совместимой с т.н. "сокетами Беркли", поэтому половина функций названы по прототипам 80-х годов. А что не влезло -- по современным. Это создаёт путаницу. Тогда ещё не было методологии именования функций, в т.ч. системных, поэтому функции приёма и передачи данных называются просто recv и send. А что в программе могут быть другие функции отправки и приёма другими способами -- никого не волнует.
recv просто так не вернёт вам управление, пока не получит данные или пока не сработает таймаут. Да, у меня на приём отдельный поток, но всё равно я хочу иногда чем-нибудь ещё заниматься, а не только ждать, пока мне пришлют запрос. Как же проверить, сколько байт пришло?
А для этого есть специальный флаговый параметр, который должен быть установлен в MSG_PEEK. Тогда мы сразу получим то, что уже готово.
Функция возвращает число типа int. Оно обозначает количество принятых байт. Если нам пока ничего не пришло, сколько байт вернётся? Наверное, ноль.
Но нет. Ноль зарезервирован для случая отключения второй стороны/ошибки связи. А сколько должна вернуть функция, если ничего не пришло, в документации Microsoft не указано!
Поэтому я поставил эксперимент и обнаружил, что в случае, если данные не пришли, recv возвращает -1. Это оказалось неожиданно.
recv просто так не вернёт вам управление, пока не получит данные или пока не сработает таймаут. Да, у меня на приём отдельный поток, но всё равно я хочу иногда чем-нибудь ещё заниматься, а не только ждать, пока мне пришлют запрос. Как же проверить, сколько байт пришло?
А для этого есть специальный флаговый параметр, который должен быть установлен в MSG_PEEK. Тогда мы сразу получим то, что уже готово.
Функция возвращает число типа int. Оно обозначает количество принятых байт. Если нам пока ничего не пришло, сколько байт вернётся? Наверное, ноль.
Но нет. Ноль зарезервирован для случая отключения второй стороны/ошибки связи. А сколько должна вернуть функция, если ничего не пришло, в документации Microsoft не указано!
Поэтому я поставил эксперимент и обнаружил, что в случае, если данные не пришли, recv возвращает -1. Это оказалось неожиданно.
Пожалуйста, ознакомьтесь с комментариями!
24.11.2020 в 12:00
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() )
24.11.2020 в 12:34
24.11.2020 в 16:00
А я был уверен что tcp передаёт непрерывный поток байт и вот такая постановка вопроса в принципе не верна:
> Вот если у тебя сообщение больше этого буфера, ты его одним махом никогда не получишь.
Это же как читать файл или пайп. ХЗ сколько там ещё байт, никто ничего не гарантирует.
24.11.2020 в 17:12
Вот ещё полезная ссылка.
24.11.2020 в 18:36
25.11.2020 в 16:51
25.11.2020 в 19:24
25.11.2020 в 19:32
25.11.2020 в 21:10