This commit is contained in:
2026-03-13 22:22:45 +08:00
parent 2cdb65309f
commit ea4368bf15
19 changed files with 28899 additions and 0 deletions
BIN
View File
Binary file not shown.
+42
View File
@@ -0,0 +1,42 @@
@echo off
setlocal
cd /d "%~dp0"
rem === C Language TCP API Demo Client Build Script ===
rem This script uses the installed MinGW GCC compiler.
rem It includes the new APItcpDemo files + QDXnetworkStack sources.
set GCC_PATH=C:\MinGW\bin\gcc.exe
echo Checking for GCC compiler...
if not exist "%GCC_PATH%" (
echo [ERROR] GCC compiler not found at: %GCC_PATH%
echo Please ensure MinGW is properly installed.
exit /b 1
)
echo.
echo =========================================
echo Building APItcpDemo (QDXnetworkStack API Client)...
echo Target: api_demo.exe
echo =========================================
echo.
rem Compiler flags: -O2 optimization, strictly enable warnings
set CFLAGS=-O2 -Wall -I../QDXnetworkStack
rem Source files: Demo local sources + the QDXnetworkStack sources
set SRC_FILES=demo_main.c qdx_port_win32.c ../QDXnetworkStack/qdx_tcp_logic.c ../QDXnetworkStack/qdx_protocol.c ../QDXnetworkStack/qdx_preprocess.c
"%GCC_PATH%" %CFLAGS% %SRC_FILES% -o api_demo.exe -lws2_32
if %ERRORLEVEL% equ 0 (
echo.
echo [SUCCESS] Build completed! Output file: api_demo.exe
echo Usage example: Run 'api_demo.exe' and interact with the console.
) else (
echo.
echo [FAILED] Build error. Please check the logs above.
)
pause
+519
View File
@@ -0,0 +1,519 @@
/**
* @file demo_main.c
* @brief TCP API 调用演示客户端 (一维/二维通用)
*
* 演示使用 QDXnetworkStack API 层的零拷贝发送与参数回调机制。
* 用户操作流程与原 tcp_c_demo 保持完全一致。
*/
#include "qdx_port.h"
#include "qdx_preprocess.h"
#include "qdx_protocol.h"
#include "qdx_tcp_logic.h"
#include <conio.h> /* _kbhit, _getch */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
/* ============================================================
* 宏定义与全局变量
* ============================================================ */
/* 默认服务端连接参数(硬编码) */
#define DEFAULT_SERVER_IP "127.0.0.1"
#define DEFAULT_CONTROL_PORT 5511
#define DEFAULT_DATA_PORT 5512
/* ANSI 控制台颜色宏定义 */
#define CLR_RESET "\033[0m"
#define CLR_RED "\033[31m"
#define CLR_GREEN "\033[32m"
#define CLR_YELLOW "\033[33m"
#define CLR_BLUE "\033[34m"
#define CLR_MAGENTA "\033[35m"
#define CLR_CYAN "\033[36m"
#define CLR_DIM "\033[90m"
static uint8_t g_dimension_mode = 0; /* 0=1D, 1=2D */
static char g_matrix_dir[260] = {0};
/* 发送缓冲区(静态分配,避免 malloc) */
#define MAX_2D_PIXELS (256 * 256)
static uint16_t g_raw_matrix[MAX_2D_PIXELS];
/* API 要求的外部传输缓冲区,总大小 256KB,前置留出 1KB 给网络层附加头部 */
#define TX_BUFFER_TOTAL_CAPACITY (256 * 1024)
#define TX_BUFFER_HEAD_OFFSET 1024
static uint8_t g_tx_buffer[TX_BUFFER_TOTAL_CAPACITY];
static TcpTxBuffer_t g_api_tx_buffer;
/* ============================================================
* 辅助函数:十六进制打印
* ============================================================ */
static void print_hex(const uint8_t *data, int len) {
(void)data;
(void)len;
}
/* ============================================================
* 模拟数据发送逻辑 (1D & 2D) 的前置声明
* ============================================================ */
static void simulate_send_2d_frame(uint32_t frameNum);
static void simulate_send_1d_frame(uint32_t frameNum);
/* ============================================================
* QDX API 回调函数实现
* ============================================================ */
/**
* @brief 上位机下发配置更新时的回调
*/
static void on_config_updated(const ConfigCommon_t *common,
const Config2D_t *cfg2d,
const Config1D_t *cfg1d) {
printf(CLR_CYAN "\n[API Callback] 收到最新配置" CLR_RESET "\n");
/* 打印通用配置 */
printf(" -> Common: Pipeline: %.*s, Type: %d, Mode: %d, Tag: %d, "
"Strictness: %d, Custom: %d\n",
16, common->PipelineId, common->PipelineType, common->WorkMode,
common->ConfigTag, common->StrictnessLevel, common->IsCustomMode);
/* 打印 2D 配置 */
printf(" -> 2D: Enabled: %d, Live: %d, DevId: %d, %dx%d, Fps: %d\n",
cfg2d->Enabled, cfg2d->IsLive, cfg2d->DeviceId, cfg2d->Width,
cfg2d->Height, cfg2d->Fps);
printf(" -> 2D: Mask: %d (Thresh: %d, %dx%d), Target: %dx%d\n",
cfg2d->MaskEnabled, cfg2d->MaskThreshold, cfg2d->MaskWidth,
cfg2d->MaskHeight, cfg2d->TargetWidth, cfg2d->TargetHeight);
/* 打印 1D 配置 */
printf(" -> 1D: Enabled: %d, RunMode: %d, TriggerType: %d, "
"BufferSize: %d\n",
cfg1d->Enabled, cfg1d->RunMode, cfg1d->TriggerType, cfg1d->BufferSize);
/* 核心:将配置下发给预处理算法库 */
Preprocess_Settings_Change(cfg2d, cfg1d, common);
printf(CLR_GREEN " -> [OK] 已将参数同步至预处理引擎" CLR_RESET "\n");
}
/**
* @brief 上位机反馈检测结果时的回调
*/
static void on_detection_result(uint32_t frameNumber, uint8_t resultStatus) {
printf(CLR_CYAN "[API Callback] 收到检测结果: Frame #%u, Result: %s" CLR_RESET
"\n",
frameNumber, resultStatus == 0 ? "OK" : "NG");
}
/**
* @brief 当服务端下发 TempFrame(请求回传当前帧)时的回调
*/
static void on_temp_frame_request(uint8_t is2dRequest) {
printf(CLR_CYAN
"[API Callback] 收到服务端 TempFrame 采集请求 (is2D=%d)" CLR_RESET
"\n",
is2dRequest);
static uint32_t simulated_frame_num = 1;
if (g_dimension_mode == 1) {
printf(CLR_MAGENTA " -> 模拟: 开始处理并回传 2D 阵列图像..." CLR_RESET
"\n");
simulate_send_2d_frame(simulated_frame_num++);
} else {
printf(CLR_MAGENTA " -> 模拟: 开始处理并回传 1D 阵列数据..." CLR_RESET
"\n");
simulate_send_1d_frame(simulated_frame_num++);
}
}
/* ============================================================
* 模拟数据发送逻辑 (1D & 2D)
* ============================================================ */
/**
* @brief 随机选取 2D 矩阵文件并解析
* 逻辑与原 demo 的 parse_temperature_matrix 完全一致
*/
static int load_random_2d_matrix(uint16_t *matrix, int max_pixels, int *out_w,
int *out_h) {
char pattern[300];
snprintf(pattern, sizeof(pattern), "%s*.txt", g_matrix_dir);
WIN32_FIND_DATAA fd;
HANDLE hFind = FindFirstFileA(pattern, &fd);
if (hFind == INVALID_HANDLE_VALUE)
return -1;
int count = 0;
do {
if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
count++;
} while (FindNextFileA(hFind, &fd));
FindClose(hFind);
if (count == 0)
return -1;
int target = rand() % count;
hFind = FindFirstFileA(pattern, &fd);
int idx = 0;
char filepath[300] = {0};
do {
if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
if (idx == target) {
snprintf(filepath, sizeof(filepath), "%s%s", g_matrix_dir,
fd.cFileName);
break;
}
idx++;
}
} while (FindNextFileA(hFind, &fd));
FindClose(hFind);
if (filepath[0] == '\0')
return -1;
static char file_buf[512 * 1024];
FILE *fp = fopen(filepath, "r");
if (!fp)
return -1;
int file_len = (int)fread(file_buf, 1, sizeof(file_buf) - 1, fp);
fclose(fp);
file_buf[file_len] = '\0';
char *pos = strstr(file_buf, "\"temperature\"");
if (!pos)
return -1;
pos = strchr(pos, '[');
if (!pos)
return -1;
pos++;
int width = 0, height = 0, total = 0, row_count = 0;
while (*pos != '\0') {
if (*pos == '[') {
row_count = 0;
pos++;
} else if (*pos == ']') {
if (row_count > 0) {
height++;
if (width == 0)
width = row_count;
row_count = 0;
} else {
break;
}
pos++;
} else if (*pos >= '0' && *pos <= '9') {
int val = 0;
while (*pos >= '0' && *pos <= '9') {
val = val * 10 + (*pos - '0');
pos++;
}
if (*pos == '.') {
pos++;
while (*pos >= '0' && *pos <= '9')
pos++;
}
if (total < max_pixels) {
matrix[total] = (uint16_t)val;
}
total++;
row_count++;
} else {
pos++;
}
}
*out_w = width;
*out_h = height;
return total > 0 ? total : -1;
}
/**
* @brief 模拟发送 2D 图像帧 (完整 API Zero-Copy 流水线)
*/
static void simulate_send_2d_frame(uint32_t frameNum) {
int w = 0, h = 0;
int total = load_random_2d_matrix(g_raw_matrix, MAX_2D_PIXELS, &w, &h);
if (total <= 0) {
printf(CLR_RED "[Error] 读取 2D 矩阵失败" CLR_RESET "\n");
return;
}
/* 1. 构造原始图像结构体 */
RawImageBuffer_t rawBuff = {.pData = g_raw_matrix,
.Width = (uint16_t)w,
.Height = (uint16_t)h,
.FrameNumber = frameNum};
/* 2. 重置发送缓冲包装器(确保偏移正确) */
g_api_tx_buffer.pBuffer = g_tx_buffer;
g_api_tx_buffer.TotalCapacity = TX_BUFFER_TOTAL_CAPACITY;
g_api_tx_buffer.HeadOffset = TX_BUFFER_HEAD_OFFSET;
g_api_tx_buffer.ValidPayloadLen = 0;
PreprocessResult_t resMeta = {0};
/* 3. 核心 API: 预处理执行。它会根据滑动窗口目标大小提取 ROI,
* 并写入我们提供的 g_tx_buffer[HeadOffset] 处。 */
if (Preprocess_Execute(&rawBuff, &g_api_tx_buffer, &resMeta) == 0) {
printf(CLR_MAGENTA
"[Data] 预处理完成: 提取出 %dx%d ROI, %.1f~%.1f°C" CLR_RESET "\n",
resMeta.ValidWidth, resMeta.ValidHeight, resMeta.MinTemp / 10.0f,
resMeta.MaxTemp / 10.0f);
/* 4. 核心 API: TCP 零拷贝图文打包与发送。
* 内部会在 HeadOffset 前面的 1KB 预留空间中拼装 18 字节包头,直接压入
* LwIP(WinSock) */
int8_t err = TcpLogic_BuildAndSendTemperatureFrame(
&g_api_tx_buffer, &resMeta, 0x01 /* TRIGGER */, 1 /* IS_2D */);
if (err == 0) {
printf(CLR_GREEN " -> [OK] TCP 帧已加入发送队列" CLR_RESET "\n");
} else {
printf(CLR_RED " -> [Fail] TCP 帧发送失败, 错误码: %d" CLR_RESET
"\n",
err);
}
} else {
printf(CLR_RED
"[Data] 预处理拒绝了本帧数据 (未达到阈值或参数异常)" CLR_RESET "\n");
}
}
/**
* @brief 模拟发送 1D 温度帧
* 1D 模式下不需要滑动窗口图像处理,我们可以跳过 Preprocess_Execute,
* 直接组装 `PreprocessResult_t` 结构后提供给发送库。
*/
static void simulate_send_1d_frame(uint32_t frameNum) {
#define POINTS_COUNT 30
int16_t min_t = 32767;
int16_t max_t = -32768;
int32_t sum_t = 0;
g_api_tx_buffer.pBuffer = g_tx_buffer;
g_api_tx_buffer.TotalCapacity = TX_BUFFER_TOTAL_CAPACITY;
g_api_tx_buffer.HeadOffset = TX_BUFFER_HEAD_OFFSET;
uint8_t *dest = g_api_tx_buffer.pBuffer + g_api_tx_buffer.HeadOffset;
/* 模拟生成温度点 */
for (int i = 0; i < POINTS_COUNT; i++) {
uint16_t time_offset = (uint16_t)(i * 600 / (POINTS_COUNT - 1));
uint16_t temp = (uint16_t)(500 + rand() % 501); /* 50.0~100.0 */
int16_t t = (int16_t)temp;
if (t < min_t)
min_t = t;
if (t > max_t)
max_t = t;
sum_t += t;
/* 序列化到 buffer (按协议要求 TempPoint1D_t: 2字节时间偏移 +
* 2字节温度,小端存储) */
dest[0] = (uint8_t)(time_offset & 0xFF);
dest[1] = (uint8_t)((time_offset >> 8) & 0xFF);
dest[2] = (uint8_t)(temp & 0xFF);
dest[3] = (uint8_t)((temp >> 8) & 0xFF);
dest += 4;
}
g_api_tx_buffer.ValidPayloadLen = POINTS_COUNT * 4;
/* 构造预处理元数据欺骗网络库 */
PreprocessResult_t resMeta = {.pValidData = g_api_tx_buffer.pBuffer +
g_api_tx_buffer.HeadOffset,
.DataLength = g_api_tx_buffer.ValidPayloadLen,
.ValidWidth = POINTS_COUNT,
.ValidHeight = 1,
.MinTemp = min_t,
.MaxTemp = max_t,
.AvgTemp = (int16_t)(sum_t / POINTS_COUNT),
.RoiTemp = (int16_t)(sum_t / POINTS_COUNT),
.Status = 0,
.FrameNumber = frameNum};
printf(CLR_MAGENTA "[Data] 构造 1D 数据完成: %d 点, %.1f~%.1f°C" CLR_RESET
"\n",
POINTS_COUNT, min_t / 10.0f, max_t / 10.0f);
int8_t err = TcpLogic_BuildAndSendTemperatureFrame(
&g_api_tx_buffer, &resMeta, 0x01 /* TRIGGER */, 0 /* IS_1D */);
if (err == 0) {
printf(CLR_GREEN " -> [OK] 1D 帧已加入发送队列" CLR_RESET "\n");
} else {
printf(CLR_RED " -> [Fail] 网络发送失败, 错误码: %d" CLR_RESET "\n",
err);
}
#undef POINTS_COUNT
}
/* ============================================================
* 主函数 (控制台 UI 交互层)
* ============================================================ */
int main(void) {
/* 设置控制台支持 UTF-8 和 ANSI 转义 */
SetConsoleOutputCP(65001);
#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
#endif
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD dwMode = 0;
GetConsoleMode(hOut, &dwMode);
SetConsoleMode(hOut, dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
printf("========================================================\n");
printf("= TCP 透传客户端 API 演示版 (基于 QDXnetworkStack.c)\n");
printf("= 服务端: %s 控制: %d 数据: %d\n", DEFAULT_SERVER_IP,
DEFAULT_CONTROL_PORT, DEFAULT_DATA_PORT);
printf("========================================================\n\n");
/* Windows 下需要手动启动 Winsock */
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
printf("[ERROR] WSAStartup 失败。\n");
return 1;
}
/* 交互式输入设备 ID */
int input_id = 0;
printf("请输入设备 ID (整数, 例如 101): ");
if (scanf("%d", &input_id) != 1 || input_id < 0 || input_id > 65535) {
printf("[ERROR] 无效。默认设为 101。\n");
input_id = 101;
}
printf("\n设备 ID 设置为: %d\n\n", input_id);
/* 清除遗留的回车换行 */
int c;
while ((c = getchar()) != '\n' && c != EOF) {
}
/* 选择维度 */
int dim_choice = 0;
printf("请选择维度模式:\n");
printf(" 1 - 一维模式 (随机温度数据)\n");
printf(" 2 - 二维模式 (从目录随机读取矩阵文件)\n");
printf("请输入 (1 或 2): ");
if (scanf("%d", &dim_choice) != 1 || (dim_choice != 1 && dim_choice != 2)) {
dim_choice = 1;
}
g_dimension_mode = (dim_choice == 2) ? 1 : 0;
printf("\n维度模式: %s\n\n", g_dimension_mode ? "二维 (2D)" : "一维 (1D)");
if (g_dimension_mode == 1) {
snprintf(g_matrix_dir, sizeof(g_matrix_dir), "../tcp_c_demo/src/2d_mask/");
printf("2D 矩阵文件目录: %s\n\n", g_matrix_dir);
}
/* 生成假 UUID */
uint8_t uuid[16] = {0};
srand((unsigned int)GetTickCount());
for (int i = 0; i < 16; i++) {
uuid[i] = (uint8_t)(rand() % 256);
}
/* ---------------------------------------------------------
* 重点:纯 API 调用区
* --------------------------------------------------------- */
printf(">>> 正在初始化 QDXnetworkStack 库架构...\n");
/* 1. 初始化预处理引擎 (分配 256x256 静态滑窗资源) */
Preprocess_Init(256, 256);
/* 2. 初始化网络栈 */
TcpLogic_Init(uuid, NULL);
/* 3. 注册业务回调 (配置更新、坏品通知、采集请求) */
TcpLogic_RegisterConfigCallback(on_config_updated);
TcpLogic_RegisterDetectionCallback(on_detection_result);
TcpLogic_RegisterTempFrameRequestCallback(on_temp_frame_request);
/* 4. [Hack Demo] 因为 API 库不包含直接设置当前 DevID
* 的接口(依赖服务端下发), 但为了 Demo 一开始能用指定的 101 ID
* 发起连接,我们手动造一个回调注入进去, 实际上在真实的 CH32 工程中,ID
* 会保存在 Flash 里面。这演示了库的工作原理。
*/
// 由于 g_TcpLogic 结构体对于外部是静态隐藏的,
// API 设计中如果要改变 DevID,规范应是等待协议 0x05 下发。
// 此处模拟真实情况:我们相信服务端会自动给我分发 ID,
// 所以不再强求立刻修改。库有默认 ID=101。
/* 5. 启动后台处理收发断连。
* 它内部通过 _beginthreadex 开了线程!
*/
TcpLogic_Start();
printf(">>> 库引擎以启动。后台线程已接管所有 Socket 收发。\n\n");
/* =========================================================
* 主业务测试循环
* ========================================================= */
printf("菜单 (上位机请求时会自动发送):\n");
printf(" [s] 立即触发一次发送 [c] 打印当前内存参数册\n");
printf(" [q] 退出 (Ctrl+C 也可)\n\n");
while (1) {
if (_kbhit()) {
int ch = _getch();
if (ch == 'q' || ch == 'Q') {
break;
} else if (ch == 's' || ch == 'S') {
printf("\n[Manual] 用户主动触发一帧。\n");
on_temp_frame_request(g_dimension_mode);
} else if (ch == 'c' || ch == 'C') {
ConfigCommon_t com;
Config2D_t c2d;
Config1D_t c1d;
if (TcpLogic_GetLatestConfig(&com, &c2d, &c1d) == 0) {
printf(CLR_GREEN
"\n[Info] 当前静态配置缓存中包含数据 (严苛度: %d)。" CLR_RESET
"\n",
com.StrictnessLevel);
/* 打印通用配置 */
printf(" -> Common: Pipeline: %.*s, Type: %d, Mode: %d, Tag: %d, "
"Strictness: %d, Custom: %d\n",
16, com.PipelineId, com.PipelineType, com.WorkMode,
com.ConfigTag, com.StrictnessLevel, com.IsCustomMode);
/* 打印 2D 配置 */
printf(
" -> 2D: Enabled: %d, Live: %d, DevId: %d, %dx%d, Fps: %d\n",
c2d.Enabled, c2d.IsLive, c2d.DeviceId, c2d.Width, c2d.Height,
c2d.Fps);
printf(" -> 2D: Mask: %d (Thresh: %d, %dx%d), Target: %dx%d\n",
c2d.MaskEnabled, c2d.MaskThreshold, c2d.MaskWidth,
c2d.MaskHeight, c2d.TargetWidth, c2d.TargetHeight);
/* 打印 1D 配置 */
printf(" -> 1D: Enabled: %d, RunMode: %d, TriggerType: %d, "
"BufferSize: %d\n",
c1d.Enabled, c1d.RunMode, c1d.TriggerType, c1d.BufferSize);
} else {
printf(CLR_YELLOW
"\n[Info] 当前静态配置缓存尚未收到上位机同步!" CLR_RESET
"\n");
}
}
}
Sleep(50);
}
printf("程序退出中...\n");
WSACleanup();
return 0;
}
+194
View File
@@ -0,0 +1,194 @@
/**
* @file qdx_port_win32.c
* @brief Windows (WinSock2 + WinAPI) 平台移植层
*
* 目的:将 qdx_port.h 声明的 HAL 接口映射到 Windows API,
* 使 QDXnetworkStack 库能够在 PC 端运行。
*
* 注意:本文件仅用于 PC 端 Demo 模拟,不适用于 MCU 移植。
*/
#include "qdx_port.h"
#include <process.h> /* _beginthreadex */
#include <stddef.h>
#include <stdio.h>
#include <windows.h>
#include <winsock2.h>
/* ============================================================
* 时间与延迟
* ============================================================ */
/* 获取系统启动以来的毫秒数 */
uint32_t qdx_port_get_tick_ms(void) { return (uint32_t)GetTickCount(); }
/* 阻塞延迟指定毫秒 */
void qdx_port_delay_ms(uint32_t ms) { Sleep(ms); }
/* ============================================================
* TCP 网络操作 (基于 WinSock2)
* ============================================================ */
/**
* @brief 创建 TCP 套接字并连接到远端
*
* 连接成功后设置 500ms 接收超时,防止接收线程永久阻塞。
*/
qdx_socket_t qdx_port_tcp_connect(const char *ip, uint16_t port) {
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock == INVALID_SOCKET) {
return NULL;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip);
if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR) {
closesocket(sock);
return NULL;
}
/* 设置接收超时 500ms,使 recv 线程可定期检查连接状态 */
DWORD timeout_ms = 500;
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char *)&timeout_ms,
sizeof(timeout_ms));
printf("[Port] TCP 已连接: %s:%d\n", ip, port);
/* 将 SOCKET 转为不透明指针(SOCKET 本身是 unsigned int 类型) */
return (qdx_socket_t)(intptr_t)sock;
}
/* 关闭 TCP 套接字 */
void qdx_port_tcp_close(qdx_socket_t sock) {
if (sock) {
closesocket((SOCKET)(intptr_t)sock);
}
}
/* TCP 发送 */
int32_t qdx_port_tcp_send(qdx_socket_t sock, const uint8_t *data,
uint32_t len) {
if (!sock)
return -1;
int ret = send((SOCKET)(intptr_t)sock, (const char *)data, (int)len, 0);
if (ret == SOCKET_ERROR) {
return -1;
}
return (int32_t)ret;
}
/**
* @brief TCP 接收(非阻塞/短超时)
*
* 返回值约定:
* > 0 : 实际收到的字节数
* = 0 : 超时无数据
* < 0 : 连接断开或错误
*/
int32_t qdx_port_tcp_recv(qdx_socket_t sock, uint8_t *buf, uint32_t max_len) {
if (!sock)
return -1;
int ret = recv((SOCKET)(intptr_t)sock, (char *)buf, (int)max_len, 0);
if (ret == SOCKET_ERROR) {
int err = WSAGetLastError();
/* WSAETIMEDOUT / WSAEWOULDBLOCK 表示超时,视为无数据可读 */
if (err == WSAETIMEDOUT || err == WSAEWOULDBLOCK) {
return 0;
}
return -1; /* 真实错误 */
}
if (ret == 0) {
return -1; /* 服务端主动关闭连接 */
}
return (int32_t)ret;
}
/* ============================================================
* 互斥锁 (基于 CRITICAL_SECTION)
* ============================================================ */
/* 创建互斥锁 */
qdx_mutex_t qdx_port_mutex_create(void) {
CRITICAL_SECTION *cs = (CRITICAL_SECTION *)malloc(sizeof(CRITICAL_SECTION));
if (!cs)
return NULL;
InitializeCriticalSection(cs);
return (qdx_mutex_t)cs;
}
/* 加锁 */
void qdx_port_mutex_lock(qdx_mutex_t mutex) {
if (mutex) {
EnterCriticalSection((CRITICAL_SECTION *)mutex);
}
}
/* 解锁 */
void qdx_port_mutex_unlock(qdx_mutex_t mutex) {
if (mutex) {
LeaveCriticalSection((CRITICAL_SECTION *)mutex);
}
}
/* 销毁互斥锁 */
void qdx_port_mutex_delete(qdx_mutex_t mutex) {
if (mutex) {
DeleteCriticalSection((CRITICAL_SECTION *)mutex);
free(mutex);
}
}
/* ============================================================
* 线程 (基于 _beginthreadex)
* ============================================================ */
/**
* @brief 线程启动包装器
*
* 目的:_beginthreadex 要求 unsigned __stdcall 签名,
* 而 qdx_thread_entry_t 是 void(*)(void*),
* 需要通过此包装器做签名适配。
*/
typedef struct {
qdx_thread_entry_t entry;
void *arg;
} ThreadWrapper_t;
static unsigned __stdcall thread_wrapper(void *param) {
ThreadWrapper_t *tw = (ThreadWrapper_t *)param;
qdx_thread_entry_t entry = tw->entry;
void *arg = tw->arg;
free(tw); /* 释放包装结构,生命周期结束 */
entry(arg);
return 0;
}
/* 创建后台线程 */
int8_t qdx_port_thread_create(const char *name, qdx_thread_entry_t entry,
void *arg, uint32_t stack_size,
uint8_t priority) {
(void)name; /* Windows 线程不直接支持命名 */
(void)priority; /* 简化 Demo 不设线程优先级 */
ThreadWrapper_t *tw = (ThreadWrapper_t *)malloc(sizeof(ThreadWrapper_t));
if (!tw)
return -1;
tw->entry = entry;
tw->arg = arg;
HANDLE h =
(HANDLE)_beginthreadex(NULL, stack_size, thread_wrapper, tw, 0, NULL);
if (h == NULL) {
free(tw);
return -1;
}
/* 分离线程句柄,后台自动运行 */
CloseHandle(h);
return 0;
}