Ada Programming - поведение блокировки сокетов - PullRequest
0 голосов
/ 13 октября 2019

Обратите внимание, что этот вопрос специально предназначен для языка Ada и API "g-socket" Ады

Я открыл Socket локально и слушаюдля входящих соединений. Соединения принимаются, и я могу установить несколько согласованную передачу данных по соединению, читая и записывая объект Stream, подключенный к удаленному сокету.

Вопрос:

КогдаПоток подключен к сокету TCP, каждый ли вызов процедуры обобщенного потока 'Write вызывает немедленную отправку пакета?

Пример A:

--  two separate `'Write` calls always seems to generate two packets of 1 byte each
U8'Write (Comms, Number_Of_Security_Types);
U8'Write (Comms, Security_Type_None);

Пример B:

--  One `'Write` call that happens to send the same data formatted as a 16 bit value is sent as a single packet.
U16'Write (Comms,
  (U16 (Number_Of_Security_Types) * 16#100#) +
  U16 (Security_Type_None)
  );

Пример C:

--  This is a complex record with a further record nested within it.
--    its `'Write` callback is implemented as a series of many sequential `Integer'Write` calls...
Server_Init'Write (Comms, Server_Init_Rec);

Примеры A и C приводят к тому, что Wireshark обнаруживает искаженные пакеты, но в примере B создается хорошо сформированный пакет без проблем.

Такое поведение кажется детерминированным, но я не могу найти никакой последовательной документации относительно 'Write -> Stream -> Socket расположения относительно того, как и когда отправляются пакеты.

Ответы [ 2 ]

1 голос
/ 13 октября 2019

Поведение по умолчанию ’Write, ’Read определено в ARM13.13.2 (8.2) .

В вашем первом случае это будет означать два вызова sendto(). Если вы установили TCP_NODELAY, это, вероятно, приведет к двум IP-пакетам в сети, что произойдет, если между двумя ’Write с (100 мс?) Достаточно большого промежутка времени. В противном случае данные будут буферизироваться до тех пор, пока низкоуровневое сетевое программное обеспечение не заполнит IP-пакет (или, опять-таки, пройдет достаточно большой интервал времени).

Если у вас было

type Info is record
   A : U8;
   B : U8;
end record;

затем Info’Write((B => 5, A => 6)) приведет к двум sendto() вызовам, передающим по одному байту каждый, первое значение 6 (значение A; передача в каноническом порядке, A затем B), второе значение 5.


Я не понимаю, как это может привести к искажению пакетов. Требуется дополнительная информация.

1 голос
/ 13 октября 2019

Согласно этой ссылке базовый код для потока TCP записи должен быть следующим. Как видите, есть цикл, который пытается отправить данные, пока все не перейдет в Send_Socket. Так что для меня все зависит от реализации C_Sendto, которую сам fwhici вызывает примитив ОС

Однако одна запись указанных 8 битов не гарантирует, что она будет соответствовать сетевому пакету, содержащему эти пакеты (из-засама природа ПТС).

   -----------
   -- Write --
   -----------

   procedure Write 
     (Stream : in out Datagram_Socket_Stream_Type; 
      Item   : Ada.Streams.Stream_Element_Array) 
   is
      First : Ada.Streams.Stream_Element_Offset          := Item'First; 
      Index : Ada.Streams.Stream_Element_Offset          := First - 1; 
      Max   : constant Ada.Streams.Stream_Element_Offset := Item'Last; 

   begin
      loop
         Send_Socket <== try to send until all content of write sent ?
           (Stream.Socket,
            Item (First .. Max),
            Index,
            Stream.To);

         --  Exit when all or zero data sent. Zero means that the
         --  socket has been closed by peer.

         exit when Index < First or else Index = Max;

         First := Index + 1;
      end loop;

      if Index /= Max then
         raise Socket_Error;
      end if;
   end Write;

-- [...]
   procedure Send_Socket 
     (Socket : Socket_Type; 
      Item   : Ada.Streams.Stream_Element_Array; 
      Last   : out Ada.Streams.Stream_Element_Offset; 
      To     : Sock_Addr_Type) 
   is
      use type Ada.Streams.Stream_Element_Offset;

      Res : C.int; 
      Sin : aliased Sockaddr_In; 
      Len : aliased C.int := Sin'Size / 8; 

   begin
      Sin.Sin_Family := C.unsigned_short (Families (To.Family));
      Sin.Sin_Addr   := To_In_Addr (To.Addr);
      Sin.Sin_Port   := Port_To_Network (C.unsigned_short (To.Port));

      Res := C_Sendto -- <== where things happen
        (C.int (Socket),
         Item (Item'First)'Address,
         Item'Length, 0,
         Sin'Unchecked_Access,
         Len);

      if Res = Failure then
         Raise_Socket_Error (Socket_Errno);
      end if;

      Last := Item'First + Ada.Streams.Stream_Element_Offset (Res - 1);
   end Send_Socket;

https://www2.adacore.com/gap-static/GNAT_Book/html/rts/g-socthi__adb.htm

   --------------
   -- C_Sendto --
   --------------

   function C_Sendto 
     (S     : C.int; 
      Msg   : System.Address; 
      Len   : C.int; 
      Flags : C.int; 
      To    : Sockaddr_In_Access; 
      Tolen : C.int) 
      return  C.int
   is
      Res : C.int; 

   begin
      loop
         Res := Syscall_Sendto (S, Msg, Len, Flags, To, Tolen);
         exit when Thread_Blocking_IO
           or else Res /= Failure
           or else Table (S).Non_Blocking
           or else Errno /= Constants.EWOULDBLOCK;
         delay Quantum;
      end loop;

      return Res;
   end C_Sendto;
...