Передача сообщений TCP/IP

Настройка, программирование, распределённые вычисления

Модераторы: Olej, bellic, vikos

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Передача сообщений TCP/IP

Непрочитанное сообщение Olej » 04 фев 2013, 15:58

Эта тема переползла сюда из обсуждений модулей ядра, модифицирующих сетевой протокол: Виртуальный сетевой интерфейс.

Исходные данные:
mcrandy писал(а): У меня есть своя клиент-серверная программка организующая передачу рандомного сообщения заданной длины. В ней используются стандартные функции send() и recv() для передачи/приема соответственно. Используется протокол TCP. Она осложнена только тем что в начало сообщения записывает длину этого сообщения которое собирается передать, чтобы сервер знал сообщение какой длины ему ожидать. Программку прикрепляю к сообщению. Внутри есть небольшой README.
Тестовые программы (клиент и сервер) вот они прикреплены к исходной теме: test_tcp.tar

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Передача сообщений TCP/IP

Непрочитанное сообщение Olej » 04 фев 2013, 16:28

Olej писал(а):Тестовые программы (клиент и сервер) вот они прикреплены к исходной теме: test_tcp.tar
P.S. Ув. mcrandy, прежде совершенно формальные замечания (или даже скорее пожелания) по исходным текстам:

1-е замечание:
- не готовьте исходные тексты в Windows, предназначенные для выполнения в Linux...
- эти виндоузные двухсимвольные переводы строки (0D 0A) - задолбывают ;-)
- но не это худшее - ваши русскоязычные комментарии не читаются (одни в CP-1251 - другие в UTF-8)
- но если уж вам так удобно (в Windows), то делаете это хотя бы единообразно, а то у вас server.c - в CP-1251, а client.c - в UTF-8 :-o

2-е замечание:

Код: Выделить всё

#define PORT 33112              
#define IP_ADDRESS "192.168.52.189"     
#define MESS_LEN 1024
- такие вещи как адрес сервера, порт, длину сообщения ... и т.д. (т.е. "вариабельные" параметры) лучше сделать параметрами запуска приложения, а не зашитыми константами... которые требуют каждый раз перекомпиляции :-(
- а ещё лучше - опциями (ключами) приложения (имеется в виду client.c), так как это принято в приложениях GNU и стандартах POSIX - используя вызов optarg() ...
- а чтоб сильно не заморачиваться с разбирательством "как?" - можете взять за основу готовый код вот отсюда: Linux-инструменты для Windows-программистов (ну, и можете предусмотреть "дефаултные" значения переменных при отсутствии опций - те самые константы).
- при такой конструкции работа с приложением становится куда быстрее (с экспериментированием и нахождением каких-то ненормальностей в поведении - это особенно актуально бывает для клиент-серверных приложений).

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Передача сообщений TCP/IP

Непрочитанное сообщение Olej » 04 фев 2013, 17:44

Olej писал(а):
mcrandy писал(а): У меня есть своя клиент-серверная программка организующая передачу рандомного сообщения заданной длины. В ней используются стандартные функции send() и recv() для передачи/приема соответственно. Используется протокол TCP. Она осложнена только тем что в начало сообщения записывает длину этого сообщения которое собирается передать, чтобы сервер знал сообщение какой длины ему ожидать. Программку прикрепляю к сообщению. Внутри есть небольшой README.
Сомнения вызывают выделенные мной в цитате слова:

В TCP нет "сообщений" (а ещё более вводит в заблуждение когда говорят "пакет") - это есть в UDP.
Сокет TCP - это поток, stream ... это "труба", в которую в один конец вливается, а из другого выливается ;-)

Очень часто неявно предполагают, что если, например, клиент передаёт последовательность сообщений фиксированной длины:
... 10, 10, 10, 10, ... и т.д.
то и на приёмной стороне (сервер) будет такая же последовательность длин принимаемых recv():

Код: Выделить всё

while(...) {
   len = recv( sock, ptr, nrd, MSG_NOSIGNAL );
...
}
А это принципиально не так!
На приёмном конце могут появляться len ("пакеты") хоть такой длины:
... 10, 5, 5, 10, ...
хоть такой:
... 10, 20, 20, 10 ...
хоть даже такой
... 10, 13, 17, 10, ...

А это значит, что сами "сообщения" в потоке TCP можете разграничивать только вы сами.
И сделать это можно только 2-мя способами (я только эти знаю), точно так же как с текстовыми строками (char* и string ... то, как это делается в языках C и PASCAL):
1. завершая сообщение фиксированным разделителем ('\0', например, или -1, или ...);
2. указывая в сообщении (в голове) длину последующего тела сообщения.

