无OS的LWIP仅能使用RAW API,其中常用的与UDP相关的有这些: | RAW API函数 | 描述 |
---|---|---|
udp_new | Creates a new UDP pcb which can be used for UDP communication. The pcb is not active until it has either been bound to a local address or connected to a remote address. | |
– | 创建一个新的 UDP PCB(协议控制块),可以用于 UDP 通信。该 PCB 在绑定到本地地址或连接到远程地址之前是非活动状态。 | |
udp_remove | Removes and deallocates the pcb. | |
– | 移除并释放 PCB。 | |
udp_bind | Bind an UDP PCB. | |
– | 绑定一个 UDP PCB。 | |
udp_connect | Sets the remote end of the pcb. This function does not generate any network traffic, but only sets the remote address of the pcb. | |
– | 设置 PCB的远端地址。此函数不会产生任何网络流量,只是设置 PCB 的远程地址。 | |
udp_disconnect | Remove the remote end of the pcb. This function does not generate any network traffic, but only removes the remote address of the pcb. | |
– | 移除 PCB的远端地址。此函数不会产生任何网络流量,只是移除 PCB 的远程地址。 | |
udp_send | Sends the pbuf p using UDP. The pbuf is not deallocated. | |
– | 使用 UDP 发送 pbuf p。pbuf 不会被释放。 | |
udp_sendto | Send data to a specified address using UDP. | |
– | 使用 UDP 向指定地址发送数据。 | |
udp_recv | Set a receive callback for a UDP PCB. This callback will be called when receiving a datagram for the pcb. | |
– | 为一个UDP PCB设置一个接收回调函数。当接收到一个数据报时,这个回调函数将被调用。 |
其中PCB(协议控制块)就是一个用于UDP通信的对象。pbuf是以链表形式存在的数据包,接受的数据就是以pbuf形式储存,也需要先将数据装填进pbuf中再进行发送。
直接发送数据
让我们从简单粗暴的使用udp_sendto发送数据开始。
添加两个所需的头文件
需要PCB和pbuf的指针,targetIp、要发送的信息以及辅助变量。
创建新PCB
创建并填充pbuf并定期发送
虽然我不理解但是在之前的调试中我发现,udp_sendto可能会对pbuf进行修改:如果把填充pbuf放到while外面,也就是只填充一次之后一直发送这同一个包。那么从第二次发送开始,发送的内容前面会多出来一坨我也不知道是什么的东西。所以我放在了while中,每次发送重新分配空间。
整个main函数:
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
struct udp_pcb *upcb = NULL;
struct pbuf *sendPbuf = NULL;
ip_addr_t targetIP; //< 存放目标IP地址
const char* message = "Tick!\n";
int i = 0,j = 0;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
HAL_Delay(100);
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART3_UART_Init();
MX_USB_OTG_FS_PCD_Init();
MX_LWIP_Init();
/* USER CODE BEGIN 2 */
upcb = udp_new();
//< 创建新upcb
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
MX_LWIP_Process();
if (i>100)
{
sendPbuf = pbuf_alloc(PBUF_TRANSPORT, strlen(message), PBUF_RAM);
//< 为pbuf分配空间
memset(sendPbuf->payload,0,sendPbuf->len);
//< 清空pbuf数据
memcpy(sendPbuf->payload, message, strlen(message));
//< 填充数据
IP4_ADDR( &targetIP, 192, 168, 1, 40);
udp_sendto(upcb, sendPbuf, &targetIP, 51087);
pbuf_free(sendPbuf);
i = 0;
}
if (j>10000)
{
i++;
j = 0;
}
j++;
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
编译烧录测试,使用软件监听上述代码设置的51087端口,我们可以看到:
以非连接态通信
即使用udp_bind作为服务器接收任何任意发来的信息并回复。
先写一个回调函数,这里我直接原样发回去。
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
static void UDPCallback(void *arg,struct udp_pcb *upcb,struct pbuf *revBuf,const ip_addr_t *addr,u16_t port)
{
udp_sendto(upcb, revBuf, addr, port);
pbuf_free(revBuf);
}
/* USER CODE END 0 */
然后在main中绑定本地端口和接收回调函数。
完整的main函数:
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
struct udp_pcb *upcb = NULL;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
HAL_Delay(100);
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART3_UART_Init();
MX_USB_OTG_FS_PCD_Init();
MX_LWIP_Init();
/* USER CODE BEGIN 2 */
upcb = udp_new();
//< 创建新upcb
udp_bind(upcb, IP_ADDR_ANY, 8081);
//< 绑定本地ip和端口
udp_recv(upcb,UDPCallback, NULL);
//< 绑定回调函数
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
MX_LWIP_Process();
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
编译烧录测试,用测试软件向MCU发送UDP包,可以收到回复:
我另使用了手机一起进行了测试,在非连接态,当有多个任意地址的设备向MCU发送数据包时,都能进入回调函数并回复。
这里有一点可以注意到,回调函数的形参
存储的是数据包的发送方的地址和端口,所以这个函数的功能才是原路发回:从电脑(192.168.1.40)的56050端口发向MCU(192.168.1.118)的8081端口,回复则是从MCU(192.168.1.118)的8081端口发向从电脑(192.168.1.40)的56050端口,测试软件左侧的本地端口可以设为任意值。const ip_addr_t *addr,u16_t port
以连接态通信
即使用udp_connect作为客户端对特定地址进行通信。
使用udp_connect绑定远程地址后,即可直接使用udp_send向绑定的远程地址发送数据,且仅当接收到远程地址发来的数据包时进入回调函数。
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
struct udp_pcb *upcb = NULL;
struct pbuf *sendPbuf = NULL;
ip_addr_t targetIP; //< 存放目标IP地址
const char* message = "Tick!\n";
int i = 0,j = 0;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
HAL_Delay(100);
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART3_UART_Init();
MX_USB_OTG_FS_PCD_Init();
MX_LWIP_Init();
/* USER CODE BEGIN 2 */
upcb = udp_new();
//< 创建新upcb
udp_recv(upcb,UDPCallback, NULL);
//< 绑定回调函数
IP4_ADDR( &targetIP, 192, 168, 1, 40);
udp_connect(upcb, &targetIP, 51087);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
MX_LWIP_Process();
if (i>100)
{
sendPbuf = pbuf_alloc(PBUF_TRANSPORT, strlen(message), PBUF_RAM);
//< 为pbuf分配空间
memset(sendPbuf->payload,0,sendPbuf->len);
//< 清空pbuf数据
memcpy(sendPbuf->payload, message, strlen(message));
//< 填充数据
udp_send(upcb, sendPbuf);
pbuf_free(sendPbuf);
i = 0;
}
if (j>10000)
{
i++;
j = 0;
}
j++;
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
测试结果:
可以看到随机分配了一个62510端口,而我们向这个端口发送test也可以收到回复。我另使用了手机一起进行了测试,在连接态,仅有我的电脑(192.168.1.40)发送数据有回复。
在连接态也可以使用udp_bind绑定本地端口,这时候就是从固定的端口而不是随机的端口发送数据了,且依旧仅对绑定的远程地址的数据有响应。
有的教程可能会说udp_sendto相当于先udp_connect再udp_send,这也是不准确的。udp_connect是设置PCB的远端地址和端口,udp_sendto是向(可能是不同的)指定远端地址和端口发送信息但是不修改PCB绑定的远端地址和端口。