第 10 章 网络操作 --- 10.4 Socket的无连接通信
本节剖析 UDP 协议的 Socket 无连接通信实现。 UDP(User Datagram Protocol)是无连接、不可靠的传输层协议,在 Winsock 中对应 SOCK_DGRAM。ReactOS 的 UDP 实现在 AFD(afd.sys)的 sendto/recvfrom 路径以及 TCP/IP(tcpip.sys)的 lwIP UDP 实现中。
概述
UDP 通信的关键特性:
- 无连接:不需要建立连接,直接发送
- 不可靠:不保证数据包到达、不保证顺序、不避免重复
- 面向消息 :保留消息边界(一次
sendto一次recvfrom) - 低延迟:适合 DNS、视频、语音等实时场景
本节内容概览
- 10.4.0 框架图
- 10.4.1 UDP 协议概述
- 10.4.2 UDP socket 创建
- 10.4.3
sendto发送数据报 - 10.4.4
recvfrom接收数据报 - 10.4.5 内部数据流
- 10.4.6 UDP socket 选项
- 10.4.7 总结与代码索引
学习目标
- 理解 UDP 通信的工作模型
- 掌握
sendto/recvfrom在 AFD 的路径 - 知道 UDP 数据报如何从 AFD 到 NIC
- 区分阻塞/非阻塞 UDP 通信
涉及的内核子系统
| 子系统 | 头文件/源文件 | 核心作用 |
|---|---|---|
| AFD 主 | drivers/network/afd/afd/main.c(file:///d:/reactos/drivers/network/afd/afd/main.c) | DriverEntry |
| AFD write | write.c(file:///d:/reactos/drivers/network/afd/afd/write.c) | AfdSendDatagram |
| AFD read | read.c(file:///d:/reactos/drivers/network/afd/afd/read.c) | AfdReceiveDatagram |
| AFD info | info.c(file:///d:/reactos/drivers/network/afd/afd/info.c) | socket 信息 |
| TCP/IP | drivers/network/tcpip/tcpip/(file:///d:/reactos/drivers/network/tcpip/tcpip/) | TCP/IP 协议栈 |
| lwIP UDP | drivers/network/tcpip/ip/src/core/udp.c(file:///d:/reactos/drivers/network/tcpip/ip/src/core/udp.c) | UDP 实现 |
| Winsock | dll/win32/ws2_32/(file:///d:/reactos/dll/win32/ws2_32/) | 用户态 Winsock |
| msafd | dll/win32/msafd/(file:///d:/reactos/dll/win32/msafd/) | Service Provider |
10.4.0 框架图
应用 sendto(s, buf, len, 0, addr, addrlen)
|
v
+-------------------------------------+
| ws2_32.dll WSPSendTo |
+-------------------------------------+
|
v DeviceIoControl (IOCTL_AFD_SEND_DATAGRAM)
+-------------------------------------+
| AFD AfdSendDatagram | 本节主题
| - 构造 TDI_SEND_DATAGRAM 请求 |
+-------------------------------------+
|
v IOCTL_TDI_SEND_DATAGRAM
+-------------------------------------+
| TCP/IP tcpip.sys (lwIP udp_send) |
| - 构造 UDP 头 |
| - 构造 IP 头 |
| - ARP 解析目的 MAC |
| - NdisSend |
+-------------------------------------+
|
v
+-------------------------------------+
| NIC Miniport |
+-------------------------------------+
|
v
网络硬件
10.4.1 UDP 协议概述
UDP 头(8 字节)
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| (data) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
UDP 应用场景
- DNS:域名查询(53 端口)
- DHCP:动态地址分配
- NTP:时间同步(123 端口)
- TFTP:简单文件传输
- 实时音视频:RTP/RTSP
- 游戏:多人游戏
10.4.2 UDP socket 创建
c
// 应用
SOCKET s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
路径
ws2_32.dll::socket()调WSPSocketmsafd.dll::WSPSocket翻译为IOCTL_AFD_CREATE到\Device\Afd- AFD 创建
AFD_ENDPOINT,SocketType =SOCK_DGRAM,Protocol =IPPROTO_UDP - AFD 打开
\Device\Udp创建设备句柄
AFD 端点初始化
afd/main.c(file:///d:/reactos/drivers/network/afd/afd/main.c):
c
NTSTATUS AfdCreate(...)
{
// ...
Endpoint->AddressFamily = AF_INET;
Endpoint->SocketType = SOCK_DGRAM;
Endpoint->Protocol = IPPROTO_UDP;
// 打开 \Device\Udp
RtlInitUnicodeString(&DeviceName, L"\\Device\\Udp");
InitializeObjectAttributes(&ObjAttr, &DeviceName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL, NULL);
ZwCreateFile(&Endpoint->TdiAddressHandle,
GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE,
&ObjAttr, &IoStatus, NULL, FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ | FILE_SHARE_WRITE,
FILE_OPEN, 0, NULL, 0);
// ...
}
10.4.3 sendto 发送数据报
应用层
c
int sendto(SOCKET s, const char *buf, int len, int flags,
const struct sockaddr *to, int tolen)
{
WSABUF wsaBuf = {len, (char*)buf};
DWORD bytesSent;
return WSASendTo(s, &wsaBuf, 1, &bytesSent, flags, to, tolen, NULL, NULL);
}
ws2_32.dll::WSASendTo
c
int WSAAPI WSASendTo(SOCKET s, LPWSABUF bufs, DWORD bufCount, LPDWORD bytesSent,
DWORD flags, const struct sockaddr *to, int tolen,
LPWSAOVERLAPPED ovl, LPWSAOVERLAPPED_COMPLETION_ROUTINE cr)
{
// 翻译为 IOCTL_AFD_SEND_DATAGRAM
AFD_SEND_DATAGRAM_REQUEST req = {bufs, bufCount, ...};
AFD_SEND_DATAGRAM_INFO info = {to, tolen, 0, ...};
DWORD bytesReturned;
BOOL ok = DeviceIoControl((HANDLE)s, IOCTL_AFD_SEND_DATAGRAM,
&info, sizeof(info),
&req, sizeof(req),
&bytesReturned, ovl);
// ...
}
AFD AfdSendDatagram
write.c(file:///d:/reactos/drivers/network/afd/afd/write.c):
c
NTSTATUS AfdSendDatagram(PAFD_ENDPOINT Endpoint, PAFD_SEND_DATAGRAM_INFO SendInfo,
PAFD_WSABUF Buf, ULONG BufCount, ULONG *BytesSent)
{
NTSTATUS Status;
PIRP Irp;
PIO_STACK_LOCATION IoStack;
PTDI_REQUEST_KERNEL_SENDDG DatagramRequest;
TDI_CONNECTION_INFORMATION ConnInfo;
PCHAR Buffer;
UINT BufferLength;
UINT TotalLength;
UINT i;
// 1. 构造连接信息(目的地址)
ConnInfo.UserDataLength = 0;
ConnInfo.UserData = NULL;
ConnInfo.OptionsLength = 0;
ConnInfo.Options = NULL;
ConnInfo.RemoteAddressLength = SendInfo->TdiOutputBufferSize;
ConnInfo.RemoteAddress = SendInfo->TdiOutputBuffer;
// 复制目的地址
RtlCopyMemory(ConnInfo.RemoteAddress, &SendInfo->Address, SendInfo->AddressLength);
// 2. 合并用户缓冲到单一缓冲
TotalLength = 0;
for (i = 0; i < BufCount; i++) {
TotalLength += Buf[i].len;
}
Buffer = ExAllocatePoolWithTag(NonPagedPool, TotalLength, 'pdaF');
PCHAR p = Buffer;
for (i = 0; i < BufCount; i++) {
RtlCopyMemory(p, Buf[i].buf, Buf[i].len);
p += Buf[i].len;
}
// 3. 构造 IRP_MJ_INTERNAL_DEVICE_CONTROL
DatagramRequest = ExAllocatePoolWithTag(NonPagedPool, sizeof(TDI_REQUEST_KERNEL_SENDDG), 'pdaF');
DatagramRequest->SendDatagramFlags = 0;
Irp = IoBuildDeviceIoControlRequest(
IOCTL_TDI_SEND_DATAGRAM,
IoGetRelatedDeviceObject(Endpoint->TdiAddressHandle), // Udp 设备
NULL, 0, // input
Buffer, TotalLength, // output (实际是 input data)
FALSE, NULL, NULL);
// 4. 设置 IRP
IoStack = IoGetNextIrpStackLocation(Irp);
IoStack->Parameters.DeviceIoControl.InputBufferLength = TotalLength;
IoStack->Parameters.DeviceIoControl.Type3InputBuffer = Buffer;
IoStack->MinorFunction = TDI_SEND_DATAGRAM;
TdiBuildSendDatagram(Irp, ...);
// 5. 调用 TCP/IP
Status = IoCallDriver(IoGetRelatedDeviceObject(Endpoint->TdiAddressHandle), Irp);
// 6. 等待
if (Status == STATUS_PENDING) {
KeWaitForSingleObject(...);
}
*BytesSent = TotalLength;
return Status;
}
10.4.4 recvfrom 接收数据报
应用层
c
int recvfrom(SOCKET s, char *buf, int len, int flags,
struct sockaddr *from, int *fromlen)
{
WSABUF wsaBuf = {len, buf};
DWORD bytesRecv;
DWORD flags_arg = flags;
return WSARecvFrom(s, &wsaBuf, 1, &bytesRecv, &flags_arg, from, fromlen, NULL, NULL);
}
AFD AfdReceiveDatagram
read.c(file:///d:/reactos/drivers/network/afd/afd/read.c):
c
NTSTATUS AfdReceiveDatagram(PAFD_ENDPOINT Endpoint,
PAFD_RECV_DATAGRAM_INFO RecvInfo,
PAFD_WSABUF Buf, ULONG BufCount,
PULONG BytesReceived, PULONG Flags)
{
PIRP Irp;
PMDL Mdl;
// 1. 构造 MDL 描述用户缓冲
Mdl = IoAllocateMdl(Buf[0].buf, Buf[0].len, FALSE, FALSE, NULL);
MmBuildMdlForNonPagedPool(Mdl);
// 2. 构造 IRP
Irp = TdiBuildReceiveDatagram(
Endpoint->TdiAddressHandle,
...,
Mdl,
...,
RecvInfo->Address, &RecvInfo->AddressLength,
...);
// 3. 调用 TCP/IP
IoCallDriver(IoGetRelatedDeviceObject(Endpoint->TdiAddressHandle), Irp);
// 4. 等待
if (Irp->IoStatus.Status == STATUS_PENDING) {
KeWaitForSingleObject(...);
}
*BytesReceived = Irp->IoStatus.Information;
return Irp->IoStatus.Status;
}
异步 recvfrom
c
// 应用层
WSAOVERLAPPED ovl = {0};
ovl.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
WSARecvFrom(s, &wsaBuf, 1, &bytesRecv, &flags, from, &fromLen, &ovl, NULL);
// 等待
WaitForSingleObject(ovl.hEvent, INFINITE);
GetOverlappedResult(s, &ovl, &bytesRecv, FALSE);
10.4.5 内部数据流
发送:AFD → TCP/IP → NIC
WSASendTo
-> IOCTL_AFD_SEND_DATAGRAM
-> AfdSendDatagram
-> IOCTL_TDI_SEND_DATAGRAM
-> tcpip.sys TdiDispatch
-> lwIP udp_send
-> ip4_output
-> NdisSend
-> NDIS 库
-> Miniport Send
-> NIC 发送
接收:NIC → TCP/IP → AFD
NIC 接收
-> Miniport Interrupt
-> NdisMIndicateReceivePacket
-> TCP/IP ProtocolReceive
-> lwIP udp_input
-> 把数据复制到 AFD 端点的接收队列
-> 唤醒等待的 recvfrom 线程
-> AfdReceiveDatagram 完成
-> WSARecvFrom 返回
lwIP UDP 发送
c
// lwIP 内部
err_t udp_send(struct udp_pcb *pcb, struct pbuf *p)
{
// 1. 构造 UDP 头
struct udp_hdr *udphdr = p->payload;
udphdr->src = pcb->local_port;
udphdr->dest = pcb->remote_port;
udphdr->len = p->tot_len;
udphdr->chksum = 0; // 可选校验和
// 2. 调用 IP 发送
return ip4_output(p, ...);
}
lwIP UDP 接收
c
void udp_input(struct pbuf *p, struct netif *inp)
{
// 1. 解析 UDP 头
struct udp_hdr *udphdr = p->payload;
// 2. 查找匹配 PCB(基于本地端口)
for (pcb = udp_pcbs; pcb != NULL; pcb = pcb->next) {
if (pcb->local_port == udphdr->dest) {
// 3. 把数据排队到 PCB
pbuf_free(pcb->recv_data); // 释放旧
pcb->recv_data = p;
pcb->recv_flags = ...;
// 4. 通知 AFD
AfdNotifyUdpReceive(pcb);
}
}
}
10.4.6 UDP socket 选项
SO_BROADCAST(广播)
允许发送广播数据报:
c
int enable = 1;
setsockopt(s, SOL_SOCKET, SO_BROADCAST, &enable, sizeof(enable));
sendto 的目的地址是 255.255.255.255(或子网广播地址)。
SO_RCVBUF / SO_SNDBUF(缓冲大小)
设置 socket 接收/发送缓冲区大小:
c
int size = 65536;
setsockopt(s, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
SO_RCVTIMEO / SO_SNDTIMEO(超时)
设置阻塞 recv/send 的超时:
c
struct timeval tv = {5, 0}; // 5 秒
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
IP_MULTICAST_TTL(多播 TTL)
设置多播数据报的 TTL:
c
int ttl = 32;
setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));
10.4.7 总结
关键要点:
- UDP 无连接:不需要三次握手
- 应用 API :
socket(AF_INET, SOCK_DGRAM, 0)+sendto+recvfrom - AFD 路径 :
AfdSendDatagram/AfdReceiveDatagram通过 TDI_SEND_DATAGRAM - TDI 接口 :
IOCTL_TDI_SEND_DATAGRAM/IOCTL_TDI_RECEIVE_DATAGRAM - lwIP 实现 :
udp_send/udp_input - 数据报保留边界 :
recvfrom一次调用返回一次发送 - socket 选项:SO_BROADCAST、SO_RCVBUF、SO_RCVTIMEO、IP_MULTICAST_TTL
UDP 无连接通信的深度技术分析
UDP(User Datagram Protocol)是互联网基础协议之一,在 ReactOS 网络栈中扮演着重要角色。本节深入分析 UDP 协议在 ReactOS 中的完整实现路径,从应用层 API 到硬件发送的每个环节。
UDP 协议的设计哲学
UDP 的设计遵循"最小化"原则:它只在 IP 协议之上增加了端口号和校验和两个基本功能。这种简洁性带来了几个关键特性:
- 无连接:每个数据报独立处理,不需要建立和维护连接状态。
- 不可靠:不保证数据报到达、不保证顺序、不重传丢失的数据报。
- 低开销:UDP 头只有 8 字节,比 TCP 的 20 字节头更轻量。
- 无拥塞控制:不会因网络拥塞而降低发送速率。
这些特性使 UDP 成为实时应用(如视频流、在线游戏、DNS 查询)的理想选择。
ReactOS 中 UDP 的完整数据路径
在 ReactOS 中,UDP 数据报的发送经过以下完整路径:
-
应用层 :调用
sendto(s, buf, len, 0, addr, addrlen)。 -
ws2_32.dll :
sendto函数将调用转换为WSASendTo,准备WSABUF结构。 -
msafd.dll :
WSPSendTo函数通过NtDeviceIoControlFile发送IOCTL_AFD_SEND_DATAGRAM到\Device\Afd。 -
afd.sys :
AfdSendDatagram函数处理请求:- 验证 socket 状态和参数
- 构造 TDI 请求结构
- 合并用户缓冲到单一缓冲区
- 发送
IOCTL_TDI_SEND_DATAGRAM到\Device\Udp
-
tcpip.sys :
DispatchTdi函数处理 TDI 请求:- 解析目标地址和端口
- 查找匹配的 UDP PCB(Protocol Control Block)
- 调用 lwIP 的
udp_sendto
-
lwIP :
udp_send函数:- 分配
pbuf结构 - 填充 UDP 头(源端口、目标端口、长度、校验和)
- 调用
ip4_output发送 IP 数据报
- 分配
-
IP 层 :
ip4_output函数:- 填充 IP 头(源 IP、目标 IP、TTL、协议号 17)
- 计算 IP 校验和
- 调用
netif->output(以太网输出函数)
-
ARP 解析 :
etharp_output函数:- 查找目标 IP 的 MAC 地址
- 如果未找到,发送 ARP 请求并排队数据报
- 填充以太网头(源 MAC、目标 MAC、类型 0x0800)
-
NDIS 发送 :
ethernetif_linkoutput函数:- 分配
NDIS_PACKET和NDIS_BUFFER - 调用
NdisSend将包传递给 NDIS 库
- 分配
-
Miniport 驱动:NIC 驱动将数据发送到物理介质。
UDP 接收的完整路径
UDP 数据报的接收是发送的逆过程:
-
NIC 接收:网卡收到帧,触发中断。
-
Miniport ISR :中断处理程序读取数据,调用
NdisMIndicateReceivePacket。 -
NDIS 分发:NDIS 库将包分发给所有绑定的协议驱动。
-
tcpip.sys 接收 :
TCPReceive回调:- 分配
pbuf结构 - 复制数据到
pbuf - 调用
netif->input(lwIP 输入函数)
- 分配
-
lwIP 以太网层 :
ethernet_input函数:- 解析以太网头
- 根据类型字段分发到 IP 层
-
IP 层 :
ip4_input函数:- 验证 IP 校验和
- 检查目标 IP
- 根据协议字段(17)分发到 UDP 层
-
UDP 层 :
udp_input函数:- 解析 UDP 头
- 验证 UDP 校验和
- 查找匹配的 PCB(基于目标端口)
- 将数据放入 PCB 的接收队列
- 通知等待的接收者
-
tcpip.sys 通知:通过 TDI 事件通知 AFD 数据已到达。
-
afd.sys 完成 :
AfdReceiveDatagram完成挂起的 IRP:- 从 PCB 接收队列取出数据
- 复制到用户缓冲区
- 填充源地址信息
- 完成 IRP
-
应用层 :
recvfrom返回,数据可用。
UDP PCB(Protocol Control Block)管理
lwIP 使用 udp_pcb 结构管理每个 UDP 端点:
c
struct udp_pcb {
struct udp_pcb *next; // 链表指针
ip_addr_t local_ip; // 本地 IP
ip_addr_t remote_ip; // 远程 IP
u16_t local_port; // 本地端口
u16_t remote_port; // 远程端口
// 接收回调
void (*recv)(void *arg, struct udp_pcb *pcb,
struct pbuf *p, ip_addr_t *addr, u16_t port);
void *recv_arg; // 回调参数
// 选项
u8_t flags; // 标志(如 UDP_FLAGS_CONNECTED)
u16_t chksum_len; // 校验和长度
};
所有 UDP PCB 通过 udp_pcbs 全局链表管理。当收到 UDP 数据报时,udp_input 遍历这个链表,查找匹配的 PCB(基于目标端口和 IP)。
UDP 校验和处理
UDP 校验和是可选的(IPv4 中),覆盖 UDP 头、数据和伪首部。ReactOS 的 lwIP 实现支持校验和计算和验证:
c
// 发送时计算校验和
if (pcb->flags & UDP_FLAGS_NOCHK) {
udphdr->chksum = 0; // 禁用校验和
} else {
// 计算伪首部 + UDP 头 + 数据的校验和
udphdr->chksum = ip_chksum_pseudo(p, IP_PROTO_UDP,
p->tot_len, src, dest);
}
// 接收时验证校验和
if (udphdr->chksum != 0) {
if (ip_chksum_pseudo(p, IP_PROTO_UDP, p->tot_len, src, dest) != 0) {
// 校验和错误,丢弃数据报
pbuf_free(p);
return;
}
}
UDP 广播和多播
UDP 支持广播和多播两种特殊传输模式:
-
广播 :目标 IP 为
255.255.255.255或子网广播地址。需要设置SO_BROADCASTsocket 选项:cint enable = 1; setsockopt(s, SOL_SOCKET, SO_BROADCAST, &enable, sizeof(enable)); -
多播:目标 IP 为 D 类地址(224.0.0.0 - 239.255.255.255)。需要加入多播组:
cstruct ip_mreq mreq; mreq.imr_multiaddr.s_addr = inet_addr("224.0.0.1"); mreq.imr_interface.s_addr = INADDR_ANY; setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
ReactOS 的 lwIP 支持广播和多播,通过 IGMP(Internet Group Management Protocol)管理多播组成员。
UDP 的常见问题与解决方案
UDP 的不可靠特性带来几个常见问题:
-
数据报丢失:网络拥塞或路由问题导致数据报丢失。解决方案:应用层实现重传机制,或使用 RUDP(Reliable UDP)等协议。
-
数据报乱序:网络路径变化导致数据报到达顺序与发送顺序不同。解决方案:应用层添加序列号,重新排序。
-
数据报重复:网络重传导致收到重复数据报。解决方案:应用层去重。
-
拥塞:UDP 没有拥塞控制,可能导致网络拥塞加剧。解决方案:应用层实现拥塞控制,或使用 DCCP(Datagram Congestion Control Protocol)。
与 Windows UDP 实现的对比
| 特性 | Windows UDP | ReactOS UDP |
|---|---|---|
| 校验和 | 完整支持 | 完整支持 |
| 广播 | 完整支持 | 完整支持 |
| 多播 | 完整支持 | 基本支持 |
| UDP 封装 | 支持 | 不支持 |
| UDP 卸载 | 支持 | 不支持 |
| 性能优化 | 零拷贝 | 标准实现 |
ReactOS 的 UDP 实现已经能够支持大多数常见的 UDP 应用场景,包括 DNS、DHCP、NTP、TFTP 等。
本章代码索引
| 文件 | 内容 |
|---|---|
| afd/main.c(file:///d:/reactos/drivers/network/afd/afd/main.c) | DriverEntry |
| afd/write.c(file:///d:/reactos/drivers/network/afd/afd/write.c) | AfdSendDatagram |
| afd/read.c(file:///d:/reactos/drivers/network/afd/afd/read.c) | AfdReceiveDatagram |
| afd/info.c(file:///d:/reactos/drivers/network/afd/afd/info.c) | socket 信息 |
| tcpip/(file:///d:/reactos/drivers/network/tcpip/tcpip/) | TCP/IP 协议栈 |
| ip/src/core/udp.c(file:///d:/reactos/drivers/network/tcpip/ip/src/core/udp.c) | lwIP UDP |
| ws2_32/(file:///d:/reactos/dll/win32/ws2_32/) | Winsock DLL |
| msafd/(file:///d:/reactos/dll/win32/msafd/) | Service Provider |