28 鸿蒙LiteOS RK2206 LwIP Raw API 实现无阻塞UDP双向通信

鸿蒙LiteOS RK2206 LwIP Raw API 实现无阻塞UDP双向通信

B站 配套视频教程【鸿蒙 LiteOS 实战 14】LwIP Raw API实现全自动端口分配+无阻塞双向收发

一、前言

在鸿蒙LiteOS嵌入式物联网项目开发中,UDP通信是设备数据上报、远程指令控制最常用的通信方式。

日常开发中多数开发者习惯使用标准Socket接口实现UDP收发,但Socket模式存在线程阻塞、收发相互干扰、资源占用偏高等问题。

本文基于RK2206开发板 + 鸿蒙LiteOS ,使用LwIP底层Raw原生API 开发UDP客户端,采用异步回调接收+独立线程发送 架构,彻底消除阻塞问题,实现无干扰双向通信,同时支持系统自动分配本地端口,无需手动指定端口号,适配性更强,是嵌入式高性能网络开发标准方案。

主要解决问题

  1. 如果解决阻塞的问题
  2. 如果做到同时收和发
  3. raw 和 socket有什么区别

二、传统Socket UDP存在的两大痛点

1. 阻塞等待问题

Socket提供的recvfrom属于阻塞式接收函数,在没有服务器数据下发时,当前线程会直接挂起休眠等待,无法执行其他业务逻辑,发送周期被接收状态严重影响,实时性大打折扣。

2. 双向并发收发困难

若想要同时收发数据,必须拆分多线程运行,由于多个线程共用同一个Socket文件描述符,属于共享临界资源,必须添加互斥锁进行保护,不仅增加代码复杂度,还容易出现资源竞争、程序异常崩溃等问题。

三、Raw API核心优势与解决思路

  1. 摒弃阻塞读取:采用LwIP内核异步回调机制,数据到达自动触发接收函数,程序无需主动轮询等待
  2. 收发逻辑解耦:接收交由内核回调处理,发送独立线程定时执行,两者互不干扰
  3. 无需线程锁:架构天然无共享资源竞争,省去互斥锁创建、加锁、解锁流程
  4. 端口自动分配:绑定端口传入0,由LwIP协议栈自动分配空闲本地端口,避免端口占用冲突
  5. 底层高性能:直接调用LwIP原生接口,跳过Socket封装层,运行效率更高、内存占用更小

四、实现架构:真正做到同时收发

  1. 数据接收端 :LwIP内核层回调函数,网络数据包抵达网卡后,协议栈自动调用注册好的接收回调函数完成数据解析,不占用业务线程
  2. 数据发送端:独立LiteOS任务线程,按照自定义周期定时向上位机服务器发送设备数据
  3. 网络前置条件:程序上电自动等待WiFi连接成功并获取有效IP地址,联网成功后再初始化UDP网络

整体架构完全并行运行,接收不打断发送,发送不影响接收,实现标准意义上的全双工无阻塞UDP通信

五、关键技术点详解

5.1 彻底解决线程阻塞

传统方案:任务主动调用接收函数 → 无数据则阻塞休眠

Raw方案:内核被动推送数据 → 有数据才执行接收逻辑,空闲状态线程全程正常运行

5.2 端口自动分配原理

调用udp_bind绑定本地端口时,第二个端口参数填写0 ,LwIP协议栈会自动从系统空闲端口池中选取未被占用的端口完成绑定,绑定成功后可直接从UDP控制块local_port成员读取实际分配端口。

c 复制代码
// 自动分配本地端口
udp_bind(g_udp_pcb, IP_ADDR_ANY, 0);
// 获取分配完成的端口号
g_local_port = g_udp_pcb->local_port;

5.3 LwIP数据缓冲区pbuf使用

LwIP所有网络数据收发都依赖pbuf数据包缓冲区,发送前申请内存存放数据,发送完成必须手动调用pbuf_free释放内存,避免出现内存泄漏。

六、完整可运行源码

c 复制代码
/*
 * Copyright (c) 2022 FuZhou Lockzhiner Electronic Co., Ltd. All rights reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *     http://www.apache.org/licenses/LICENSE-2.0
 */

#include "ohos_init.h"
#include "los_task.h"
#include "lz_hardware.h"
#include "config_network.h"
#include "lwip/udp.h"
#include "lwip/ip_addr.h"
#include "lwip/pbuf.h"
#include <string.h>
#include <stdio.h>

#define LOG_TAG "udp_raw"
#define OC_SERVER_IP "192.168.111.61"    // 上位机服务器IP
#define SERVER_PORT 20108                // 上位机服务器端口
#define SEND_INTERVAL_MS 1000            // 数据发送间隔

