Частичная запись для сокетов в LINUX

#sockets

#сокеты

Вопрос:

У нас есть серверно-клиентская связь в нашем приложении. Сокеты используются для обмена данными. Мы используем сокеты AF_INET с SOCK_STREAM (TCP / IP). Также эти сокеты находятся в неблокирующем режиме (O_NONBLOCK). Приложение написано на C для UNIX.

В нашей системе сервер будет записывать в сокет, а Клиент будет считывать из него. Мы написали код для обработки частичной записи. Если произойдет частичная запись, мы попробуем еще 30 раз записать все данные целиком.

Наш сервер пытается записать 2464 байта в сокет. В некоторых случаях не удавалось записать данные целиком. Таким образом, сервер попытается записать еще 30 раз, чтобы передать все данные. В большинстве случаев все данные будут записаны в течение 30 попыток. Но иногда даже после 30 повторений сервер не сможет записать все данные. Здесь это выдаст ошибку EAGAIN. Проблема возникает на стороне клиента, когда он пытается прочитать эти частично записанные данные.

Рассмотрим, что сервер пытался записать 2464 байта. Но после повторных 30 попыток он смог записать только 1080 байт. В этот момент сервер вызовет EAGAIN. Клиент пытается прочитать 2464 байта. Команда чтения вернет 2464, и, следовательно, само чтение выполнено нормально. Но полученные нами данные повреждены (только частично записанные данные). Итак, клиент выходит из строя.

Может ли кто-нибудь, пожалуйста, посоветовать следующее,

1) Возможно ли удалить только частично записанные данные самим сервером. Таким образом, клиент не получит поврежденные неполные данные?. (Мы не можем использовать функцию read() с сервера, чтобы удалить это. Считайте, что сервер успешно записал n сообщений в сокет. Клиент находится в состоянии занятости и не может их прочитать. Затем сервер попытался записать n 1-е сообщение, и произошла частичная запись. Если мы используем команду чтения с сервера, все n сообщений об успешном завершении будут удалены. Нам нужно удалить только частично исправленное (n 1-е) сообщение)

2) Есть ли какой-либо способ определить на стороне клиента, что мы прочитали частично написанное сообщение?.

Пожалуйста, обратите внимание, что мы сталкиваемся с проблемой частичной записи только в LINUX (REDHAT 5.4). Система в Solaris работает нормально (в solaris либо будут записаны все данные, ЛИБО НИКАКИХ данных не будет записано за 30 попыток записи).

Заранее спасибо.

Ответ №1:

В вашем коде что-то ужасно неправильно.

  • вы должны вызывать write столько раз, сколько необходимо для передачи всех данных, которые вы хотите, я не вижу причин останавливаться после 30 раз

  • если вы используете неблокирующие сокеты, вам, вероятно, следует использовать select() (или poll() или что-нибудь подобное), чтобы получать уведомления, когда вы сможете записать больше данных

  • что-то не так с принимающей стороной — если вы отправили менее 2464, вы не сможете прочитать это количество из клиентского сокета. Проверяете ли вы значение, возвращаемое из read() (т.Е. Количество прочитанных байт)? Опять же, на стороне клиента вы должны использовать select() etc. и вызывать read столько раз, сколько необходимо для получения полного сообщения.

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

1. Я подозреваю, что он сдается после отправки частичных данных и продолжает отправлять другое сообщение, и в этом случае поток TCP более или менее поврежден и бесполезен. Возможно, логика повторных попыток также неверна и не регулирует длины и буферы при частичной записи.

2. Да, может быть. Трудно сказать без более подробной информации, но я считаю, что наши ответы уже должны немного помочь (и да, я снова был слишком медленным ;))

3. Большое вам спасибо за ваши ответы. Реальная проблема в том, что я не могу выполнять запись бесконечно, поскольку есть много других клиентов, ожидающих ответа от сервера.

4. Запись всех данных не должна мешать вам обрабатывать несколько подключений одновременно 🙂 Для каждого соединения вам нужно иметь какой-то контекст — некоторое состояние, объем данных, оставшихся для отправки, и т.д. Затем вам придется бесконечно вызывать select() и на каждой итерации обрабатывать все соединения, которые нуждаются в уходе и подпитке. Если есть соединение, по которому вы должны отправить данные, вам следует повторять запись последовательных фрагментов на каждой итерации, пока не будут отправлены все данные.

Ответ №2:

То, что вы видите, является нормальным поведением для неблокирующего сокета. Когда буферы (как локальные, так и удаленные) заполняются, вы получаете частичную запись.

Вы не должны сдаваться после 30 попыток, которые приводят к EAGAIN / EWOULDBLOCK, но продолжайте пытаться. Вы должны использовать select () / poll () или что-то подобное, чтобы получать уведомления, когда вы можете возобновить запись, или вам следует просто использовать блокирующие вызовы. То, что вы видите разные результаты в Solaris и RHEL, — это просто (не) удача.

  1. Нет, в таком случае вам пришлось бы закрыть соединение и попросить клиента обработать частичные данные.

  2. Нет, пока вы не закроете TCP-соединение. (*)

Если вы всегда отправляете сообщения размером 2464 байта, вероятно, все в порядке, но имейте в виду, что TCP — это поток, он не обрабатывает «сообщения»

(*) Технически существует много способов, но они требуют существенных усилий. например, вы могли бы самостоятельно реализовать фреймы, подобные HDLC, поверх TCP, где сообщения разделяются специальным битовым шаблоном. Пользовательские данные должны были бы экранироваться (заполнение битом), чтобы не содержать этот специальный битовый шаблон