Так что наличие длины в голове сообщения не "осложняет" приём сообщений по TCP, а единственно только и делает их возможным!

Но в любом случае обратите внимание на то, что:
- после того как вы выгребете текущее сообщение из буфера приёма...
- там в хвосте, за выбранным сообщением, может быть ещё одно полное сообщение...
- или может быть только несколько начальных байт следующего сообщения...
- и если вы их отбросите, то вы потеряете синхронизацию с сообщениями (с их разметкой) и уже не сможете больше принимать последующие сообщения ... считайте, что ваш сервер "завис".

Эти эффекты возникают из-за весьма сложной алгоритмики TCP, таких механизмов как:
- алгоритм Нэйгла;
- отсроченные подтверждения;
- "медленный старт";
- адаптивное изменение размеров окон передачи и прийма;
... там довольно много таких механизмов, связано это с оптимизацией.

Огромной неприятностью (говорю это по опыту завершённых и внедрённых проектов) таких эффектов есть то, что:
- они практически почти не проявляются в LAN (там оптимизировать особенно нечего), поэтому их не видно при отладке ... "похоже что всё работает нормально" - но это до поры до времени... :-?
- а в реальной установленной системе они проявляются тоже с малыми вероятностями ... я реально ловил 10**-9 (1 прокол на несколько сот миллионов посылок)...
- и выглядит тогда это так: система работает ... сутки, двое ... а потом "неожиданно" валится.

Поэтому такие вещи нужно не отлаживать, а сразу писать код понимая что и как будет происходить.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Передача сообщений TCP/IP

Непрочитанное сообщение Olej » 04 фев 2013, 17:58

Olej писал(а):Поэтому такие вещи нужно не отлаживать, а сразу писать код понимая что и как будет происходить.
Всё это замечательно и с примерами описано в книгах, вот в этих:

У. Ричард Стивенс "UNIX: разработка сетевых приложений"
Изображение

У.Стивенс,Б.Феннер,Э.Рудофф "UNIX. Разработка сетевых приложений"
(новое, свежее издание предыдущей книги, через 20 лет после предыдущего издания)
Изображение

Й. Снейдер "Эффективное программирование TCP/IP. Библиотека программиста"
Изображение
(эта книга показывает и сравнение работы с TCP/IP стеком в UNIX и в Windows, и в их взаимообмене ... а учитывая, что TCP/IP стек WIndows сделан через задницу - это актуально)

Эти книги много раз выложены в Интернет, так что, если не имеете желание их покупать, можете найти и скачать.
Но эти книги хорошо бы иметь в качестве настольного справочника, всегда лежащего под рукой.

P.S. Более того, я сказал бы так: не читайте никаких других источников по программированию TCP/IP, кроме этих - и тогда будет вам счастье. ;-)

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Передача сообщений TCP/IP

Непрочитанное сообщение Olej » 04 фев 2013, 18:23

Olej писал(а): В TCP нет "сообщений" (а ещё более вводит в заблуждение когда говорят "пакет") - это есть в UDP.
И для разрешения и таких, в частности противоречий (и других), к TCP и UDP добавили через 10 лет ещё один протокол SCTP.
Stream Control Transmission Protocol — «протокол передачи с управлением потоком»
В противоположность им протокол TCP обрабатывает неструктурированный поток байт. Если не использовать процедуру формирования кадров сообщения, то узел сети может получать данные по размеру больше или меньше отправленных. Такой режим функционирования требует, чтобы для протоколов, ориентированных на работу с сообщениями и функционирующих поверх протокола TCP, на прикладном уровне был предоставлен специальный буфер данных и выполнялась процедура формирования кадров сообщений (что потенциально является сложной задачей).
Протокол SCTP обеспечивает формирование кадров при передаче данных. Когда узел выполняет запись в сокет, его корреспондент с гарантией получает блок данных того же размера.
Важные вещи я выделил.

Вот здесь есть полные примеры кода клиента и сервера SCTP: Надежная передача данных по протоколу SCTP.

Там же:
Протокол SCTP является сравнительно новым протоколом, учитывая то, что он был представлен в виде RFC в октябре 2000 года. С этого момента он начал использоваться во многих операционных системах, включая GNU/Linux, BSD и Solaris. В виде дополнительного коммерческого пакета независимого поставщика он также доступен для операционной системы Microsoft® Windows®.
...
После включения протокола SCTP в ядро Linux 2.6 стало возможным построение и развертывание надежных сетевых приложений высокой готовности. Основываясь на протоколе IP, SCTP позволяет прозрачно заменить протоколы TCP и UDP, введя при этом дополнительные возможности: множественную адресацию, многопоточную передачу и повышенную безопасность.
...