static struct udp_pcb *g_udp_pcb = NULL;
static ip4_addr_t g_server_ip;
static u16_t g_local_port = 0;         // 存储系统自动分配的本地端口
static unsigned int g_send_cnt = 0;     // 发送数据计数

/**
 * @brief 等待WiFi连接并获取本机IP地址
 * @param info WiFi信息结构体
 * @return 0连接成功,-1连接失败
 */
int udp_get_wifi_info(WifiLinkedInfo *info)
{
    int ret = -1;
    memset(info, 0, sizeof(WifiLinkedInfo));
    unsigned int retry = 20;

    while (retry--)
    {
        if (GetLinkedInfo(info) == WIFI_SUCCESS)
        {
            if (info->connState == WIFI_CONNECTED && info->ipAddress != 0)
            {
                LZ_HARDWARE_LOGD(LOG_TAG, "WiFi IP: %s", inet_ntoa(info->ipAddress));
                ret = 0;
                break;
            }
        }
        LOS_Msleep(1000);
    }
    return ret;
}

/**
 * @brief UDP数据异步接收回调函数
 * @param arg 自定义传入参数
 * @param pcb UDP控制块
 * @param p 网络数据缓冲区
 * @param addr 发送方IP地址
 * @param port 发送方端口号
 */
static void udp_recv_callback(void *arg, struct udp_pcb *pcb, struct pbuf *p,
                              const ip_addr_t *addr, u16_t port)
{
    if (p == NULL)
        return;

    printf("[Raw Recv] %.*s\n", p->len, (char *)p->payload);
    pbuf_free(p);   // 释放pbuf内存,防止内存泄漏
}

/**
 * @brief Raw API UDP数据发送函数
 */
void udp_raw_send(void)
{
    char buf[64];
    snprintf(buf, sizeof(buf), "RK2206 Raw API UDP msg: %u \r\n", g_send_cnt++);

    // 申请传输层pbuf缓冲区
    struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, strlen(buf), PBUF_RAM);
    if (p == NULL)
        return;

    // 拷贝发送数据
    memcpy(p->payload, buf, strlen(buf));
    // 指定IP与端口发送数据
    udp_sendto(g_udp_pcb, p, &g_server_ip, SERVER_PORT);
    printf("[Raw Send] %s\n", buf);

    pbuf_free(p);
}

/**
 * @brief 独立发送任务线程
 */
void udp_send_thread(void)
{
    while (1)
    {
        if (g_udp_pcb != NULL)
        {
            udp_raw_send();
        }
        LOS_Msleep(SEND_INTERVAL_MS);
    }
}

/**
 * @brief UDP Raw API初始化
 * @note 绑定端口填写0,实现系统自动分配本地端口
 */
void udp_raw_init(void)
{
    // 创建UDP协议控制块
    g_udp_pcb = udp_new();
    if (g_udp_pcb == NULL)
    {
        printf("udp_new failed!\n");
        return;
    }

    // 绑定任意网卡,端口0自动分配
    err_t err = udp_bind(g_udp_pcb, IP_ADDR_ANY, 0);
    if (err != ERR_OK)
    {
        printf("udp_bind failed!\n");
        return;
    }

    // 获取协议栈自动分配的本地端口
    g_local_port = g_udp_pcb->local_port;
    // 注册异步接收回调函数
    udp_recv(g_udp_pcb, udp_recv_callback, NULL);
    // 配置服务器IP地址
    IP4_ADDR(&g_server_ip, 192, 168, 111, 61);

    printf("===== UDP Raw API 初始化完成 =====\n");
    printf("本地端口(系统自动分配): %d\n", g_local_port);
    printf("目标服务器地址: %s:%d\n", OC_SERVER_IP, SERVER_PORT);
}

/**
 * @brief UDP网络业务主流程
 */
void udp_raw_example_process(void)
{
    WifiLinkedInfo info;
    // 循环等待WiFi联网成功
    while (udp_get_wifi_info(&info) != 0)
    {
        LOS_Msleep(500);
    }

    // 初始化LwIP Raw UDP
    udp_raw_init();

    // 创建独立发送任务
    unsigned int send_tid;
    TSK_INIT_PARAM_S send_task = {0};
    send_task.pfnTaskEntry = (TSK_ENTRY_FUNC)udp_send_thread;
    send_task.uwStackSize = 8192;
    send_task.pcName = "udp_send_task";
    send_task.usTaskPrio = 24;
    LOS_TaskCreate(&send_tid, &send_task);
}

/**
 * @brief 系统开机自启动入口
 */
