【问题标题】:Ada Programming - Socket blocking behaviourAda Programming - 套接字阻塞行为
【发布时间】:2019-10-14 04:35:49
【问题描述】:

请注意,这个问题专门针对 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 的连贯文档 --> 流 --> 关于如何以及何时分派数据包的套接字安排。

【问题讨论】:

标签: sockets tcp stream ada


【解决方案1】:

根据this link,TCP流Write的底层代码应该如下。 如您所见,有一个循环尝试发送数据,直到所有内容都传递到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;

【讨论】:

    【解决方案2】:

    ’Write’Read 的默认行为在ARM13.13.2(8.2) 中定义。

    在您的第一种情况下,这相当于对 sendto() 的两次调用。如果你设置了TCP_NODELAY,这可能会导致网络上有两个IP数据包,如果两个’Writes(100毫秒?)之间有足够长的时间间隔,就会发生这种情况。否则,数据将被缓冲,直到低级网络软件的 IP 数据包已满(或者,再次,经过足够长的时间间隔)。

    如果你有

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

    然后Info’Write((B =&gt; 5, A =&gt; 6)) 将导致两个sendto() 调用每个传输一个字节,值6 中的第一个(A 值;按规范顺序传输,A 然后B),第二个值5.


    我不明白这些如何导致格式错误的数据包。需要更多信息。

    【讨论】:

    • 回复:“格式错误的数据包”。这就是 Wireshark 对它们的描述。我现在想知道我是否过于相信 Wireshark 的 VNC 协议解析器。
    • 不一定会产生两个数据包。仍然存在可能发生合并的情况,例如,如果接收窗口关闭并且在第二次写入之前无法进行传输。在任何情况下,例如如果接收器很慢,它仍然可以在一次读取中接收
    • @Wossname 确保您已在 Wireshark 中为 VNC(重新组装跨越多个 TCP 段的 VNC 消息)和 TCP(允许子解析器重新组装 TCP 流)启用重新组装。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-10-31
    • 2013-10-15
    • 1970-01-01
    • 2019-08-02
    • 2011-08-19
    • 2021-03-03
    • 1970-01-01
    相关资源
    最近更新 更多