You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
874 lines
35 KiB
874 lines
35 KiB
|
49 minutes ago
|
/******************************************************************************
|
||
|
|
|
||
|
|
版权所有 (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 */
|