void udp_client_raw_example(void)
{
    unsigned int thread_id;
    TSK_INIT_PARAM_S task = {0};

    task.pfnTaskEntry = (TSK_ENTRY_FUNC)udp_raw_example_process;
    task.uwStackSize = 10240;
    task.pcName = "udp_raw_main";
    task.usTaskPrio = 23;
    LOS_TaskCreate(&thread_id, &task);
}

// 鸿蒙系统应用自动初始化
APP_FEATURE_INIT(udp_client_raw_example);

七、程序运行效果

  1. 设备上电自动连接WiFi,打印本机局域网IP

  2. 联网成功后初始化UDP协议栈,打印系统自动分配的本地端口

  3. 每秒主动向服务器推送自定义设备消息

  4. 上位机下发指令可实时被回调函数捕获打印

    WiFi IP: 192.168.111.105
    ===== UDP Raw API 初始化完成 =====
    本地端口(系统自动分配): 52010
    目标服务器地址: 192.168.111.61:20108
    [Raw Send] RK2206 Raw API UDP msg: 0
    [Raw Send] RK2206 Raw API UDP msg: 1
    [Raw Recv] Server test data
    [Raw Send] RK2206 Raw API UDP msg: 2

八、LwIP Raw API 与 Socket API详细对比

对比维度 标准Socket API LwIP Raw原生API
接收模式 主动调用函数阻塞等待 内核异步回调被动接收
阻塞特性 存在阻塞,影响业务时序 全程无阻塞,线程运行流畅
并发收发 多线程需互斥锁保护 天然线程安全,无需加锁
调用层级 LwIP上层封装接口 直接调用协议栈底层接口
运行性能 中等,存在封装损耗 性能最优,资源占用极低
端口使用 手动指定固定端口 支持填0自动分配空闲端口
开发难度 入门简单,上手快 熟悉协议栈后开发更灵活
适用场景 快速调试、简易通信项目 工业物联网、高并发、低功耗设备

九、开发总结

  1. 采用LwIP Raw异步回调彻底解决传统UDP接收阻塞问题,保障设备业务逻辑稳定运行
  2. 发送任务+内核回调的分离架构,轻松实现UDP全双工同时收发,互不干扰
  3. 绑定端口置0实现协议栈自动分配端口,有效解决多设备同网段端口冲突问题
  4. Raw API跳过Socket封装层,更贴合嵌入式底层开发需求,在低配置IoT芯片中优势明显
  5. 开发使用pbuf缓冲区务必及时释放,长期运行项目必须做好内存管理,避免内存泄漏

本文代码完全适配RK2206鸿蒙LiteOS原生工程,修改服务器IP与端口即可直接编译烧录,可直接用于物联网数据采集、无线遥控、局域网设备通信等实际项目开发。

十、思考

  • 如果客户端不给服务端发数据,服务端能发送数据给客户端吗?
相关推荐
2501_9197490325 分钟前
华为鸿蒙免费视频播放器—小羊免费播放器
华为·harmonyos·鸿蒙
贾伟康1 小时前
【天体运行模拟|15】HarmonyOS ArkTS 本地状态持久化实战:让保存、删除和页面返回后的数据即时一致
harmonyos·arkts·数据持久化·状态管理·preferences
黑臂麒麟1 小时前
Harmony鸿蒙实战应用10:随手账本——发布检查与最终验收
华为·app·arkts·鸿蒙
贾伟康2 小时前
【天体运行模拟|16】HarmonyOS ArkTS 多设备布局实战:适配手机、平板和 PC/2in1 的窗口变化
harmonyos·arkts·arkui·响应式布局·多设备适配
黑臂麒麟2 小时前
HarmonyOS鸿蒙实战应用6:随手账本——Preferences本地持久化
数据库·华为·app·鸿蒙
大雷神2 小时前
HarmonyOS AR Engine高精几何重建实战——扫描纸盒并测量体积
华为·ar·harmonyos
用户0934077735142 小时前
HarmonyOS WPS Open SDK 实践:registerApp 鉴权与就绪门禁
android·typescript·harmonyos
特立独行的猫a3 小时前
仓颉语言原生 Coding Agent:cjh · 仓颉语言实现的 Harness
ai·agent·harmonyos·仓颉·cangjie·harness
大锅盖13 小时前
警示橙如何驱动巡检闭环?ArkUI 物业安全平台的声明式实现
安全·华为·harmonyos
贾伟康3 小时前
【时光清单|03】HarmonyOS ArkTS 提醒服务实战:计算触发时间并防止重复注册
harmonyos·arkts·后台任务·幂等设计·通知提醒