Tcp send и recv: всегда в петлях?

Каковы наилучшие методы отправки (или записи) и получения (или чтения) в/из сокета TCP?

Предположим, обычный блокирующий ввод-вывод на сокетах. Из того, что я понимаю:

  • запись (отправка) должна быть в порядке без цикла, потому что она будет блокироваться, если буфер записи сокета заполнен, поэтому что-то вроде
    if ((nbytes_w = write(sock, buf, nb)) < nb)
         /* something bad happened : error or interrupted by signal */
    
    всегда должно быть правильно?
  • с другой стороны, нет гарантии, что сообщение будет прочитано полностью, поэтому читать следует с
    while ((nbytes_r = read(sock, buf, MAX)) > 0) {
        /* do something with these bytes */
        /* break if encounter specific application protocol end of message flag
           or total number of bytes was known from previous message
           and/or application protocol header */
    } 
    
    

Я прав ? Или есть какой-то «маленький размер сообщения» или другие условия, позволяющие безопасно читать вне цикла?

Я в замешательстве, потому что я видел примеры «голого чтения», например, в Tanenbaum-Wetherall:

read(sa, buf, BUF_SIZE); /* read file name in socket */

Для достаточно малых буферов люди склонны предполагать, что шансы на получение пакета в нескольких фрагментах слишком малы, чтобы о них беспокоиться. Не будьте одним из них. ;)

Gerhardh 19.03.2022 18:41

Проверьте этот ответ, чтобы отправить/написать: stackoverflow.com/a/14399717/1076479. Подводя итог, можно сказать, что большинство/все реализации блокируют будем при отправке, но ни одна из спецификаций реализаций на самом деле не блокирует гарантия. Поскольку не будет ошибкой, если реализация вернет короткую запись, лучше всего предположить, что это может произойти. А так как реализовать цикл несложно, то стоит просто сделать. Это даст вам максимальную переносимость в отношении как платформ, так и транспортных механизмов.

Gil Hamilton 19.03.2022 21:05

И, кстати, в вашем заявлении write выше отсутствуют скобки, необходимые для выполнения того, что вы намереваетесь. Так должно быть if ((nbytes_w = write(sock, buf, nb)) < nb)

Gil Hamilton 19.03.2022 21:06

@GilHamilton Спасибо за опечатку, она отредактирована. Спасибо. Что касается записи в циклах, я думаю, вы правы, но я все еще не понимаю. Зачем вообще внедрять блокировку записи, если вы не можете на нее рассчитывать?

Villetaneuse 20.03.2022 18:01

Блокирующая запись по существу гарантирует, что в системном вызове произойдет прогресс некоторый (или будет возвращена ошибка). Альтернатива неблокирующей записи привела бы к тому, что ваша программа (потенциально) вращалась бы, тратя впустую циклы ЦП и ничего не выполняя, пока не будет достигнут прогресс. Следовательно, это делает парадигму программирования намного проще: нет необходимости использовать select или другие сложные механизмы для реализации простого последовательного процесса.

Gil Hamilton 20.03.2022 21:36

Разработчику приложений было бы бы проще, если бы ОС гарантировала, что весь send будет завершен до возврата. И, на практике, вы, вероятно, можете рассчитывать на это. Я думаю, что это не гарантировано, потому что первоначальные разработчики не хотели ограничивать будущие реализации. (Кроме того, IMO это согласуется с общей философией ОС: рассмотрите попытку записи блока 16G в файл на диске, но диск был заполнен во время записи. Естественный способ справиться с этим - вернуть успешно записанные байты. Затем вызывающий может повторить попытку напишите с оставшимися байтами, чтобы обнаружить ошибку.)

Gil Hamilton 20.03.2022 21:48
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
63
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Да, вы должны зацикливаться на приеме

Раз в неделю я отвечаю на вопрос, где именно по этой причине перестает работать чье-то приложение TCP. Настоящим убийцей является то, что они разработали клиент и сервер на одной машине, поэтому они получают петлевое соединение. Почти все время петля будет получать сообщения отправки в тех же блоках, в которых они были отправлены. Это делает код правильным.

Действительно большая проблема заключается в том, что это означает, что вам нужно знать перед циклом, насколько велико сообщение, которое вы собираетесь получить. Возможности

  • сначала отправьте фиксированную длину (т.е. вы знаете ее, скажем, 4 байта).
  • иметь узнаваемую конечную последовательность (например, двойной crlf в конце HTTP-запроса.
  • Сообщение фиксированного размера

У меня всегда была бы функция «вытащить следующие n байтов».

Написание тоже должно зацикливаться, но это просто, это просто вопрос зацикливания.

Вы также должны зациклить посыл. send() может принимать меньше байтов, чем запрошено, точно так же, как recv() может возвращать меньше байтов, чем запрошено. Обе функции возвращают фактическое количество принятых/полученных байтов. Обратите внимание на это значение.

Remy Lebeau 19.03.2022 21:01

«двойной crlf в конце HTTP-запроса» — HTTP-сообщения не заканчиваются двойным crlf. Только ЗАГОЛОВКИ есть, а ТЕЛА нет.

Remy Lebeau 19.03.2022 21:02

@pm100 Большое спасибо. Не могли бы вы дать ссылки (RFC или справочные страницы) о написании необходимых циклов? Мне не имеет смысла реализовывать блокировку записи, если нет гарантии, что все будет записано. Это нормально в системах Unix?

Villetaneuse 20.03.2022 18:06

@Villetaneuse man7.org/linux/man-pages/man2/write.2.html — прочитать комментарии «возвращаемое значение»

pm100 20.03.2022 18:29

@pm100 Спасибо. Но тогда примеры незавершенной записи на этой справочной странице очень специфичны. При записи в сокет это может происходить только при прерывании сигналом? Извините, если это выглядит педантично, но я хотел бы добраться до сути.

Villetaneuse 21.03.2022 10:03

@Villetaneuse проверьте read_n и write_n здесь github.com/fpagliughi/sockpp/blob/master/src/stream_socket.c‌​pp

pm100 21.03.2022 21:35

Другие вопросы по теме