mcrandy
Активист
Сообщения: 18
Зарегистрирован: 21 янв 2013, 12:51
Контактная информация:

Re: Передача сообщений TCP/IP

Непрочитанное сообщение mcrandy » 07 фев 2013, 13:44

1-е замечание:
- не готовьте исходные тексты в Windows, предназначенные для выполнения в Linux...
- эти виндоузные двухсимвольные переводы строки (0D 0A) - задолбывают
- но не это худшее - ваши русскоязычные комментарии не читаются (одни в CP-1251 - другие в UTF-8)
- но если уж вам так удобно (в Windows), то делаете это хотя бы единообразно, а то у вас server.c - в CP-1251, а client.c - в UTF-8
Согласен, в исходных кодах произошел полный бардак из-за множественного копирования файлов то в windows, то в Linux. Учту...
2-е замечание:

Код: Выделить всё

#define PORT 33112              
#define IP_ADDRESS "192.168.52.189"     
#define MESS_LEN 1024
- такие вещи как адрес сервера, порт, длину сообщения ... и т.д. (т.е. "вариабельные" параметры) лучше сделать параметрами запуска приложения, а не зашитыми константами... которые требуют каждый раз перекомпиляции
- а ещё лучше - опциями (ключами) приложения (имеется в виду client.c), так как это принято в приложениях GNU и стандартах POSIX - используя вызов optarg() ...
- а чтоб сильно не заморачиваться с разбирательством "как?" - можете взять за основу готовый код вот отсюда: Linux-инструменты для Windows-программистов (ну, и можете предусмотреть "дефаултные" значения переменных при отсутствии опций - те самые константы).
- при такой конструкции работа с приложением становится куда быстрее (с экспериментированием и нахождением каких-то ненормальностей в поведении - это особенно актуально бывает для клиент-серверных приложений).
Когда писал тест, не хотел тратить на него много времени, а опыта по созданию опций приложения нет, вот и не стал тогда разбираться. Воспользуюсь вашим советом.

mcrandy
Активист
Сообщения: 18
Зарегистрирован: 21 янв 2013, 12:51
Контактная информация:

Re: Передача сообщений TCP/IP

Непрочитанное сообщение mcrandy » 07 фев 2013, 13:53

Эти книги много раз выложены в Интернет, так что, если не имеете желание их покупать, можете найти и скачать.
Но эти книги хорошо бы иметь в качестве настольного справочника, всегда лежащего под рукой.

P.S. Более того, я сказал бы так: не читайте никаких других источников по программированию TCP/IP, кроме этих - и тогда будет вам счастье.
Отлично! Правильные книги я люблю :-)
Раньше, когда я только начинал знакомиться с UNIX-системами я думал что всю необходимую информацию смогу найти в интернете, но как только мне в руки попали несколько книг по UNIX, я понял что был не прав.
Во-первых не всегда можно быть уверенным в информации из интернета.
Во-вторых в книге за частую все подробнее и понятнее описано.
К счастью, у меня есть возможность заказывать книги за счет организации. Обязательно закажу их при следующей возможности

mcrandy
Активист
Сообщения: 18
Зарегистрирован: 21 янв 2013, 12:51
Контактная информация:

Re: Передача сообщений TCP/IP

Непрочитанное сообщение mcrandy » 07 фев 2013, 14:52

Очень часто неявно предполагают, что если, например, клиент передаёт последовательность сообщений фиксированной длины:
... 10, 10, 10, 10, ... и т.д.
то и на приёмной стороне (сервер) будет такая же последовательность длин принимаемых recv()
Не понимаю о чем идет речь... :-?
Вы имеете ввиду что есть я отправляю 10 сообщений подряд длиной 15 каждое, например, то при приеме они могут превратиться в 5 сообщений длиной 30?
Или я вообще глупость сказал... :oops:
А это значит, что сами "сообщения" в потоке TCP можете разграничивать только вы сами.
И сделать это можно только 2-мя способами (я только эти знаю), точно так же как с текстовыми строками (char* и string ... то, как это делается в языках C и PASCAL):
1. завершая сообщение фиксированным разделителем ('\0', например, или -1, или ...);
2. указывая в сообщении (в голове) длину последующего тела сообщения.

Так что наличие длины в голове сообщения не "осложняет" приём сообщений по TCP, а единственно только и делает их возможным!
Я использовал не правильную фразу "сообщения заданной длины". В client.c осуществляется передача по второму способу. В начало сообщения записывается длина последующего тела сообщения.
Это реализовано в функциях, которые я позаимствовал:

Код: Выделить всё

static int _send(int sock, const unsigned char *buf, int size);
int ucnet_send(int sock, const unsigned char *buf, int size);
Запись длины сообщения в начало самого сообщения:

Код: Выделить всё

*(int*) msg = htonl(size);
memcpy(msg + sizeof(int), buf, size);

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Передача сообщений TCP/IP

Непрочитанное сообщение Olej » 07 фев 2013, 16:09

mcrandy писал(а):
Очень часто неявно предполагают, что если, например, клиент передаёт последовательность сообщений фиксированной длины:
... 10, 10, 10, 10, ... и т.д.
то и на приёмной стороне (сервер) будет такая же последовательность длин принимаемых recv()
Не понимаю о чем идет речь... :-?
Вы имеете ввиду что есть я отправляю 10 сообщений подряд длиной 15 каждое, например, то при приеме они могут превратиться в 5 сообщений длиной 30?
Или я вообще глупость сказал... :oops:
Нет, вы сказали как-раз разумно и именно то ;-)
Я это и имел в виду (но, наверное, плохо сформулировал), что если вы "отправляете 10 сообщений подряд длиной 15 каждое", то:
- вы можете на приёмной стороне получить 5 сообщений длиной 30
- и могут быть в последовательности принятых сообщения короче ... по 5 байт, условно говоря
- и могут быть просто некратно побитые посреди "сообщений" ... по 23 байта.

Здесь главное то, что в TCP нет никаких "сообщений" - это поток, нельзя делать никаких предположений, какими "порциями" будет вам отдавать данные функция приёма.

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

Сообщения внутри потока TCP вы можете организовать (размежевать) только своими собственными средствами пользовательского уровня (что вы и делаете в примере).

P.S. Первоначально (да и сейчас отчасти), большинство протоколов прикладного уровня были ориентированы на передачу символьных потоков (текстовых данных). HTTP тому самый яркий пример, когда посылаются запросы GET или POST... и в ответ получается опять же текст в формате HTML. И в символьном потоке сообщений GET/POST ловится последовательность двух '\n', т.е. полностью пустая строка, что и есть разделителем, означающим конец "сообщения". Тот же разделитель '\n\n' используют многие протоколы IP прикладного уровня ... тот же SIP (если не перепутал) в IP телефонии, например.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: Передача сообщений TCP/IP

Непрочитанное сообщение Olej » 07 фев 2013, 16:27

mcrandy писал(а): Я использовал не правильную фразу "сообщения заданной длины". В client.c осуществляется передача по второму способу. В начало сообщения записывается длина последующего тела сообщения.
Это реализовано в функциях, которые я позаимствовал:

Код: Выделить всё

static int _send(int sock, const unsigned char *buf, int size);
int ucnet_send(int sock, const unsigned char *buf, int size);
Запись длины сообщения в начало самого сообщения:

Код: Выделить всё

*(int*) msg = htonl(size);
memcpy(msg + sizeof(int), buf, size);
Я это всё видел - я достаточно внимательно посмотрел код ваших примеров.
Передача TCP вообще не интересна, вопросы могут быть только к приёму...
Мне сначала показалось, что сомнения вызывает чтение начальных 4-х байт длины (нет никакой абсолютной гарантии, что recv() вернёт обязательно все 4 байта, может и 1, 2, 3), но потом убедился, что и поле длины у вас "выгребается" побайтно _recv().
Так что всё более-менее нормально.

Недостаток способа: длина + поле указанной длины байт (2-й способ, как вы его назвали) состоит в том, что:
- клиент-серверные системы обычно предназаначены работать долго, на многих миллионах последовательных посылок...
- но если при таком способе вы каким-то образом рассинхронизируетесь хоть на 1 байт с началов ваших сообщений - то вы уже никогда больше не восстановите работу вашей системы без перезагрузки.

Как вариант, может быть комбинированный метод:
- в начале сообщения идёт какой-то фиксированный характерный (не 0 и не FF) разделитель из 2-3-4-х характерных байт (b01010101, b10101010, ...), чередующихся между собой...
- а дальше может быть 4 байта длины последующего тела сообщения - это облегчает чтение этого тела (как у вас в коде)
- при приёме очередного сообщения вы сначала проверяете разделитель в его голове...
- в такой структуре если вы каким-то образом и "сдвинитесь", потеряете синхронизм - то всегда восстановите его чтением с поиском последующего разделителя ... потеряв при этом всего 1 сообщение.

Ответить

Вернуться в «Сети»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 13 гостей