4 changed files with 1067 additions and 1 deletions
@ -0,0 +1,874 @@ |
|||
/******************************************************************************
|
|||
|
|||
版权所有 (C), 2018-2099, Radkil |
|||
|
|||
****************************************************************************** |
|||
文 件 名 : rd_udp.c |
|||
版 本 号 : 初稿 |
|||
作 者 : Radkil |
|||
生成日期 : 2026年11月3日 |
|||
最近修改 : |
|||
功能描述 : 跨平台 UDP 传输实现 (TComCtrl 集成, 与 rd_serial 同构) |
|||
USE_LWIP → lwip/sockets.h (需 tcpip 栈已启动) |
|||
BUILD_PLATFORM_LINUX → POSIX socket + pthread |
|||
BUILD_PLATFORM_WIN32 → winsock2 |
|||
与 Linux 标准 BSD socket 的差异收敛到本文件内: |
|||
- 头文件按平台宏分支 |
|||
- IP 解析不依赖 inet_addr/inet_pton (sscanf + htonl) |
|||
- 接收超时不用 SO_RCVTIMEO (lwip 为 int 毫秒, Linux 为 |
|||
struct timeval), 统一 fcntl(O_NONBLOCK) + 让步轮询 |
|||
UDP 广播设备发现 (beacon, 见 rd_udp.h 宏): |
|||
- 独立发现 socket 绑 RD_UDP_DISCOVERY_PORT, 周期广播 beacon |
|||
- peer 表记录 (源IP, data_port), 数据帧扇出给全部活 peer |
|||
- m_psRemoteIp 非空时固定直连与发现扇出并存 |
|||
|
|||
修改历史 : |
|||
1.日 期 : 2026年11月3日 |
|||
作 者 : Radkil |
|||
修改内容 : 创建文件 |
|||
|
|||
2.日 期 : 2026年11月4日 |
|||
作 者 : Radkil |
|||
修改内容 : 新增 beacon 设备发现: 发现 socket+线程/peer 表/ |
|||
发送扇出, m_psRemoteIp 可空 (空 = 纯发现模式) |
|||
|
|||
******************************************************************************/ |
|||
|
|||
/* ─── 平台头文件 ─────────────────────────────────────────────────────── */ |
|||
/* __ANDROID__ (NDK 自动定义) 并入 POSIX 分支: Bionic 的 socket/fcntl/errno
|
|||
* 与 Linux 同名同义, 头文件一致 (与 rd_thread.h 的平台分支同模式); |
|||
* 不要用 BUILD_PLATFORM_LINUX 识别安卓 —— 会误激活 rd_serial 的 termios 路径 */ |
|||
#include "common.h" |
|||
#ifdef USE_LWIP |
|||
#include "lwip/sockets.h" |
|||
#include "lwip/netdb.h" |
|||
/* robot 适配: 线程创建走固件既有 CMSIS-RTOS2 (与 My_freeRTOS.c talnet 任务同款),
|
|||
* 不引入 Rd_ThreadCreate 跨平台抽象; 蹦床终止用 FreeRTOS vTaskDelete |
|||
* (头文件顺序与 My_freeRTOS.c 完全一致) */ |
|||
#include "cmsis_os.h" |
|||
#include "cmsis_os2.h" |
|||
#include "FreeRTOS.h" |
|||
#include "task.h" |
|||
#endif /* USE_LWIP */ |
|||
|
|||
#if defined(BUILD_PLATFORM_LINUX) || defined(__ANDROID__) |
|||
#include <stdio.h> |
|||
#include <stdlib.h> |
|||
#include <unistd.h> |
|||
#include <fcntl.h> |
|||
#include <string.h> |
|||
#include <errno.h> |
|||
#include <sys/socket.h> |
|||
#include <netinet/in.h> |
|||
#include <arpa/inet.h> |
|||
#endif /* BUILD_PLATFORM_LINUX || __ANDROID__ */ |
|||
|
|||
#ifdef BUILD_PLATFORM_WIN32 |
|||
#include <winsock2.h> |
|||
#include <ws2tcpip.h> |
|||
#include <stdio.h> |
|||
#endif /* BUILD_PLATFORM_WIN32 */ |
|||
|
|||
/* ─── 项目头文件 ─────────────────────────────────────────────────────── */ |
|||
#include "rd_thread.h" |
|||
#include "rd_time.h" |
|||
#include "com.h" |
|||
#include "rd_udp.h" |
|||
|
|||
/*==============================================*
|
|||
* 常量/宏 * |
|||
*==============================================*/ |
|||
#define RD_UDP_RECV_BUF_SIZE 2048 /* 接收线程单次读取缓冲(单报文上限) */ |
|||
|
|||
/* ── beacon 报文布局 (22B 二进制, 周期重发自愈 → 无需 CRC) ──
|
|||
* [0..1] magic "RD" (0x52 0x44) 与 55 55 数据帧天然区分 |
|||
* [2] ver = 1 |
|||
* [3] type = 1 (beacon) |
|||
* [4..7] instance_id (u32, 网络序) 防自环: 同机广播自己也会收到一份 |
|||
* [8..9] data_port (htons) 本端数据帧端口 (对端据此回发数据) |
|||
* [10..21] name[12] 设备名截断 (可选, 空补 0) */ |
|||
#define RD_UDP_BEACON_SIZE 22 |
|||
#define RD_UDP_BEACON_MAGIC_0 0x52 /* 'R' */ |
|||
#define RD_UDP_BEACON_MAGIC_1 0x44 /* 'D' */ |
|||
#define RD_UDP_BEACON_VER 1 |
|||
#define RD_UDP_BEACON_TYPE 1 /* 1 = beacon */ |
|||
#define RD_UDP_NAME_MAX 12 /* beacon name 字段宽度 */ |
|||
|
|||
/* 未识别平台: 不提供 socket 环境, 创建直接失败 */ |
|||
#if !defined(USE_LWIP) && !defined(BUILD_PLATFORM_LINUX) && \ |
|||
!defined(BUILD_PLATFORM_WIN32) && !defined(__ANDROID__) |
|||
#define RD_UDP_NO_PLATFORM 1 |
|||
#endif |
|||
|
|||
#ifndef RD_UDP_NO_PLATFORM |
|||
|
|||
#if defined(BUILD_PLATFORM_WIN32) |
|||
#define RD_UDP_SOCK_ERR() WSAGetLastError() |
|||
/* Win32 错误码走 WSA* 命名空间, 与 errno 体系不同 */ |
|||
#define RD_UDP_ERR_YIELD(e) ((e) == WSAEWOULDBLOCK || (e) == WSAEINTR || (e) == WSAECONNREFUSED) |
|||
#define RD_UDP_CLOSE(s) closesocket(s) |
|||
#else |
|||
#define RD_UDP_SOCK_ERR() errno |
|||
/* ECONNREFUSED: Linux 对端未监听时 ICMP 异步错误, 容忍不退出 */ |
|||
#define RD_UDP_ERR_YIELD(e) ((e) == EAGAIN || (e) == EWOULDBLOCK || (e) == EINTR || (e) == ECONNREFUSED) |
|||
#define RD_UDP_CLOSE(s) close(s) |
|||
#endif |
|||
|
|||
/* ═══════════════════════════════════════════════════════════════════════ *
|
|||
* 平台特定 UserData 结构 * |
|||
* ═══════════════════════════════════════════════════════════════════════ */ |
|||
/* beacon 发现的活 peer (design: 同 ip+port 刷新 last_seen, 超时剔除) */ |
|||
typedef struct |
|||
{ |
|||
uint32_t m_uiIp; /* 对端 IP (网络序, beacon 源地址) */ |
|||
uint16_t m_uiDataPort; /* beacon 携带的对端数据帧端口 */ |
|||
uint32_t m_uiLastSeen; /* 最近活跃时刻 Rd_GetTime() (ms) */ |
|||
char m_sName[RD_UDP_NAME_MAX + 1]; |
|||
} TRdUdpPeer; |
|||
|
|||
typedef struct |
|||
{ |
|||
int m_iRxSocket; /* 接收 socket (bind 本地端口) */ |
|||
int m_iTxSocket; /* 发送 socket (可复用 m_iRxSocket) */ |
|||
struct sockaddr_in m_stRemoteAddr; /* 固定对端地址 (发送目的, 可无效) */ |
|||
volatile int m_iRunning; /* 接收/发现线程运行标志 */ |
|||
/* ── beacon 设备发现 ── */ |
|||
uint32_t m_uiInstanceId; /* 本实例标识 (create 时生成, 防自环) */ |
|||
uint16_t m_uiDataPort; /* 本端数据帧端口 (beacon 携带, 0=只发不收) */ |
|||
char m_sName[RD_UDP_NAME_MAX + 1]; /* 设备名 (beacon 携带, 可空) */ |
|||
struct sockaddr_in m_stDiscAddr; /* beacon 广播目的 255.255.255.255:发现端口 */ |
|||
int m_iDiscSocket; /* 发现接收 socket (-1 = 发现降级关闭) */ |
|||
int m_iFixedValid; /* 固定对端有效 (m_psRemoteIp 非空) */ |
|||
int m_iBcnWarned; /* beacon 广播失败已警告 (仅首次, 防刷屏) */ |
|||
TRdUdpPeer m_astPeers[RD_UDP_PEER_MAX]; /* 无锁: 发现线程单写 +
|
|||
发送线程读, u32/u16 对齐写在双平台均原子, demo 级容忍 */ |
|||
} TUdpUserData; |
|||
|
|||
/* ═══════════════════════════════════════════════════════════════════════ *
|
|||
* 文件级全局 (cbk_ComSend 签名限制, 单链路约束) * |
|||
* ═══════════════════════════════════════════════════════════════════════ */ |
|||
static TComCtrl *g_ptUdpComCtrl = NULL; |
|||
|
|||
/* ═══════════════════════════════════════════════════════════════════════ *
|
|||
* 内部辅助 * |
|||
* ═══════════════════════════════════════════════════════════════════════ */ |
|||
|
|||
/* 接收线程让步 (非阻塞轮询间隔) */ |
|||
static void rd_udp_yield(void) |
|||
{ |
|||
#if defined(BUILD_PLATFORM_LINUX) || defined(__ANDROID__) |
|||
usleep(1000); |
|||
#elif defined(BUILD_PLATFORM_WIN32) |
|||
Sleep(1); |
|||
#else |
|||
Rd_Delay(1); |
|||
#endif |
|||
} |
|||
|
|||
/* 点分十进制 → sockaddr_in (不依赖 inet_addr/inet_pton, 全平台一致) */ |
|||
static int rd_udp_fill_remote(struct sockaddr_in *_pstAddr, |
|||
const char *_psIp, int _iPort) |
|||
{ |
|||
unsigned int a = 0, b = 0, c = 0, d = 0; |
|||
|
|||
if (_pstAddr == NULL || _psIp == NULL || |
|||
RD_STRLEN(_psIp) >= RD_UDP_IP_MAX - 1) |
|||
{ |
|||
return RD_FAILURE; |
|||
} |
|||
if (sscanf(_psIp, "%u.%u.%u.%u", &a, &b, &c, &d) != 4 || |
|||
a > 255 || b > 255 || c > 255 || d > 255) |
|||
{ |
|||
return RD_FAILURE; |
|||
} |
|||
|
|||
RD_MEMSET(_pstAddr, 0, sizeof(*_pstAddr)); |
|||
_pstAddr->sin_family = AF_INET; |
|||
_pstAddr->sin_port = htons((uint16_t)_iPort); |
|||
_pstAddr->sin_addr.s_addr = htonl((a << 24) | (b << 16) | (c << 8) | d); |
|||
return RD_SUCCESS; |
|||
} |
|||
|
|||
/* 接收 socket 非阻塞 (lwip fcntl 支持 F_GETFL/F_SETFL/O_NONBLOCK) */ |
|||
static int rd_udp_set_nonblock(int _iSocket) |
|||
{ |
|||
#if defined(BUILD_PLATFORM_WIN32) |
|||
u_long ulNonBlock = 1; |
|||
return (ioctlsocket(_iSocket, FIONBIO, &ulNonBlock) == 0) ? RD_SUCCESS : RD_FAILURE; |
|||
#else |
|||
int iFlags = fcntl(_iSocket, F_GETFL, 0); |
|||
if (iFlags < 0) return RD_FAILURE; |
|||
return (fcntl(_iSocket, F_SETFL, iFlags | O_NONBLOCK) >= 0) ? RD_SUCCESS : RD_FAILURE; |
|||
#endif |
|||
} |
|||
|
|||
/* ─────────────────── beacon 设备发现内部辅助 ─────────────────── */ |
|||
|
|||
/* 网络序 IP → "a.b.c.d" (静态缓冲, 仅日志用, 用完即弃) */ |
|||
static const char *rd_udp_ip_str(uint32_t _uiIpNet) |
|||
{ |
|||
static char s_szIp[16]; |
|||
uint32_t ui = ntohl(_uiIpNet); |
|||
snprintf(s_szIp, sizeof(s_szIp), "%u.%u.%u.%u", |
|||
(unsigned)((ui >> 24) & 0xFF), (unsigned)((ui >> 16) & 0xFF), |
|||
(unsigned)((ui >> 8) & 0xFF), (unsigned)(ui & 0xFF)); |
|||
return s_szIp; |
|||
} |
|||
|
|||
/* 实例标识: 地址/时刻/本地数据端口混合散列 (不依赖平台 rand, lwip 无 rand);
|
|||
* 并存实例必然本地端口不同 (否则数据 bind 冲突), 身份不依赖远端配置; |
|||
* 两活实例撞号只会互相当自环过滤掉 (发现失效), 无其他副作用 */ |
|||
static uint32_t rd_udp_make_instance_id(const TUdpUserData *_ptUd, uint16_t _hwDataPort) |
|||
{ |
|||
uint32_t uiId = (uint32_t)(uintptr_t)_ptUd; |
|||
uiId ^= Rd_GetTime(); |
|||
uiId ^= (uint32_t)_hwDataPort << 16; |
|||
uiId *= 2654435761U; /* Knuth 乘法散列, 拉开低位 */ |
|||
return (uiId != 0) ? uiId : 0x52440001U; /* 避开全 0 */ |
|||
} |
|||
|
|||
/* beacon 入账: 同 ip+port 静默刷新 last_seen, 新发现打 log_i;
|
|||
* 表满时覆盖 last_seen 最旧的项 */ |
|||
static void rd_udp_peer_update(TUdpUserData *_ptUd, uint32_t _uiIp, |
|||
uint16_t _hwDataPort, const char *_psName, |
|||
uint32_t _uiNow) |
|||
{ |
|||
TRdUdpPeer *ptEmpty = NULL, *ptOldest = NULL, *ptSlot = NULL; |
|||
int i; |
|||
|
|||
for (i = 0; i < RD_UDP_PEER_MAX; i++) |
|||
{ |
|||
TRdUdpPeer *pt = &_ptUd->m_astPeers[i]; |
|||
if (pt->m_uiIp == 0) /* 空位 (0.0.0.0 不会是合法对端) */ |
|||
{ |
|||
if (ptEmpty == NULL) ptEmpty = pt; |
|||
continue; |
|||
} |
|||
if (pt->m_uiIp == _uiIp && pt->m_uiDataPort == _hwDataPort) |
|||
{ |
|||
pt->m_uiLastSeen = _uiNow; /* 已知 peer */ |
|||
return; |
|||
} |
|||
if (ptOldest == NULL || pt->m_uiLastSeen < ptOldest->m_uiLastSeen) |
|||
{ |
|||
ptOldest = pt; |
|||
} |
|||
} |
|||
|
|||
ptSlot = (ptEmpty != NULL) ? ptEmpty : ptOldest; |
|||
if (ptSlot == NULL) return; |
|||
RD_MEMSET(ptSlot, 0, sizeof(*ptSlot)); |
|||
ptSlot->m_uiIp = _uiIp; |
|||
ptSlot->m_uiDataPort = _hwDataPort; |
|||
ptSlot->m_uiLastSeen = _uiNow; |
|||
if (_psName != NULL) |
|||
{ |
|||
uint32_t uiLen = (uint32_t)RD_STRLEN(_psName); |
|||
if (uiLen > RD_UDP_NAME_MAX) uiLen = RD_UDP_NAME_MAX; |
|||
RD_MEMCPY(ptSlot->m_sName, _psName, uiLen); /* 目标已清零, 天然 NUL 结尾 */ |
|||
} |
|||
log_i("udp discovery: peer %s:%u (%s)", rd_udp_ip_str(_uiIp), |
|||
(unsigned)_hwDataPort, ptSlot->m_sName[0] ? ptSlot->m_sName : "?"); |
|||
} |
|||
|
|||
/* 超时剔除 (发现线程每轮顺手执行, 表仅 RD_UDP_PEER_MAX 项) */ |
|||
static void rd_udp_peer_sweep(TUdpUserData *_ptUd, uint32_t _uiNow) |
|||
{ |
|||
int i; |
|||
for (i = 0; i < RD_UDP_PEER_MAX; i++) |
|||
{ |
|||
TRdUdpPeer *pt = &_ptUd->m_astPeers[i]; |
|||
if (pt->m_uiIp == 0) continue; |
|||
/* uint32 回绕安全减法 */ |
|||
if ((uint32_t)(_uiNow - pt->m_uiLastSeen) > RD_UDP_PEER_TIMEOUT_MS) |
|||
{ |
|||
log_i("udp discovery: peer %s:%u (%s) timeout", |
|||
rd_udp_ip_str(pt->m_uiIp), (unsigned)pt->m_uiDataPort, |
|||
pt->m_sName[0] ? pt->m_sName : "?"); |
|||
RD_MEMSET(pt, 0, sizeof(*pt)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/* 组装并广播 beacon (复用 tx socket, 已设 SO_BROADCAST;
|
|||
* 周期重发自愈 → 失败仅首次警告防刷屏) */ |
|||
static void rd_udp_beacon_send(TUdpUserData *_ptUd) |
|||
{ |
|||
uint8_t ucBcn[RD_UDP_BEACON_SIZE]; |
|||
uint32_t uiIdNet = htonl(_ptUd->m_uiInstanceId); |
|||
uint16_t hwPortNet = htons(_ptUd->m_uiDataPort); |
|||
int iSent; |
|||
|
|||
if (_ptUd->m_iTxSocket < 0) return; |
|||
|
|||
RD_MEMSET(ucBcn, 0, sizeof(ucBcn)); |
|||
ucBcn[0] = RD_UDP_BEACON_MAGIC_0; |
|||
ucBcn[1] = RD_UDP_BEACON_MAGIC_1; |
|||
ucBcn[2] = RD_UDP_BEACON_VER; |
|||
ucBcn[3] = RD_UDP_BEACON_TYPE; |
|||
RD_MEMCPY(&ucBcn[4], &uiIdNet, sizeof(uiIdNet)); |
|||
RD_MEMCPY(&ucBcn[8], &hwPortNet, sizeof(hwPortNet)); |
|||
if (_ptUd->m_sName[0] != '\0') |
|||
{ |
|||
/* name 字段定长 12B, 源串已保证 NUL 结尾, 不足补 0 (RD_MEMSET 已清) */ |
|||
RD_MEMCPY(&ucBcn[10], _ptUd->m_sName, |
|||
RD_STRLEN(_ptUd->m_sName) < RD_UDP_NAME_MAX |
|||
? RD_STRLEN(_ptUd->m_sName) : RD_UDP_NAME_MAX); |
|||
} |
|||
|
|||
iSent = (int)sendto(_ptUd->m_iTxSocket, (char *)ucBcn, sizeof(ucBcn), 0, |
|||
(struct sockaddr *)&_ptUd->m_stDiscAddr, |
|||
sizeof(_ptUd->m_stDiscAddr)); |
|||
if (iSent < 0 && !_ptUd->m_iBcnWarned) |
|||
{ |
|||
_ptUd->m_iBcnWarned = 1; |
|||
log_w("udp discovery: beacon broadcast failed, err=%d (discovery degraded)", |
|||
RD_UDP_SOCK_ERR()); |
|||
} |
|||
} |
|||
|
|||
/* 收到疑似 beacon: 校验 magic/ver/type → 自环过滤 → 入账;
|
|||
* 数据地址取 beacon 源 IP, 数据端口取报文 data_port (非源端口) */ |
|||
static void rd_udp_beacon_on_rx(TUdpUserData *_ptUd, const uint8_t *_pBuf, |
|||
uint32_t _iLen, const struct sockaddr_in *_pstFrom) |
|||
{ |
|||
uint32_t uiId = 0; |
|||
uint16_t hwPortNet = 0; |
|||
char sName[RD_UDP_NAME_MAX + 1]; |
|||
|
|||
if (_iLen < RD_UDP_BEACON_SIZE || |
|||
_pBuf[0] != RD_UDP_BEACON_MAGIC_0 || _pBuf[1] != RD_UDP_BEACON_MAGIC_1 || |
|||
_pBuf[2] != RD_UDP_BEACON_VER || _pBuf[3] != RD_UDP_BEACON_TYPE) |
|||
{ |
|||
return; /* 非 beacon, 发现通道上直接忽略 */ |
|||
} |
|||
|
|||
RD_MEMCPY(&uiId, &_pBuf[4], sizeof(uiId)); |
|||
if (ntohl(uiId) == _ptUd->m_uiInstanceId) return; /* 防自环: 同机广播回环 */ |
|||
|
|||
RD_MEMCPY(&hwPortNet, &_pBuf[8], sizeof(hwPortNet)); |
|||
RD_MEMSET(sName, 0, sizeof(sName)); |
|||
RD_MEMCPY(sName, &_pBuf[10], RD_UDP_NAME_MAX); /* 定宽字段, 已清零保 NUL */ |
|||
|
|||
rd_udp_peer_update(_ptUd, _pstFrom->sin_addr.s_addr, |
|||
ntohs(hwPortNet), sName, Rd_GetTime()); |
|||
} |
|||
|
|||
/* ═══════════════════════════════════════════════════════════════════════ *
|
|||
* 发送回调 (cbk_ComSend → 全局 TComCtrl → UserData → 扇出 sendto) * |
|||
* ═══════════════════════════════════════════════════════════════════════ */ |
|||
/* 扇出策略: 固定对端 (remote ip 非空) + beacon 发现的全部活 peer;
|
|||
* 任一路成功即返回字节数, 全失败返回 RD_FAILURE (cbk_ComSend 语义不变) */ |
|||
static int udp_send_impl(char *_pBuffer, uint32_t _iSize) |
|||
{ |
|||
if (g_ptUdpComCtrl == NULL) return RD_FAILURE; |
|||
TUdpUserData *ptUd = (TUdpUserData *)(g_ptUdpComCtrl->m_pUserData); |
|||
if (ptUd == NULL || ptUd->m_iTxSocket < 0) return RD_FAILURE; |
|||
|
|||
int iBest = -1; |
|||
uint32_t uiNow = Rd_GetTime(); |
|||
int i; |
|||
|
|||
/* 1. 固定对端 (m_psRemoteIp 非空时保留现状) */ |
|||
if (ptUd->m_iFixedValid) |
|||
{ |
|||
int iSent = (int)sendto(ptUd->m_iTxSocket, _pBuffer, _iSize, 0, |
|||
(struct sockaddr *)&ptUd->m_stRemoteAddr, |
|||
sizeof(ptUd->m_stRemoteAddr)); |
|||
if (iSent > 0) |
|||
{ |
|||
iBest = iSent; |
|||
} |
|||
} |
|||
|
|||
/* 2. 发现的活 peer 扇出 */ |
|||
for (i = 0; i < RD_UDP_PEER_MAX; i++) |
|||
{ |
|||
TRdUdpPeer *pt = &ptUd->m_astPeers[i]; |
|||
struct sockaddr_in stTo; |
|||
int iSent; |
|||
|
|||
if (pt->m_uiIp == 0 || pt->m_uiDataPort == 0) continue; /* 空位/无数据口 */ |
|||
if ((uint32_t)(uiNow - pt->m_uiLastSeen) > RD_UDP_PEER_TIMEOUT_MS) continue; |
|||
|
|||
/* 【去重】固定对端同时被 beacon 发现时 (ip+data_port 相同), 若不再跳过
|
|||
* 会对同一地址双发每个报文 —— ymodem 协议字节双发直接错位/损坏文件. |
|||
* 字节序: m_uiIp 网络序直存, data_port 为入表 ntohs 后的主机序 */ |
|||
if (ptUd->m_iFixedValid && |
|||
pt->m_uiIp == ptUd->m_stRemoteAddr.sin_addr.s_addr && |
|||
pt->m_uiDataPort == ntohs(ptUd->m_stRemoteAddr.sin_port)) |
|||
{ |
|||
continue; /* peer 即固定对端, 第 1 步固定发送已覆盖 */ |
|||
} |
|||
|
|||
RD_MEMSET(&stTo, 0, sizeof(stTo)); |
|||
stTo.sin_family = AF_INET; |
|||
stTo.sin_port = htons(pt->m_uiDataPort); |
|||
stTo.sin_addr.s_addr = pt->m_uiIp; |
|||
iSent = (int)sendto(ptUd->m_iTxSocket, _pBuffer, _iSize, 0, |
|||
(struct sockaddr *)&stTo, sizeof(stTo)); |
|||
if (iSent > 0 && iBest < 0) |
|||
{ |
|||
iBest = iSent; |
|||
} |
|||
} |
|||
|
|||
if (iBest < 0) |
|||
{ |
|||
log_e("udp_send_impl: sendto all failed (fixed=%d), err=%d", |
|||
ptUd->m_iFixedValid, RD_UDP_SOCK_ERR()); |
|||
return RD_FAILURE; |
|||
} |
|||
return iBest; |
|||
} |
|||
|
|||
/* ═══════════════════════════════════════════════════════════════════════ *
|
|||
* 接收线程 (非阻塞 recvfrom + 让步轮询) * |
|||
* ═══════════════════════════════════════════════════════════════════════ */ |
|||
#if defined(BUILD_PLATFORM_WIN32) |
|||
static DWORD WINAPI udp_recv_thread(LPVOID _lpParam) |
|||
#else |
|||
static void *udp_recv_thread(void *_pArg) |
|||
#endif |
|||
{ |
|||
#if defined(BUILD_PLATFORM_WIN32) |
|||
TComCtrl *ptComCtrl = (TComCtrl *)_lpParam; |
|||
#else |
|||
TComCtrl *ptComCtrl = (TComCtrl *)_pArg; |
|||
#endif |
|||
TUdpUserData *ptUd = (TUdpUserData *)(ptComCtrl->m_pUserData); |
|||
uint8_t ucBuf[RD_UDP_RECV_BUF_SIZE]; |
|||
|
|||
while (ptUd->m_iRunning) |
|||
{ |
|||
struct sockaddr_in stFrom; |
|||
socklen_t iFromLen = sizeof(stFrom); |
|||
int iRd = (int)recvfrom(ptUd->m_iRxSocket, (char *)ucBuf, |
|||
sizeof(ucBuf), 0, |
|||
(struct sockaddr *)&stFrom, &iFromLen); |
|||
if (iRd > 0) |
|||
{ |
|||
rd_ComRecvProc(ptComCtrl, (const char *)ucBuf, (uint32_t)iRd); |
|||
} |
|||
else if (iRd < 0) |
|||
{ |
|||
int iErr = RD_UDP_SOCK_ERR(); |
|||
if (RD_UDP_ERR_YIELD(iErr)) |
|||
{ |
|||
rd_udp_yield(); |
|||
} |
|||
else |
|||
{ |
|||
log_e("udp_recv_thread: recvfrom failed, err=%d", iErr); |
|||
break; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
rd_udp_yield(); |
|||
} |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
/* ═══════════════════════════════════════════════════════════════════════ *
|
|||
* 发现线程 (非阻塞收 beacon + 周期广播 + 超时剔除) * |
|||
* ═══════════════════════════════════════════════════════════════════════ */ |
|||
#if defined(BUILD_PLATFORM_WIN32) |
|||
static DWORD WINAPI udp_disc_thread(LPVOID _lpParam) |
|||
#else |
|||
static void *udp_disc_thread(void *_pArg) |
|||
#endif |
|||
{ |
|||
#if defined(BUILD_PLATFORM_WIN32) |
|||
TComCtrl *ptComCtrl = (TComCtrl *)_lpParam; |
|||
#else |
|||
TComCtrl *ptComCtrl = (TComCtrl *)_pArg; |
|||
#endif |
|||
TUdpUserData *ptUd = (TUdpUserData *)(ptComCtrl->m_pUserData); |
|||
uint8_t ucBuf[RD_UDP_BEACON_SIZE + 16]; /* 容忍 beacon 带少量尾部冗余 */ |
|||
uint32_t uiLastBeacon = 0; /* 0 → 首轮立即广播, 尽快入对端表 */ |
|||
|
|||
/* 发现是增强通道: 收发失败只降级, 不退出 (m_iRunning 为唯一退出条件) */ |
|||
while (ptUd->m_iRunning) |
|||
{ |
|||
struct sockaddr_in stFrom; |
|||
socklen_t iFromLen = sizeof(stFrom); |
|||
uint32_t uiNow; |
|||
int iRd = (int)recvfrom(ptUd->m_iDiscSocket, (char *)ucBuf, |
|||
sizeof(ucBuf), 0, |
|||
(struct sockaddr *)&stFrom, &iFromLen); |
|||
if (iRd > 0) |
|||
{ |
|||
rd_udp_beacon_on_rx(ptUd, ucBuf, (uint32_t)iRd, &stFrom); |
|||
} |
|||
|
|||
uiNow = Rd_GetTime(); |
|||
/* 周期广播 (uint32 回绕安全减法) */ |
|||
if ((uint32_t)(uiNow - uiLastBeacon) >= RD_UDP_BEACON_INTERVAL_MS) |
|||
{ |
|||
uiLastBeacon = uiNow; |
|||
rd_udp_beacon_send(ptUd); |
|||
} |
|||
rd_udp_peer_sweep(ptUd, uiNow); |
|||
|
|||
rd_udp_yield(); |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
#if defined(USE_LWIP) |
|||
/* robot 适配: FreeRTOS 任务不允许从入口函数返回 (返回=未定义行为/断言),
|
|||
* 蹦床包一层 "函数返回即 vTaskDelete(NULL) 自杀"; 真正逻辑仍在原线程函数 */ |
|||
static void udp_recv_task_entry(void *_pArg) |
|||
{ |
|||
udp_recv_thread(_pArg); |
|||
vTaskDelete(NULL); |
|||
} |
|||
|
|||
static void udp_disc_task_entry(void *_pArg) |
|||
{ |
|||
udp_disc_thread(_pArg); |
|||
vTaskDelete(NULL); |
|||
} |
|||
#endif /* USE_LWIP */ |
|||
|
|||
/* ═══════════════════════════════════════════════════════════════════════ *
|
|||
* Rd_UdpCreate — 统一入口 * |
|||
* ═══════════════════════════════════════════════════════════════════════ */ |
|||
|
|||
/* 平台 socket 初始化 (Win32: WSAStartup; 其余平台空) */ |
|||
static int rd_udp_sock_init(void) |
|||
{ |
|||
#if defined(BUILD_PLATFORM_WIN32) |
|||
static int s_iInitDone = 0; |
|||
WSADATA stWsa; |
|||
if (!s_iInitDone) |
|||
{ |
|||
if (WSAStartup(MAKEWORD(2, 2), &stWsa) != 0) return RD_FAILURE; |
|||
s_iInitDone = 1; |
|||
} |
|||
#endif |
|||
return RD_SUCCESS; |
|||
} |
|||
|
|||
WEAK TComCtrl *Rd_UdpCreate(const TRdUdpCfg *_ptCfg) |
|||
{ |
|||
if (_ptCfg == NULL) |
|||
{ |
|||
log_e("Rd_UdpCreate: invalid config"); |
|||
return NULL; |
|||
} |
|||
/* m_psRemoteIp 允许 NULL/"": 纯发现模式 (对端经 beacon 自动获知,
|
|||
* m_iRemotePort 忽略可 0); 固定直连时远端端口才必填 */ |
|||
if (_ptCfg->m_psRemoteIp != NULL && _ptCfg->m_psRemoteIp[0] != '\0' && |
|||
(_ptCfg->m_iRemotePort <= 0 || _ptCfg->m_iRemotePort > 65535)) |
|||
{ |
|||
log_e("Rd_UdpCreate: invalid remote port %d (fixed-ip mode)", |
|||
_ptCfg->m_iRemotePort); |
|||
return NULL; |
|||
} |
|||
if (_ptCfg->m_iLocalPort < 0 || _ptCfg->m_iLocalPort > 65535 || |
|||
_ptCfg->m_iTxSrcPort < 0 || _ptCfg->m_iTxSrcPort > 65535) |
|||
{ |
|||
log_e("Rd_UdpCreate: invalid port config"); |
|||
return NULL; |
|||
} |
|||
if (rd_udp_sock_init() != RD_SUCCESS) |
|||
{ |
|||
log_e("Rd_UdpCreate: socket subsystem init failed"); |
|||
return NULL; |
|||
} |
|||
|
|||
/* 1. 分配 UserData */ |
|||
TUdpUserData *ptUd = (TUdpUserData *)RD_CALLOC(1, sizeof(TUdpUserData)); |
|||
if (ptUd == NULL) return NULL; |
|||
ptUd->m_iRxSocket = -1; |
|||
ptUd->m_iTxSocket = -1; |
|||
ptUd->m_iDiscSocket = -1; |
|||
|
|||
/* 1.5 发现参数: 实例标识 / beacon 名 / 本端数据端口 / 广播目的 */ |
|||
ptUd->m_uiDataPort = (uint16_t)(_ptCfg->m_iLocalPort > 0 ? _ptCfg->m_iLocalPort : 0); |
|||
ptUd->m_uiInstanceId = rd_udp_make_instance_id(ptUd, ptUd->m_uiDataPort); |
|||
if (_ptCfg->m_psName != NULL && _ptCfg->m_psName[0] != '\0') |
|||
{ |
|||
uint32_t uiLen = (uint32_t)RD_STRLEN(_ptCfg->m_psName); |
|||
if (uiLen > RD_UDP_NAME_MAX) uiLen = RD_UDP_NAME_MAX; |
|||
RD_MEMCPY(ptUd->m_sName, _ptCfg->m_psName, uiLen); /* 目标已清零保 NUL */ |
|||
} |
|||
RD_MEMSET(&ptUd->m_stDiscAddr, 0, sizeof(ptUd->m_stDiscAddr)); |
|||
ptUd->m_stDiscAddr.sin_family = AF_INET; |
|||
ptUd->m_stDiscAddr.sin_port = htons(RD_UDP_DISCOVERY_PORT); |
|||
ptUd->m_stDiscAddr.sin_addr.s_addr = htonl(INADDR_BROADCAST); |
|||
|
|||
/* 2. 固定对端地址 (m_psRemoteIp NULL/"" = 纯发现模式, 不设固定直连) */ |
|||
if (_ptCfg->m_psRemoteIp != NULL && _ptCfg->m_psRemoteIp[0] != '\0') |
|||
{ |
|||
if (rd_udp_fill_remote(&ptUd->m_stRemoteAddr, |
|||
_ptCfg->m_psRemoteIp, _ptCfg->m_iRemotePort) != RD_SUCCESS) |
|||
{ |
|||
log_e("Rd_UdpCreate: bad remote ip '%s'", _ptCfg->m_psRemoteIp); |
|||
RD_FREE(ptUd); |
|||
return NULL; |
|||
} |
|||
ptUd->m_iFixedValid = 1; |
|||
} |
|||
|
|||
/* 3. 接收 socket: bind 本地端口 (0 = 只发不收, 不创建) */ |
|||
if (_ptCfg->m_iLocalPort > 0) |
|||
{ |
|||
struct sockaddr_in stLocal; |
|||
int iSock = (int)socket(AF_INET, SOCK_DGRAM, 0); |
|||
if (iSock < 0) |
|||
{ |
|||
log_e("Rd_UdpCreate: rx socket failed, err=%d", RD_UDP_SOCK_ERR()); |
|||
RD_FREE(ptUd); |
|||
return NULL; |
|||
} |
|||
|
|||
RD_MEMSET(&stLocal, 0, sizeof(stLocal)); |
|||
stLocal.sin_family = AF_INET; |
|||
stLocal.sin_port = htons((uint16_t)_ptCfg->m_iLocalPort); |
|||
stLocal.sin_addr.s_addr = htonl(INADDR_ANY); |
|||
if (bind(iSock, (struct sockaddr *)&stLocal, sizeof(stLocal)) < 0) |
|||
{ |
|||
log_e("Rd_UdpCreate: bind(%d) failed, err=%d", |
|||
_ptCfg->m_iLocalPort, RD_UDP_SOCK_ERR()); |
|||
RD_UDP_CLOSE(iSock); |
|||
RD_FREE(ptUd); |
|||
return NULL; |
|||
} |
|||
|
|||
if (rd_udp_set_nonblock(iSock) != RD_SUCCESS) |
|||
{ |
|||
log_e("Rd_UdpCreate: set nonblock failed"); |
|||
RD_UDP_CLOSE(iSock); |
|||
RD_FREE(ptUd); |
|||
return NULL; |
|||
} |
|||
ptUd->m_iRxSocket = iSock; |
|||
} |
|||
|
|||
/* 4. 发送 socket: 指定源端口且与接收端口不同时单独创建 (对齐固件
|
|||
* 7006/8006 双 PCB 语义), 否则复用接收 socket (本地端口 0 时新建 |
|||
* 匿名绑定 socket 专用于发送) */ |
|||
if (_ptCfg->m_iTxSrcPort > 0 && _ptCfg->m_iTxSrcPort != _ptCfg->m_iLocalPort) |
|||
{ |
|||
struct sockaddr_in stSrc; |
|||
int iSock = (int)socket(AF_INET, SOCK_DGRAM, 0); |
|||
if (iSock < 0) |
|||
{ |
|||
log_e("Rd_UdpCreate: tx socket failed, err=%d", RD_UDP_SOCK_ERR()); |
|||
if (ptUd->m_iRxSocket >= 0) RD_UDP_CLOSE(ptUd->m_iRxSocket); |
|||
RD_FREE(ptUd); |
|||
return NULL; |
|||
} |
|||
|
|||
RD_MEMSET(&stSrc, 0, sizeof(stSrc)); |
|||
stSrc.sin_family = AF_INET; |
|||
stSrc.sin_port = htons((uint16_t)_ptCfg->m_iTxSrcPort); |
|||
stSrc.sin_addr.s_addr = htonl(INADDR_ANY); |
|||
if (bind(iSock, (struct sockaddr *)&stSrc, sizeof(stSrc)) < 0) |
|||
{ |
|||
log_e("Rd_UdpCreate: bind tx src(%d) failed, err=%d", |
|||
_ptCfg->m_iTxSrcPort, RD_UDP_SOCK_ERR()); |
|||
RD_UDP_CLOSE(iSock); |
|||
if (ptUd->m_iRxSocket >= 0) RD_UDP_CLOSE(ptUd->m_iRxSocket); |
|||
RD_FREE(ptUd); |
|||
return NULL; |
|||
} |
|||
ptUd->m_iTxSocket = iSock; |
|||
} |
|||
else |
|||
{ |
|||
ptUd->m_iTxSocket = ptUd->m_iRxSocket; |
|||
if (ptUd->m_iTxSocket < 0) |
|||
{ |
|||
/* 只发不收且未指定源端口: 新建匿名 socket */ |
|||
ptUd->m_iTxSocket = (int)socket(AF_INET, SOCK_DGRAM, 0); |
|||
if (ptUd->m_iTxSocket < 0) |
|||
{ |
|||
log_e("Rd_UdpCreate: tx-only socket failed, err=%d", RD_UDP_SOCK_ERR()); |
|||
RD_FREE(ptUd); |
|||
return NULL; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/* 5. tx socket 开广播 (beacon 发 255.255.255.255; 三平台同名宏同义,
|
|||
* 失败不致命: 固定直连仍可用, beacon 首次发送失败再警告) */ |
|||
{ |
|||
int iOn = 1; |
|||
if (setsockopt(ptUd->m_iTxSocket, SOL_SOCKET, SO_BROADCAST, |
|||
(const char *)&iOn, sizeof(iOn)) < 0) |
|||
{ |
|||
log_w("Rd_UdpCreate: SO_BROADCAST failed, err=%d", RD_UDP_SOCK_ERR()); |
|||
} |
|||
} |
|||
|
|||
/* 6. 发现 socket: SO_REUSEADDR + bind 发现端口 + 非阻塞
|
|||
* (SO_REUSEADDR: 同机多实例同端口共存, 广播各收一份; |
|||
* 绑定失败仅降级关闭发现, 链路创建继续) */ |
|||
{ |
|||
int iOn = 1; |
|||
int iSock = (int)socket(AF_INET, SOCK_DGRAM, 0); |
|||
if (iSock < 0) |
|||
{ |
|||
log_w("Rd_UdpCreate: disc socket failed, err=%d (discovery off)", |
|||
RD_UDP_SOCK_ERR()); |
|||
} |
|||
else |
|||
{ |
|||
struct sockaddr_in stLocal; |
|||
setsockopt(iSock, SOL_SOCKET, SO_REUSEADDR, |
|||
(const char *)&iOn, sizeof(iOn)); |
|||
RD_MEMSET(&stLocal, 0, sizeof(stLocal)); |
|||
stLocal.sin_family = AF_INET; |
|||
stLocal.sin_port = htons(RD_UDP_DISCOVERY_PORT); |
|||
stLocal.sin_addr.s_addr = htonl(INADDR_ANY); |
|||
if (bind(iSock, (struct sockaddr *)&stLocal, sizeof(stLocal)) < 0) |
|||
{ |
|||
log_w("Rd_UdpCreate: bind disc(%d) failed, err=%d (discovery off)", |
|||
RD_UDP_DISCOVERY_PORT, RD_UDP_SOCK_ERR()); |
|||
RD_UDP_CLOSE(iSock); |
|||
} |
|||
else if (rd_udp_set_nonblock(iSock) != RD_SUCCESS) |
|||
{ |
|||
log_w("Rd_UdpCreate: disc nonblock failed (discovery off)"); |
|||
RD_UDP_CLOSE(iSock); |
|||
} |
|||
else |
|||
{ |
|||
ptUd->m_iDiscSocket = iSock; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/* 7. 创建 TComCtrl (m_pfSend = udp_send_impl) */ |
|||
uint32_t uiBufSize = _ptCfg->m_uiBufSize > 0 ? _ptCfg->m_uiBufSize : 4096; |
|||
TComCtrl *ptComCtrl = rd_ComCreate(_ptCfg->m_pfCheck, _ptCfg->m_pfDecode, |
|||
udp_send_impl, (int)uiBufSize, ptUd); |
|||
if (ptComCtrl == NULL) |
|||
{ |
|||
log_e("Rd_UdpCreate: rd_ComCreate failed"); |
|||
if (ptUd->m_iDiscSocket >= 0) RD_UDP_CLOSE(ptUd->m_iDiscSocket); |
|||
if (ptUd->m_iRxSocket >= 0 && ptUd->m_iTxSocket != ptUd->m_iRxSocket) |
|||
RD_UDP_CLOSE(ptUd->m_iTxSocket); |
|||
if (ptUd->m_iRxSocket >= 0) RD_UDP_CLOSE(ptUd->m_iRxSocket); |
|||
RD_FREE(ptUd); |
|||
return NULL; |
|||
} |
|||
|
|||
/* 8. 记录全局引用 (供发送回调使用) */ |
|||
g_ptUdpComCtrl = ptComCtrl; |
|||
|
|||
/* 9. 启动接收线程 (local_port = 0 只发不收, 无接收线程) */ |
|||
ptUd->m_iRunning = 1; /* 接收/发现线程共用运行标志 */ |
|||
if (ptUd->m_iRxSocket >= 0) |
|||
{ |
|||
/* robot 适配: 固件既有 CMSIS-RTOS2 直接创建 (栈字节/优先级语义与
|
|||
* 主仓库 FreeRTOS 封装默认值一致: 4KB / osPriorityNormal) */ |
|||
const osThreadAttr_t attr = { .name = "udp_recv", .stack_size = 4 * 1024, |
|||
.priority = (osPriority_t)osPriorityNormal }; |
|||
if (osThreadNew(udp_recv_task_entry, ptComCtrl, &attr) == NULL) |
|||
{ |
|||
log_e("Rd_UdpCreate: osThreadNew failed"); |
|||
ptUd->m_iRunning = 0; |
|||
g_ptUdpComCtrl = NULL; |
|||
rd_ComDelete(ptComCtrl); |
|||
if (ptUd->m_iDiscSocket >= 0) RD_UDP_CLOSE(ptUd->m_iDiscSocket); |
|||
if (ptUd->m_iRxSocket >= 0 && ptUd->m_iTxSocket != ptUd->m_iRxSocket) |
|||
RD_UDP_CLOSE(ptUd->m_iTxSocket); |
|||
RD_UDP_CLOSE(ptUd->m_iRxSocket); |
|||
RD_FREE(ptUd); |
|||
return NULL; |
|||
} |
|||
} |
|||
|
|||
/* 10. 启动发现线程 (发现 socket 就绪才有; 增强功能, 失败只关发现) */ |
|||
if (ptUd->m_iDiscSocket >= 0) |
|||
{ |
|||
const osThreadAttr_t attr = { .name = "udp_disc", .stack_size = 4 * 1024, |
|||
.priority = (osPriority_t)osPriorityNormal }; |
|||
if (osThreadNew(udp_disc_task_entry, ptComCtrl, &attr) == NULL) |
|||
{ |
|||
log_w("Rd_UdpCreate: disc thread failed (discovery off)"); |
|||
RD_UDP_CLOSE(ptUd->m_iDiscSocket); |
|||
ptUd->m_iDiscSocket = -1; |
|||
} |
|||
} |
|||
|
|||
if (ptUd->m_iFixedValid) |
|||
{ |
|||
log_i("Rd_UdpCreate: local=%d txsrc=%d -> %s:%d (discovery %s)", |
|||
_ptCfg->m_iLocalPort, _ptCfg->m_iTxSrcPort, |
|||
_ptCfg->m_psRemoteIp, _ptCfg->m_iRemotePort, |
|||
ptUd->m_iDiscSocket >= 0 ? "on" : "off"); |
|||
} |
|||
else |
|||
{ |
|||
log_i("Rd_UdpCreate: local=%d txsrc=%d, pure discovery mode (disc %s)", |
|||
_ptCfg->m_iLocalPort, _ptCfg->m_iTxSrcPort, |
|||
ptUd->m_iDiscSocket >= 0 ? "on" : "off"); |
|||
} |
|||
return ptComCtrl; |
|||
} |
|||
|
|||
/* ═══════════════════════════════════════════════════════════════════════ *
|
|||
* Rd_UdpClose — 统一销毁 * |
|||
* ═══════════════════════════════════════════════════════════════════════ */ |
|||
WEAK void Rd_UdpClose(TComCtrl *_ptComCtrl) |
|||
{ |
|||
if (_ptComCtrl == NULL) return; |
|||
|
|||
TUdpUserData *ptUd = (TUdpUserData *)(_ptComCtrl->m_pUserData); |
|||
if (ptUd != NULL) |
|||
{ |
|||
/* 1. 通知接收/发现线程停止 (detached 线程无法 join, 发信号后 sleep;
|
|||
* 两个线程均为 1ms 级轮询, 多让步几拍确保脱离共享数据) */ |
|||
ptUd->m_iRunning = 0; |
|||
rd_udp_yield(); |
|||
rd_udp_yield(); |
|||
rd_udp_yield(); |
|||
rd_udp_yield(); |
|||
|
|||
/* 2. 关闭 socket (唤醒轮询中的 recvfrom 并使其退出) */ |
|||
if (ptUd->m_iDiscSocket >= 0) |
|||
{ |
|||
RD_UDP_CLOSE(ptUd->m_iDiscSocket); |
|||
ptUd->m_iDiscSocket = -1; |
|||
} |
|||
if (ptUd->m_iTxSocket >= 0 && ptUd->m_iTxSocket != ptUd->m_iRxSocket) |
|||
{ |
|||
RD_UDP_CLOSE(ptUd->m_iTxSocket); |
|||
ptUd->m_iTxSocket = -1; |
|||
} |
|||
if (ptUd->m_iRxSocket >= 0) |
|||
{ |
|||
RD_UDP_CLOSE(ptUd->m_iRxSocket); |
|||
ptUd->m_iRxSocket = -1; |
|||
} |
|||
|
|||
RD_FREE(ptUd); |
|||
} |
|||
|
|||
/* 3. 销毁 TComCtrl */ |
|||
rd_ComDelete(_ptComCtrl); |
|||
|
|||
if (g_ptUdpComCtrl == _ptComCtrl) |
|||
g_ptUdpComCtrl = NULL; |
|||
|
|||
log_i("Rd_UdpClose: done"); |
|||
} |
|||
|
|||
#else |
|||
TComCtrl *Rd_UdpCreate(const TRdUdpCfg *_ptCfg) |
|||
{ |
|||
return NULL; |
|||
} |
|||
|
|||
void Rd_UdpClose(TComCtrl *_ptComCtrl) |
|||
{ |
|||
return; |
|||
} |
|||
#endif /* RD_UDP_NO_PLATFORM */ |
|||
@ -0,0 +1,102 @@ |
|||
/******************************************************************************
|
|||
|
|||
版权所有 (C), 2018-2099, Radkil |
|||
|
|||
****************************************************************************** |
|||
文 件 名 : rd_udp.h |
|||
版 本 号 : 初稿 |
|||
作 者 : Radkil |
|||
生成日期 : 2026年11月3日 |
|||
最近修改 : |
|||
功能描述 : 跨平台 UDP 传输抽象层公共头文件 (TComCtrl 集成) |
|||
与 rd_serial 同构: 创建 TComCtrl + 内部接收线程, |
|||
平台差异在 peripheral/rd_udp.c 内通过宏处理: |
|||
USE_LWIP → lwip/sockets.h (STM32 固件, 需 tcpip 栈已启动) |
|||
BUILD_PLATFORM_LINUX → POSIX socket |
|||
BUILD_PLATFORM_WIN32 → winsock2 |
|||
|
|||
修改历史 : |
|||
1.日 期 : 2026年11月3日 |
|||
作 者 : Radkil |
|||
修改内容 : 创建文件 |
|||
|
|||
******************************************************************************/ |
|||
|
|||
#ifndef __RD_UDP_H__ |
|||
#define __RD_UDP_H__ |
|||
|
|||
#ifdef __cplusplus |
|||
#if __cplusplus |
|||
extern "C"{ |
|||
#endif |
|||
#endif /* __cplusplus */ |
|||
|
|||
/*==============================================*
|
|||
* include header files * |
|||
*----------------------------------------------*/ |
|||
#include <stdint.h> |
|||
#include "common.h" |
|||
#include "com.h" |
|||
|
|||
/*==============================================*
|
|||
* constants or macros define * |
|||
*----------------------------------------------*/ |
|||
#define RD_UDP_IP_MAX 16 /* 点分十进制 IP 最大长度("255.255.255.255"+0) */ |
|||
|
|||
/* ── UDP 广播设备发现 (beacon) 参数 ──
|
|||
* 机制: 各实例按 RD_UDP_BEACON_INTERVAL_MS 周期向 |
|||
* 255.255.255.255:RD_UDP_DISCOVERY_PORT 广播 22B beacon, |
|||
* 收到方校验 magic 后记录 (源IP, data_port) 进 peer 表, |
|||
* 数据帧扇出给所有 RD_UDP_PEER_TIMEOUT_MS 内活跃的 peer */ |
|||
#define RD_UDP_DISCOVERY_PORT 9005 /* 发现通道端口 (beacon 收发, 两端一致) */ |
|||
#define RD_UDP_PEER_MAX 8 /* peer 表容量 (满时覆盖最旧) */ |
|||
#define RD_UDP_PEER_TIMEOUT_MS 5000 /* peer 存活超时 (beacon 周期整数倍, 防误剔) */ |
|||
#define RD_UDP_BEACON_INTERVAL_MS 1000 /* beacon 广播周期 */ |
|||
|
|||
/* UDP 链路配置 */ |
|||
typedef struct |
|||
{ |
|||
const char *m_psRemoteIp; /* 对端 IP (发送目的), 点分十进制;
|
|||
* NULL/"" = 纯发现模式 (数据帧扇出给 beacon 发现的活 peer) */ |
|||
int m_iRemotePort; /* 对端端口 (仅固定 IP 模式使用; 纯发现模式忽略, 可 0) */ |
|||
int m_iLocalPort; /* 本地绑定端口 (接收), 0 = 只发不收 (beacon data_port 同源) */ |
|||
int m_iTxSrcPort; /* 发送源端口 (对齐固件 7006 双 PCB 语义),
|
|||
* 0 = 复用接收 socket (源端口 = 本地端口) */ |
|||
uint32_t m_uiBufSize; /* TComCtrl 环形缓冲区大小 */ |
|||
const char *m_psName; /* 设备名 (beacon 携带, 对端日志可见), NULL = 不填 */ |
|||
cbk_ComCheck m_pfCheck; |
|||
cbk_ComDecode m_pfDecode; |
|||
} TRdUdpCfg; |
|||
|
|||
/*==============================================*
|
|||
* routines' or functions' implementations * |
|||
*----------------------------------------------*/ |
|||
|
|||
/**
|
|||
* @brief 创建 UDP 链路 TComCtrl (内部启动接收线程 + beacon 发现线程) |
|||
* @param _ptCfg UDP 配置 (不可为空; m_psRemoteIp 可为 NULL/"") |
|||
* @return TComCtrl* 成功, NULL 失败 |
|||
* @note m_psRemoteIp 为 NULL/"" 时进入纯发现模式: 不再要求配置对端, |
|||
* 数据帧自动扇出给 beacon 发现的活 peer (rd_udp.h 宏可调); |
|||
* 非空时固定直连与发现并存 (扇出 = 固定对端 + 全部活 peer)。 |
|||
* 发现 socket 绑定失败仅降级关闭发现 (log_w), 链路创建不受影响。 |
|||
* USE_LWIP 平台需先完成 tcpip 栈/netif 初始化再调用 |
|||
* (SO_REUSEADDR 需 LWIP_SO_REUSE); |
|||
* 发送回调经模块级静态上下文解析, 每进程仅支持一条 UDP 链路 |
|||
* (与 rd_serial 的单实例约束同模式, 不改 com.c) |
|||
*/ |
|||
TComCtrl *Rd_UdpCreate(const TRdUdpCfg *_ptCfg); |
|||
|
|||
/**
|
|||
* @brief 关闭并销毁 UDP 链路 TComCtrl |
|||
* @param _ptComCtrl TComCtrl 控制块 |
|||
*/ |
|||
void Rd_UdpClose(TComCtrl *_ptComCtrl); |
|||
|
|||
#ifdef __cplusplus |
|||
#if __cplusplus |
|||
} |
|||
#endif |
|||
#endif /* __cplusplus */ |
|||
|
|||
#endif /* __RD_UDP_H__ */ |
|||
Loading…
Reference in new issue