UDP4
UDP4协议说明
UDP的全称是User Datagram Protocol,它不提供复杂的控制机制,仅利用IP提供面向无连接的通信服务。它将上层应用程序发来的数据在收到的那一刻,立即按照原样发送到网络。
UDP报文格式:
各个参数说明如下:
字段 | 长度(字节) | 描述 |
---|---|---|
Source Port | 2 | 发送端口,标识哪个应用程序发送(发送进程)。 |
Destination Port | 2 | 目标端口,标识哪个应用程序接收(接收进程)。 |
Length | 2 | UDP首部加上UDP数据的字节数,最小为8。 |
Checksum | 2 | 覆盖UDP首部和UDP数据,是可选的。 |
data octets | 变长 | UDP负载,可选的。 |
前面的四个参数对应到UEFI代码中就是UDP头部:
//
// UDP header definition
//
typedef struct {
UINT16 SrcPort;
UINT16 DstPort;
UINT16 Length;
UINT16 Checksum;
} EFI_UDP_HEADER;
UDP4代码综述
UDP4也是一个通用的网络协议,其实现在NetworkPkg\Udp4Dxe\Udp4Dxe.inf,这里首先需要看下它的入口:
EFI_STATUS
EFIAPI
Udp4DriverEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
//
// Install the Udp4DriverBinding and Udp4ComponentName protocols.
//
Status = EfiLibInstallDriverBindingComponentName2 (
ImageHandle,
SystemTable,
&gUdp4DriverBinding,
ImageHandle,
&gUdp4ComponentName,
&gUdp4ComponentName2
);
if (!EFI_ERROR (Status)) {
//
// Initialize the UDP random port.
//
mUdp4RandomPort = (UINT16)(((UINT16)NetRandomInitSeed ()) % UDP4_PORT_KNOWN + UDP4_PORT_KNOWN); // 宏的值是1024
}
}
因为UDP4也是一个UEFI Driver Model,所以第一步是安装gUdp4DriverBinding
,其实现:
EFI_DRIVER_BINDING_PROTOCOL gUdp4DriverBinding = {
Udp4DriverBindingSupported,
Udp4DriverBindingStart,
Udp4DriverBindingStop,
0xa,
NULL,
NULL
};
而第二步是初始化一个随机的UDP端口,根据通用网络协议的做法,UDP的端口占两个字节(即16位),只要不是0-1023里面的公认端口都可以,且跟TCP端口的一致也没有关系。
UDP4在UEFI网络协议栈中的关系图:
Udp4DriverBindingSupported
UDP4依赖于IP4:
EFI_STATUS
EFIAPI
Udp4DriverBindingSupported (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
)
{
//
// Test for the Ip4 Protocol
//
Status = gBS->OpenProtocol (
ControllerHandle,
&gEfiIp4ServiceBindingProtocolGuid,
NULL,
This->DriverBindingHandle,
ControllerHandle,
EFI_OPEN_PROTOCOL_TEST_PROTOCOL
);
}
Udp4DriverBindingStart
Start函数的流程大致如下:
- 初始化
UDP4_SERVICE_DATA
。 - 安装
gEfiUdp4ServiceBindingProtocolGuid
。
同其它驱动一样,重点也是结构体,这里就是UDP4_SERVICE_DATA
。
UDP4_SERVICE_DATA
UDP4_SERVICE_DATA
在Start函数中创建:
EFI_STATUS
EFIAPI
Udp4DriverBindingStart (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
)
{
Status = Udp4CreateService (Udp4Service, This->DriverBindingHandle, ControllerHandle);
}
其结构体定义如下:
typedef struct _UDP4_SERVICE_DATA_ {
UINT32 Signature;
EFI_SERVICE_BINDING_PROTOCOL ServiceBinding;
EFI_HANDLE ImageHandle;
EFI_HANDLE ControllerHandle;
LIST_ENTRY ChildrenList;
UINTN ChildrenNumber;
IP_IO *IpIo;
EFI_EVENT TimeoutEvent;
} UDP4_SERVICE_DATA;
相比之前的服务数据,这个结构体相当得简单,其中比较重要的成员有:
ServiceBinding
:对应mUdp4ServiceBinding
:
EFI_SERVICE_BINDING_PROTOCOL mUdp4ServiceBinding = {
Udp4ServiceBindingCreateChild,
Udp4ServiceBindingDestroyChild
};
用于创建UDP4子项。
ChildrenList
,ChildrenNumber
:对应UDP4_INSTANCE_DATA
结构体,由Udp4ServiceBindingCreateChild()
创建,是表示子项的结构体,在UDP4_INSTANCE_DATA会进一步介绍。IpIo
:它是对IP4实例的一个包装,UDP4通过它来进行通信,在IP_IO已经介绍过。在UDP4以及TCP4中会进场看到类似IP_IO
这样的结构体,它们仅仅是对IP层的保证,之所以要有这样的包装,是因为存在IPv4和IPv6两个版本,而这里只关注IPv4。TimeoutEvent
:一个定时事件,创建的位置是在Udp4CreateService()
函数中:
EFI_STATUS
Udp4CreateService (
IN OUT UDP4_SERVICE_DATA *Udp4Service,
IN EFI_HANDLE ImageHandle,
IN EFI_HANDLE ControllerHandle
)
{
//
// Create the event for Udp timeout checking.
//
Status = gBS->CreateEvent (
EVT_TIMER | EVT_NOTIFY_SIGNAL,
TPL_CALLBACK,
Udp4CheckTimeout,
Udp4Service,
&Udp4Service->TimeoutEvent
);
//
// Start the timeout timer event.
//
Status = gBS->SetTimer (
Udp4Service->TimeoutEvent,
TimerPeriodic,
UDP4_TIMEOUT_INTERVAL // 50 milliseconds
);
对应的回调函数Udp4CheckTimeout()
,它用来检测接收到的报文是否过期,其主体代码:
VOID
EFIAPI
Udp4CheckTimeout (
IN EFI_EVENT Event,
IN VOID *Context
)
{
NET_LIST_FOR_EACH (Entry, &Udp4Service->ChildrenList) {
//
// Iterate all the instances belonging to this service context.
//
Instance = NET_LIST_USER_STRUCT (Entry, UDP4_INSTANCE_DATA, Link);
NET_CHECK_SIGNATURE (Instance, UDP4_INSTANCE_DATA_SIGNATURE);
if (!Instance->Configured || (Instance->ConfigData.ReceiveTimeout == 0)) {
//
// Skip this instance if it's not configured or no receive timeout.
//
continue;
}
NET_LIST_FOR_EACH_SAFE (WrapEntry, NextEntry, &Instance->RcvdDgramQue) {
//
// Iterate all the rxdatas belonging to this udp instance.
//
Wrap = NET_LIST_USER_STRUCT (WrapEntry, UDP4_RXDATA_WRAP, Link);
//
// TimeoutTick unit is microsecond, MNP_TIMEOUT_CHECK_INTERVAL unit is 100ns.
//
if (Wrap->TimeoutTick < (UDP4_TIMEOUT_INTERVAL / 10)) {
//
// Remove this RxData if it timeouts.
//
Udp4RecycleRxDataWrap (NULL, (VOID *)Wrap);
} else {
Wrap->TimeoutTick -= (UDP4_TIMEOUT_INTERVAL / 10);
}
}
}
}
这里的Wrap
对应结构体UDP4_RXDATA_WRAP
:
typedef struct _UDP4_RXDATA_WRAP_ {
LIST_ENTRY Link;
NET_BUF *Packet;
UINT32 TimeoutTick;
EFI_UDP4_RECEIVE_DATA RxData;
} UDP4_RXDATA_WRAP;
它通过Udp4WrapRxData()
创建,然后放到一个队列中供UDP驱动处理,如果来不及处理就会过期,而过期时间也由这里的成员TimeoutTick
指定,而该成员由另外的一个值指定:
Wrap->TimeoutTick = Instance->ConfigData.ReceiveTimeout;
Instance
是后面会介绍的UDP4_INSTANCE_DATA
中的配置参数ConfigData
,ConfigData.ReceiveTimeout
的值在创建时是-1,表示不会过期:
//
// use the -1 magic number to disable the receiving process of the ip instance.
//
Ip4ConfigData->ReceiveTimeout = (UINT32)(-1);
不过在UDP4的配置中可以修改:
EFI_STATUS
EFIAPI
Udp4Configure (
IN EFI_UDP4_PROTOCOL *This,
IN EFI_UDP4_CONFIG_DATA *UdpConfigData OPTIONAL
)
{
if (UdpConfigData != NULL) {
if (Instance->Configured) {
//
// Save the reconfigurable parameters.
//
Instance->ConfigData.TransmitTimeout = UdpConfigData->TransmitTimeout;
}
}
}
UDP4_INSTANCE_DATA
UDP4_INSTANCE_DATA
表示一个UDP4子项,其它位于NetworkPkg\Udp4Dxe\Udp4Impl.h:
typedef struct _UDP4_INSTANCE_DATA_ {
UINT32 Signature;
LIST_ENTRY Link;
UDP4_SERVICE_DATA *Udp4Service;
EFI_UDP4_PROTOCOL Udp4Proto;
EFI_UDP4_CONFIG_DATA ConfigData;
EFI_HANDLE ChildHandle;
BOOLEAN Configured;
BOOLEAN IsNoMapping;
NET_MAP TxTokens;
NET_MAP RxTokens;
NET_MAP McastIps;
LIST_ENTRY RcvdDgramQue;
LIST_ENTRY DeliveredDgramQue;
UINT16 HeadSum;
EFI_STATUS IcmpError;
IP_IO_IP_INFO *IpInfo;
BOOLEAN InDestroy;
} UDP4_INSTANCE_DATA;
下面介绍其中比较重要的成员:
-
Udp4Service
:指向UDP4服务的结构体。 -
Udp4Proto
:对应EFI_UDP4_PROTOCOL,后面会进一步介绍。 -
ConfigData
:UDP配置数据:
typedef struct {
//
// Receiving Filters
//
BOOLEAN AcceptBroadcast;
BOOLEAN AcceptPromiscuous;
BOOLEAN AcceptAnyPort;
BOOLEAN AllowDuplicatePort;
//
// I/O parameters
//
UINT8 TypeOfService;
UINT8 TimeToLive;
BOOLEAN DoNotFragment;
UINT32 ReceiveTimeout;
UINT32 TransmitTimeout;
//
// Access Point
//
BOOLEAN UseDefaultAddress;
EFI_IPv4_ADDRESS StationAddress;
EFI_IPv4_ADDRESS SubnetMask;
UINT16 StationPort;
EFI_IPv4_ADDRESS RemoteAddress;
UINT16 RemotePort;
} EFI_UDP4_CONFIG_DATA;
TxTokens
、RxTokens
:描述收发数据的映射:
typedef struct {
LIST_ENTRY Used;
LIST_ENTRY Recycled;
UINTN Count;
} NET_MAP;
真正的Token是EFI_UDP4_COMPLETION_TOKEN
:
typedef struct {
EFI_EVENT Event;
EFI_STATUS Status;
union {
EFI_UDP4_RECEIVE_DATA *RxData;
EFI_UDP4_TRANSMIT_DATA *TxData;
} Packet;
} EFI_UDP4_COMPLETION_TOKEN;
RcvdDgramQue
、DeliveredDgramQue
:处理收发数据的队列。IpInfo
:底层IP4实例需要使用到的结构体:
///
/// The IP_IO_IP_INFO is used in IpIoSend() to override the default IP instance
/// in IP_IO.
///
typedef struct _IP_IO_IP_INFO {
EFI_IP_ADDRESS Addr;
IP_IO_IP_MASK PreMask;
LIST_ENTRY Entry;
EFI_HANDLE ChildHandle;
IP_IO_IP_PROTOCOL Ip;
IP_IO_IP_COMPLETION_TOKEN DummyRcvToken;
INTN RefCnt;
UINT8 IpVersion;
} IP_IO_IP_INFO;
从注释中可以看到发送数据时会使用到。
EFI_UDP4_PROTOCOL
UDP4通信的接口,该Protocol的结构体如下:
///
/// The EFI_UDP4_PROTOCOL defines an EFI UDPv4 Protocol session that can be used
/// by any network drivers, applications, or daemons to transmit or receive UDP packets.
/// This protocol instance can either be bound to a specified port as a service or
/// connected to some remote peer as an active client. Each instance has its own settings,
/// such as the routing table and group table, which are independent from each other.
///
struct _EFI_UDP4_PROTOCOL {
EFI_UDP4_GET_MODE_DATA GetModeData;
EFI_UDP4_CONFIGURE Configure;
EFI_UDP4_GROUPS Groups;
EFI_UDP4_ROUTES Routes;
EFI_UDP4_TRANSMIT Transmit;
EFI_UDP4_RECEIVE Receive;
EFI_UDP4_CANCEL Cancel;
EFI_UDP4_POLL Poll;
};
对应的实现:
EFI_UDP4_PROTOCOL mUdp4Protocol = {
Udp4GetModeData,
Udp4Configure,
Udp4Groups,
Udp4Routes,
Udp4Transmit,
Udp4Receive,
Udp4Cancel,
Udp4Poll
};
后面会介绍这些函数的实现。
Udp4.GetModeData
对应的实现是Udp4GetModeData()
,其代码实现:
EFI_STATUS
EFIAPI
Udp4GetModeData (
IN EFI_UDP4_PROTOCOL *This,
OUT EFI_UDP4_CONFIG_DATA *Udp4ConfigData OPTIONAL,
OUT EFI_IP4_MODE_DATA *Ip4ModeData OPTIONAL,
OUT EFI_MANAGED_NETWORK_CONFIG_DATA *MnpConfigData OPTIONAL,
OUT EFI_SIMPLE_NETWORK_MODE *SnpModeData OPTIONAL
)
{
if (Udp4ConfigData != NULL) {
//
// Set the Udp4ConfigData.
//
CopyMem (Udp4ConfigData, &Instance->ConfigData, sizeof (*Udp4ConfigData));
}
Ip = Instance->IpInfo->Ip.Ip4;
//
// Get the underlying Ip4ModeData, MnpConfigData and SnpModeData.
//
Status = Ip->GetModeData (Ip, Ip4ModeData, MnpConfigData, SnpModeData);
}
从这里可以看出,上层的网络协议可以获取到下层所有的模式数据。
对于UDP4来说,数据在UDP4_INSTANCE_DATA
的ConfigData
成员中。
Udp4.Configure
对应的实现是Udp4Configure()
,其代码实现:
EFI_STATUS
EFIAPI
Udp4Configure (
IN EFI_UDP4_PROTOCOL *This,
IN EFI_UDP4_CONFIG_DATA *UdpConfigData OPTIONAL
)
{
// 根据是否有配置存在两种情况,没有数据相当于重置
if (UdpConfigData != NULL) {
if (Instance->Configured) {
//
// The instance is already configured, try to do the re-configuration.
//
if (!Udp4IsReconfigurable (&Instance->ConfigData, UdpConfigData)) {
//
// If the new configuration data wants to change some unreconfigurable
// settings, return EFI_ALREADY_STARTED.
//
Status = EFI_ALREADY_STARTED;
goto ON_EXIT;
}
//
// Save the reconfigurable parameters.
//
Instance->ConfigData.TypeOfService = UdpConfigData->TypeOfService;
Instance->ConfigData.TimeToLive = UdpConfigData->TimeToLive;
Instance->ConfigData.DoNotFragment = UdpConfigData->DoNotFragment;
Instance->ConfigData.ReceiveTimeout = UdpConfigData->ReceiveTimeout;
Instance->ConfigData.TransmitTimeout = UdpConfigData->TransmitTimeout;
} else {
//
// Construct the Ip configuration data from the UdpConfigData.
//
Udp4BuildIp4ConfigData (UdpConfigData, &Ip4ConfigData);
//
// Configure the Ip instance wrapped in the IpInfo.
//
Status = IpIoConfigIp (Instance->IpInfo, &Ip4ConfigData);
if (EFI_ERROR (Status)) {
if (Status == EFI_NO_MAPPING) {
Instance->IsNoMapping = TRUE;
}
goto ON_EXIT;
}
Instance->IsNoMapping = FALSE;
//
// Save the configuration data.
//
CopyMem (&Instance->ConfigData, UdpConfigData, sizeof (Instance->ConfigData));
IP4_COPY_ADDRESS (&Instance->ConfigData.StationAddress, &Ip4ConfigData.StationAddress);
IP4_COPY_ADDRESS (&Instance->ConfigData.SubnetMask, &Ip4ConfigData.SubnetMask);
//
// Try to allocate the required port resource.
//
Status = Udp4Bind (&Udp4Service->ChildrenList, &Instance->ConfigData);
if (EFI_ERROR (Status)) {
//
// Reset the ip instance if bind fails.
//
IpIoConfigIp (Instance->IpInfo, NULL);
goto ON_EXIT;
}
//
// Pre calculate the checksum for the pseudo head, ignore the UDP length first.
//
CopyMem (&LocalAddr, &Instance->ConfigData.StationAddress, sizeof (IP4_ADDR));
CopyMem (&RemoteAddr, &Instance->ConfigData.RemoteAddress, sizeof (IP4_ADDR));
Instance->HeadSum = NetPseudoHeadChecksum (
LocalAddr,
RemoteAddr,
EFI_IP_PROTO_UDP,
0
);
Instance->Configured = TRUE;
}
} else {
//
// UdpConfigData is NULL, reset the instance.
//
Instance->Configured = FALSE;
Instance->IsNoMapping = FALSE;
//
// Reset the Ip instance wrapped in the IpInfo.
//
IpIoConfigIp (Instance->IpInfo, NULL);
//
// Cancel all the user tokens.
//
Instance->Udp4Proto.Cancel (&Instance->Udp4Proto, NULL);
//
// Remove the buffered RxData for this instance.
//
Udp4FlushRcvdDgram (Instance);
}
}
根据输入参数的不同,以及是否已经配置过,会走到不同的流程,此外,UDP4还会配置进一步调用IP4的接口进行配置。
Udp4.Transmit
对应的实现是Udp4Transmit()
,其代码实现:
EFI_STATUS
EFIAPI
Udp4Transmit (
IN EFI_UDP4_PROTOCOL *This,
IN EFI_UDP4_COMPLETION_TOKEN *Token
)
{
//
// Validate the Token, if the token is invalid return the error code.
//
Status = Udp4ValidateTxToken (Instance, Token);
if (EFI_ERROR (Status)) {
goto ON_EXIT;
}
if (EFI_ERROR (NetMapIterate (&Instance->TxTokens, Udp4TokenExist, Token)) ||
EFI_ERROR (NetMapIterate (&Instance->RxTokens, Udp4TokenExist, Token)))
{
//
// Try to find a duplicate token in the two token maps, if found, return
// EFI_ACCESS_DENIED.
//
Status = EFI_ACCESS_DENIED;
goto ON_EXIT;
}
TxData = Token->Packet.TxData;
//
// Create a net buffer to hold the user buffer and the udp header.
//
Packet = NetbufFromExt (
(NET_FRAGMENT *)TxData->FragmentTable,
TxData->FragmentCount,
UDP4_HEADER_SIZE,
0,
Udp4NetVectorExtFree,
NULL
);
if (Packet == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto ON_EXIT;
}
//
// Store the IpIo in ProtoData.
//
Udp4Service = Instance->Udp4Service;
*((UINTN *)&Packet->ProtoData[0]) = (UINTN)(Udp4Service->IpIo);
Udp4Header = (EFI_UDP_HEADER *)NetbufAllocSpace (Packet, UDP4_HEADER_SIZE, TRUE);
ASSERT (Udp4Header != NULL);
ConfigData = &Instance->ConfigData;
//
// Fill the udp header.
//
Udp4Header->SrcPort = HTONS (ConfigData->StationPort);
Udp4Header->DstPort = HTONS (ConfigData->RemotePort);
Udp4Header->Length = HTONS ((UINT16)Packet->TotalSize);
Udp4Header->Checksum = 0;
UdpSessionData = TxData->UdpSessionData;
IP4_COPY_ADDRESS (&Override.Ip4OverrideData.SourceAddress, &ConfigData->StationAddress);
if (UdpSessionData != NULL) {
//
// Set the SourceAddress, SrcPort and Destination according to the specified
// UdpSessionData.
//
if (!EFI_IP4_EQUAL (&UdpSessionData->SourceAddress, &mZeroIp4Addr)) {
IP4_COPY_ADDRESS (&Override.Ip4OverrideData.SourceAddress, &UdpSessionData->SourceAddress);
}
if (UdpSessionData->SourcePort != 0) {
Udp4Header->SrcPort = HTONS (UdpSessionData->SourcePort);
}
if (UdpSessionData->DestinationPort != 0) {
Udp4Header->DstPort = HTONS (UdpSessionData->DestinationPort);
}
CopyMem (&Source, &Override.Ip4OverrideData.SourceAddress, sizeof (IP4_ADDR));
CopyMem (&Destination, &UdpSessionData->DestinationAddress, sizeof (IP4_ADDR));
//
// calculate the pseudo head checksum using the overridden parameters.
//
HeadSum = NetPseudoHeadChecksum (
Source,
Destination,
EFI_IP_PROTO_UDP,
0
);
} else {
//
// UdpSessionData is NULL, use the address and port information previously configured.
//
CopyMem (&Destination, &ConfigData->RemoteAddress, sizeof (IP4_ADDR));
HeadSum = Instance->HeadSum;
}
//
// calculate the checksum.
//
Udp4Header->Checksum = Udp4Checksum (Packet, HeadSum);
if (Udp4Header->Checksum == 0) {
//
// If the calculated checksum is 0, fill the Checksum field with all ones.
//
Udp4Header->Checksum = 0xffff;
}
//
// Fill the IpIo Override data.
//
if (TxData->GatewayAddress != NULL) {
IP4_COPY_ADDRESS (&Override.Ip4OverrideData.GatewayAddress, TxData->GatewayAddress);
} else {
ZeroMem (&Override.Ip4OverrideData.GatewayAddress, sizeof (EFI_IPv4_ADDRESS));
}
Override.Ip4OverrideData.Protocol = EFI_IP_PROTO_UDP;
Override.Ip4OverrideData.TypeOfService = ConfigData->TypeOfService;
Override.Ip4OverrideData.TimeToLive = ConfigData->TimeToLive;
Override.Ip4OverrideData.DoNotFragment = ConfigData->DoNotFragment;
//
// Save the token into the TxToken map.
//
Status = NetMapInsertTail (&Instance->TxTokens, Token, Packet);
//
// Send out this datagram through IpIo.
//
IpDestAddr.Addr[0] = Destination;
Status = IpIoSend (
Udp4Service->IpIo,
Packet,
Instance->IpInfo,
Instance,
Token,
&IpDestAddr,
&Override
);
}
Udp4.Receive
对应的实现是Udp4Receive()
,其代码实现:
EFI_STATUS
EFIAPI
Udp4Receive (
IN EFI_UDP4_PROTOCOL *This,
IN EFI_UDP4_COMPLETION_TOKEN *Token
)
{
if (EFI_ERROR (NetMapIterate (&Instance->RxTokens, Udp4TokenExist, Token)) ||
EFI_ERROR (NetMapIterate (&Instance->TxTokens, Udp4TokenExist, Token)))
{
//
// Return EFI_ACCESS_DENIED if the specified token is already in the TxTokens or
// RxTokens map.
//
Status = EFI_ACCESS_DENIED;
goto ON_EXIT;
}
Token->Packet.RxData = NULL;
//
// Save the token into the RxTokens map.
//
Status = NetMapInsertTail (&Instance->RxTokens, Token, NULL);
if (EFI_ERROR (Status)) {
Status = EFI_NOT_READY;
goto ON_EXIT;
}
//
// If there is an icmp error, report it.
//
Udp4ReportIcmpError (Instance);
//
// Try to deliver the received datagrams.
//
Udp4InstanceDeliverDgram (Instance);
//
// Dispatch the DPC queued by the NotifyFunction of Token->Event.
//
DispatchDpc ();
}
同其它的网络协议中的Receive一样,重点是注册Token。
Udp4.Poll
对应的实现是Udp4Poll()
,其代码实现就是调用下一层的Poll:
EFI_STATUS
EFIAPI
Udp4Poll (
IN EFI_UDP4_PROTOCOL *This
)
{
Ip = Instance->IpInfo->Ip.Ip4;
//
// Invode the Ip instance consumed by the udp instance to do the poll operation.
//
return Ip->Poll (Ip);
}
代码示例
DHCP和DNS等都是使用UDP的,后面会进一步说明。