TCP/IP с использованием Ada Sockets: как правильно завершить пакет?

Я пытаюсь реализовать протокол удаленного буфера кадров, используя библиотеку Ada Sockets, и у меня возникают проблемы с контролем длины отправляемых пакетов.

Я следую спецификации RFC 6143 (https://tools.ietf.org/pdf/rfc6143.pdf), см. комментарии в коде для номеров разделов...

          --  Section 7.1.1
          String'Write (Comms, Protocol_Version);
          Put_Line ("Server version: '"
            & Protocol_Version (1 .. 11) & "'");

          String'Read (Comms, Client_Version);
          Put_Line ("Client version: '"
            & Client_Version (1 .. 11) & "'");

          --  Section 7.1.2
          --  Server sends security types
          U8'Write (Comms, Number_Of_Security_Types);
          U8'Write (Comms, Security_Type_None);


          --  client replies by selecting a security type
          U8'Read (Comms, Client_Requested_Security_Type);
          Put_Line ("Client requested security type: "
            & Client_Requested_Security_Type'Image);

          --  Section 7.1.3
          U32'Write (Comms, Byte_Reverse (Security_Result));

          --  Section 7.3.1
          U8'Read (Comms, Client_Requested_Shared_Flag);
          Put_Line ("Client requested shared flag: "
            & Client_Requested_Shared_Flag'Image);


          Server_Init'Write (Comms, Server_Init_Rec);

Кажется, проблема (согласно wireshark) в том, что мои вызовы различных процедур 'Write заставляют байты стоять в очереди в сокете без отправки.

Следовательно, два или более пакета данных отправляются как один, что приводит к искажению пакетов. Разделы 7.1.2 и 7.1.3 передаются последовательно в одном пакете, а не разбиваются на два.

Я ошибочно предположил, что 'Reading из сокета приведет к сбросу исходящих данных, но, похоже, это не так.

Как мне сообщить библиотеке Ada Sockets, что «этот пакет готов, отправьте его прямо сейчас»?


person Community    schedule 13.10.2019    source источник
comment
Отключите алгоритм Нэгла, но в целом ваши ожидания неуместны. TCP — это протокол потока байтов, а не протокол сообщений. Если вам нужны сообщения, вы должны реализовать их самостоятельно.   -  person user207421    schedule 13.10.2019
comment
Можете ли вы подтвердить, что длина строки Client_Version после чтения действительно равна 12? И укажите, какое значение имеет Client_Requested_Security_Type после того, как вы прочитали его из потока?   -  person DeeDee    schedule 13.10.2019
comment
@DeeDee, да, Wireshark подтверждает, что это ровно 12 байтов, включая «\ n» (все это четко видно в шестнадцатеричном дампе). Клиент отвечает аналогичным сообщением, также правильно отформатированным. При постановке в очередь нескольких 'Write вызовов все идет не так.   -  person    schedule 13.10.2019
comment
Нет, все идет не так, потому что вы читаете неправильно. Вы не можете читать, предполагая, что одно единственное чтение даст вам один полный пакет протокола. Он может дать вам что угодно, от одного байта до длины предоставленного вами буфера, и эти данные могут состоять из доли пакета или нескольких, или того и другого. Вы должны справиться со всем этим в конце чтения. Ничто из того, что вы можете сделать на отправляющей стороне, не может обойти это требование.   -  person user207421    schedule 13.10.2019
comment
@user207421. Спасибо, кажется, теперь я понял. Мне нужно создать свои собственные буферы символов, достаточно большие для любого сообщения, и вручную читать (или записывать) последовательные байты в цикле, пока не будет передано нужное количество символов. Если бы я писал это в коде C, я бы все равно инстинктивно делал такие вещи. Пакет Ada g-socket.ads содержит прекрасный рабочий пример того, как использовать TCP и UDP, но, к сожалению, не упоминает, что эти буферы должны поддерживаться сложным способом. Это говорит о том, что String'Read (stream, str_buffer) Ады БЛОКИРУЕТСЯ, пока буфер не заполнится!   -  person    schedule 13.10.2019
comment
@user207421 user207421, Похоже, все 'Read вызовы блокируются. Я не думаю, что это дублирующий вопрос, потому что я думаю, что библиотеки Ады выполняют блокировку буферизации от имени программиста. 'Read вызовы блокируются для строк фиксированной длины и для скалярных типов известной длины.   -  person    schedule 13.10.2019


Ответы (1)


Чтобы выделить комментарий https://stackoverflow.com/users/207421/user207421:

Я не гуру протоколов, но по моему собственному опыту использование TCP (см. RFC793) часто понимают неправильно.

Проблема, по-видимому, заключается (согласно wireshark) в том, что мои вызовы различных процедур записи заставляют байты стоять в очереди в сокете без отправки.

Следовательно, два или более пакета данных отправляются как один, что приводит к искажению пакетов. Разделы 7.1.2 и 7.1.3 передаются последовательно в одном пакете, а не разбиваются на два.

Короче говоря, TCP не ориентирован на сообщения.

При использовании TCP отправка/запись в сокет приводит только к добавлению данных в поток TCP. Сокет может свободно отправлять его за один обмен или несколько, и если у вас есть длинные данные для отправки и протокол, ориентированный на сообщения, для реализации поверх TCP, вам может потребоваться обработка восстановления сообщения. Обычно в конце сообщения добавляется специальная последовательность символов конца сообщения.

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

См. также https://stackoverflow.com/a/11237634/7237062, цитируя:

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

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

или Похоже, TCP-соединение получает неполные данные, цитируя:

Функция recv может получить всего 1 байт, возможно, вам придется вызывать ее несколько раз, чтобы получить всю полезную нагрузку. Из-за этого вам нужно знать, сколько данных вы ожидаете. Хотя вы можете сигнализировать о завершении, закрыв соединение, это не очень хорошая идея.

Обновлять:

Я также должен упомянуть, что функция отправки имеет те же соглашения, что и recv: вы должны вызывать ее в цикле, потому что вы не можете предположить, что она отправит все ваши данные. Хотя это всегда может работать в вашей среде разработки, это предположение, которое позже вас укусит.

person LoneWanderer    schedule 13.10.2019
comment
Не уверен в комментарии в последней цитате о «функции отправки»; (а) это sendto(), не так ли? (b) для TCP-потока единственная причина не отправки всего сообщения — ошибка? - person Simon Wright; 13.10.2019
comment
(TBH грубо копирует ответы (что спорно)) - person LoneWanderer; 13.10.2019
comment
@SimonWright Не для TCP: это send() и write(). - person user207421; 17.10.2019
comment
@user207421 user207421, согласно справочной странице BSD, sendto() можно использовать в подключенном состоянии (т. Е. TCP?), И это делает библиотека сокетов Ada OP. - person Simon Wright; 18.10.2019