如何用GNAT.Sockets在Ada中从UDP套接字获取有效数据?
Ada UDP服务器:监听、接收与回复消息
核心实现思路
UDP是无连接协议,无需Listen_Socket或Accept_Socket这类TCP专用操作,直接通过Receive_Socket读取数据包,再用Send_Socket回复即可。以下是完整的分步实现:
1. 接收UDP消息
使用GNAT.Sockets.Receive_Socket从绑定的套接字读取数据,该过程需要传入:
- 套接字对象
- 存储数据的
Stream_Element_Array缓冲区 - 存储发送方地址的
Sock_Addr_Type变量 - 输出参数
Last,标记缓冲区中实际被填充的最后一个元素位置
在你的基础代码上补充接收逻辑:
with GNAT.Sockets; use GNAT.Sockets; with Ada.Text_IO; use Ada.Text_IO; procedure UDP_Server is Sock : Socket_Type; Addr : Sock_Addr_Type (Family_Inet); Buffer : Stream_Element_Array (1 .. 1024); -- 定义1KB缓冲区 Last : Stream_Element_Offset; Sender_Addr : Sock_Addr_Type (Family_Inet); begin Create_Socket (Sock, Family_Inet, Socket_Datagram); Addr.Addr := Any_Inet_Addr; Addr.Port := 12345; Bind_Socket (Sock, Addr); Put_Line ("UDP服务器已启动,监听端口12345..."); loop -- 持续监听消息 Receive_Socket (Socket => Sock, Item => Buffer, Last => Last, From => Sender_Addr); -- 此时Buffer(1..Last)就是收到的原始数据 Put_Line ("收到来自" & Image (Sender_Addr.Addr) & ":" & Port_Type'Image (Sender_Addr.Port) & "的消息"); end loop; end UDP_Server;
2. 安全转换Stream_Element_Array到字符串
避免依赖实现细节的Unchecked_Conversion,改用符合Ada标准的显式类型转换:
function SEA_To_String (SEA : Stream_Element_Array) return String is Result : String (1 .. Natural (SEA'Length)); begin for I in SEA'Range loop Result (Natural (I - SEA'First + 1)) := Character'Val (Stream_Element'Pos (SEA (I))); end loop; return Result; end SEA_To_String;
这个函数通过Stream_Element'Pos获取字节数值,再用Character'Val转换为对应字符,完全遵循Ada标准,不依赖平台特定实现。
将其集成到接收逻辑中:
-- 在Receive_Socket调用后添加 declare Received_String : String := SEA_To_String (Buffer (1 .. Last)); begin Put_Line ("消息内容:" & Received_String); end;
3. 回复UDP消息
使用GNAT.Sockets.Send_Socket将回复发送回发送方,先将字符串转换为Stream_Element_Array:
function String_To_SEA (Str : String) return Stream_Element_Array is Result : Stream_Element_Array (1 .. Stream_Element_Offset (Str'Length)); begin for I in Str'Range loop Result (Stream_Element_Offset (I - Str'First + 1)) := Stream_Element'Val (Character'Pos (Str (I))); end loop; return Result; end String_To_SEA;
然后在接收后添加回复逻辑:
declare Reply_String : String := "已收到你的消息:" & Received_String; Reply_SEA : Stream_Element_Array := String_To_SEA (Reply_String); begin Send_Socket (Socket => Sock, Item => Reply_SEA, To => Sender_Addr); Put_Line ("已回复消息:" & Reply_String); end;
完整可运行代码
with GNAT.Sockets; use GNAT.Sockets; with Ada.Text_IO; use Ada.Text_IO; procedure UDP_Server is Sock : Socket_Type; Addr : Sock_Addr_Type (Family_Inet); Buffer : Stream_Element_Array (1 .. 1024); Last : Stream_Element_Offset; Sender_Addr : Sock_Addr_Type (Family_Inet); function SEA_To_String (SEA : Stream_Element_Array) return String is Result : String (1 .. Natural (SEA'Length)); begin for I in SEA'Range loop Result (Natural (I - SEA'First + 1)) := Character'Val (Stream_Element'Pos (SEA (I))); end loop; return Result; end SEA_To_String; function String_To_SEA (Str : String) return Stream_Element_Array is Result : Stream_Element_Array (1 .. Stream_Element_Offset (Str'Length)); begin for I in Str'Range loop Result (Stream_Element_Offset (I - Str'First + 1)) := Stream_Element'Val (Character'Pos (Str (I))); end loop; return Result; end String_To_SEA; begin Create_Socket (Sock, Family_Inet, Socket_Datagram); Addr.Addr := Any_Inet_Addr; Addr.Port := 12345; Bind_Socket (Sock, Addr); Put_Line ("UDP服务器已启动,监听端口12345..."); loop Receive_Socket (Socket => Sock, Item => Buffer, Last => Last, From => Sender_Addr); declare Received_String : String := SEA_To_String (Buffer (1 .. Last)); Reply_String : String := "已收到你的消息:" & Received_String; Reply_SEA : Stream_Element_Array := String_To_SEA (Reply_String); begin Put_Line ("收到来自" & Image (Sender_Addr.Addr) & ":" & Port_Type'Image (Sender_Addr.Port) & "的消息:" & Received_String); Send_Socket (Socket => Sock, Item => Reply_SEA, To => Sender_Addr); Put_Line ("已回复:" & Reply_String); end; end loop; exception when Socket_Error => Put_Line ("套接字错误"); Close_Socket (Sock); end UDP_Server;
关键说明
Receive_Vector适用于一次接收多个UDP数据包(分散-聚集IO),普通单包场景用Receive_Socket足够。- 不要使用TCP的流操作(
'Read/'Input),UDP是面向数据包的协议,流操作会破坏数据包边界。 - 上述转换函数完全符合Ada标准,即使
Stream_Element的实现大小不是8位,只要字符编码匹配,就能正确转换。
内容的提问来源于stack exchange,提问作者Mark Gardner
相关产品推荐
相关产品推荐

