首页 > 解决方案 > Ada Programming - 套接字阻塞行为

问题描述

请注意,这个问题专门针对 Ada 语言和 Ada 的“g-socket”API

我在本地打开了一个 Socket 并正在监听传入的连接。连接被接受,并且我能够通过读取和写入连接到远程套接字的 Stream 对象来在连接上建立某种连贯的数据传输。

问题:

当 Stream 附加到 TCP Socket 时,是否每次调用通用流'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 安排关于如何以及何时分派数据包的连贯文档。

标签: socketstcpstreamada

解决方案


根据此链接,TCP 流写入的底层代码应如下所示。如您所见,有一个循环尝试发送数据,直到所有内容都传入Send_Socket. 所以对我来说,一切都取决于C_Sendto实现 fwhici 本身调用操作系统原语

但是,所述 8 位的单个写入并不能保证它会对应于包含这些数据包的网络数据包(因为 TCP 的本质)。

   -----------
   -- 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;

推荐阅读