TCP 的逻辑实现,可以和客户端连接发送数据

This commit is contained in:
2026-03-04 10:29:20 +08:00
parent f32d91c424
commit f45b571162
16 changed files with 14027 additions and 2 deletions

13058
src/Drv/Ascii.c Normal file

File diff suppressed because it is too large Load Diff

165
src/TCP/tcp.c Normal file
View File

@@ -0,0 +1,165 @@
/*
* 跨平台 TCP 实现Windows Winsock2 / Linux BSD socket
*/
#define _WINSOCK_DEPRECATED_NO_WARNINGS 1
#include "tcp.h"
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
#pragma comment(lib, "ws2_32.lib")
static int g_wsa_started = 0;
#endif
int Tcp_Init(void)
{
#ifdef _WIN32
WSADATA wsa;
if (g_wsa_started)
return 0;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
return -1;
g_wsa_started = 1;
#endif
return 0;
}
void Tcp_Cleanup(void)
{
#ifdef _WIN32
if (g_wsa_started) {
WSACleanup();
g_wsa_started = 0;
}
#endif
}
static void close_sock(int sock)
{
if (sock == TCP_INVALID_SOCKET)
return;
#ifdef _WIN32
closesocket((SOCKET)sock);
#else
close(sock);
#endif
}
int TcpClient_Connect(const char *ip, uint16_t port)
{
struct sockaddr_in addr;
SOCKET s;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
#ifdef _WIN32
addr.sin_addr.s_addr = inet_addr(ip);
if (addr.sin_addr.s_addr == INADDR_NONE)
return TCP_INVALID_SOCKET;
#else
if (inet_pton(AF_INET, ip, &addr.sin_addr) <= 0)
return TCP_INVALID_SOCKET;
#endif
s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (s == (SOCKET)INVALID_SOCKET)
return TCP_INVALID_SOCKET;
if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
close_sock((int)s);
return TCP_INVALID_SOCKET;
}
return (int)s;
}
int TcpClient_Send(int sock, const void *buf, size_t len)
{
int n;
if (sock == TCP_INVALID_SOCKET || !buf)
return -1;
#ifdef _WIN32
n = send((SOCKET)sock, (const char *)buf, (int)len, 0);
#else
n = (int)send(sock, buf, len, 0);
#endif
return (n == SOCKET_ERROR) ? -1 : n;
}
int TcpClient_Recv(int sock, void *buf, size_t len)
{
int n;
if (sock == TCP_INVALID_SOCKET || !buf || len == 0)
return -1;
#ifdef _WIN32
n = recv((SOCKET)sock, (char *)buf, (int)len, 0);
#else
n = (int)recv(sock, buf, len, 0);
#endif
if (n == SOCKET_ERROR)
return -1;
return n;
}
void TcpClient_Close(int sock)
{
close_sock(sock);
}
int TcpServer_Listen(uint16_t port)
{
struct sockaddr_in addr;
SOCKET s;
int opt = 1;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (s == (SOCKET)INVALID_SOCKET)
return TCP_INVALID_SOCKET;
#ifdef _WIN32
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt)) != 0) {
close_sock((int)s);
return TCP_INVALID_SOCKET;
}
#else
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) != 0) {
close_sock(s);
return TCP_INVALID_SOCKET;
}
#endif
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
close_sock((int)s);
return TCP_INVALID_SOCKET;
}
if (listen(s, TCP_DEFAULT_BACKLOG) != 0) {
close_sock((int)s);
return TCP_INVALID_SOCKET;
}
return (int)s;
}
int TcpServer_Accept(int server_sock)
{
SOCKET client;
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
if (server_sock == TCP_INVALID_SOCKET)
return TCP_INVALID_SOCKET;
#ifdef _WIN32
client = accept((SOCKET)server_sock, (struct sockaddr *)&peer, &len);
#else
client = accept(server_sock, (struct sockaddr *)&peer, &len);
#endif
if (client == (SOCKET)INVALID_SOCKET)
return TCP_INVALID_SOCKET;
return (int)client;
}
void TcpServer_Close(int server_sock)
{
close_sock(server_sock);
}

46
src/TCP/tcp.h Normal file
View File

@@ -0,0 +1,46 @@
#ifndef PC_HMI_TCP_H
#define PC_HMI_TCP_H
/*
* 跨平台 TCP 通信模块
* - Windows: Winsock2
* - Linux/其他: BSD socket
* 提供 TCP 客户端:连接、发送、接收、断开
*/
#include <stddef.h>
#include <stdint.h>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
typedef int SOCKET;
#define INVALID_SOCKET (-1)
#define SOCKET_ERROR (-1)
#endif
#define TCP_INVALID_SOCKET (-1)
#define TCP_DEFAULT_BACKLOG 5
/* 初始化/清理网络库Windows 下必须最先/最后调用) */
int Tcp_Init(void);
void Tcp_Cleanup(void);
/* TCP 客户端 */
int TcpClient_Connect(const char *ip, uint16_t port);
int TcpClient_Send(int sock, const void *buf, size_t len);
int TcpClient_Recv(int sock, void *buf, size_t len);
void TcpClient_Close(int sock);
/* TCP 服务器 */
int TcpServer_Listen(uint16_t port);
int TcpServer_Accept(int server_sock);
void TcpServer_Close(int server_sock);
#endif /* PC_HMI_TCP_H */

119
src/main.c Normal file
View File

@@ -0,0 +1,119 @@
/* ============================================================================
* main.c - PC 端 HMI 菜单主程序
* 功能:菜单交互(主线程)+ TCP 服务器(独立线程),按 Q 退出
* ========================================================================== */
/* 标准输入输出printf、fprintf、stderr 等 */
#include <stdio.h>
/* 标准库malloc、exit、system 等 */
#include <stdlib.h>
#ifdef _WIN32
/* Windows控制台无回显读键 _getch() */
#include <conio.h>
#else
/* Linux/Unix终端属性与读键实现 */
#include <termios.h>
#include <unistd.h>
/* 无回显读入一个字符Linux 下替代 _getch */
static int getch(void)
{
struct termios oldt, newt; /* 当前/新终端属性 */
int ch;
tcgetattr(STDIN_FILENO, &oldt); /* 保存当前终端属性 */
newt = oldt; /* 复制一份用于修改 */
newt.c_lflag &= ~(ICANON | ECHO); /* 关闭规范模式与回显,实现“按即得” */
tcsetattr(STDIN_FILENO, TCSANOW, &newt); /* 应用新属性 */
ch = getchar(); /* 读一个字符 */
tcsetattr(STDIN_FILENO, TCSANOW, &oldt); /* 恢复原终端属性 */
return ch;
}
#endif
/* 菜单MenuItem、MenuCtrl、Menu_InitPC、Menu_LoopStep、Menu_MapKey、KEY_* */
#include "menu.h"
/* TCPTcp_Init/Cleanup、TcpServer_Listen/Accept/Close、TcpClient_Send/Recv/Close */
#include "TCP/tcp.h"
/* 线程Thread_Create、Thread_Join、thread_handle_t */
#include "thread_utils.h"
/* 远程显示 / TCP 服务器线程server_thread_arg_t、tcp_server_thread_fn */
#include "remoteDisplay.h"
/* ----------------------------------------------------------------------------
* 程序入口
* ---------------------------------------------------------------------------- */
int main(void)
{
#ifdef _WIN32
/* Windows将控制台代码页设为 UTF-8避免中文乱码如“监听”“退出”等 */
system("chcp 65001 > nul");
#endif
/* 初始化网络库Windows 下必须最先调用,否则 Winsock 不可用) */
if (Tcp_Init() != 0) {
fprintf(stderr, "Tcp_Init failed\n");
return 1;
}
/* 主线程持有的监听 socket由服务器线程在 Listen 成功后回写,退出时主线程用来 Close 以唤醒 Accept */
int server_sock = TCP_INVALID_SOCKET;
/* 退出标志:主线程置 1 后,服务器线程在下一轮循环或 Accept 返回后退出 */
volatile int server_quit = 0;
/* 传给服务器线程的参数(端口、回写 socket 的指针、退出标志指针) */
server_thread_arg_t server_arg = {
.port = 7070, /* TCP 监听端口 */
.p_server_sock = &server_sock, /* 服务器线程将监听 socket 写回此处 */
.p_quit = &server_quit, /* 主线程通过此处通知服务器线程退出 */
};
thread_handle_t server_th; /* 服务器线程句柄,用于后续 Thread_Join 等待其结束 */
if (Thread_Create(tcp_server_thread_fn, &server_arg, &server_th) != 0) { /* 创建并启动服务器线程 */
fprintf(stderr, "Thread_Create(server) failed\n");
Tcp_Cleanup(); /* 创建失败则清理网络库后退出 */
return 1;
}
#ifdef _WIN32
Sleep(200); /* Windows等待 200ms让服务器线程完成 Listen 并写回 server_sock */
#else
usleep(200000); /* Linux同上200ms = 200000 微秒 */
#endif
MenuCtrl ctrl; /* 菜单控制结构当前选中项、0 级头节点等 */
int ch, key; /* ch键盘原始字符key映射后的 KEY_U/KEY_D/KEY_ENT 等 */
Menu_InitPC(&ctrl); /* 初始化菜单树(运行界面/定值设置/查看数据等) */
printf("PC 端 HMI 菜单模拟启动TCP 服务在单独线程,端口 7070\n");
printf("--------------------------------------------------\n");
Menu_LoopStep(&ctrl, KEY_NONE); /* 用 KEY_NONE 触发一次重绘,显示当前菜单 */
while (1) { /* 主循环:处理键盘并驱动菜单,直到用户按 Q 退出 */
printf("\n按键(W/S/A/D/Enter/Esc)Q 退出:");
#ifdef _WIN32
ch = _getch(); /* Windows无回显读键 */
#else
ch = getch(); /* Linux上面实现的 getch() */
#endif
if (ch == 'q' || ch == 'Q') { /* 用户选择退出 */
printf("\n正在退出...\n");
break;
}
key = Menu_MapKey(ch); /* 将字符映射为菜单键W→KEY_U 等) */
if (key == KEY_NONE) { /* 非菜单键则忽略 */
printf(" (忽略)\n");
continue;
}
printf("\n");
Menu_LoopStep(&ctrl, key); /* 根据按键更新当前菜单项并重绘 */
}
server_quit = 1; /* 通知服务器线程退出:下一轮 while 或 Accept 返回后会结束循环 */
if (server_sock != TCP_INVALID_SOCKET) { /* 若监听 socket 已有效(服务器线程已启动成功) */
TcpServer_Close(server_sock); /* 关闭监听,使 Accept 返回,服务器线程得以退出 */
server_sock = TCP_INVALID_SOCKET; /* 避免重复关闭 */
}
Thread_Join(server_th); /* 等待服务器线程完全退出 */
Tcp_Cleanup(); /* 清理网络库Windows 下必须调用) */
return 0;
}

164
src/menu.c Normal file
View File

@@ -0,0 +1,164 @@
#include "menu.h"
#include <stdio.h>
#include <string.h>
/* 简单的静态菜单树:
*
* 层级 0: [运行界面] [定值设置] [查看数据]
* │ │
* 层级 1: (子菜单...) (子菜单...)
*/
static MenuItem g_menuRun;
static MenuItem g_menuSet;
static MenuItem g_menuSee;
static MenuItem g_menuSet1;
static MenuItem g_menuSet2;
void Menu_InitPC(MenuCtrl *ctrl)
{
memset(&g_menuRun, 0, sizeof(g_menuRun));
memset(&g_menuSet, 0, sizeof(g_menuSet));
memset(&g_menuSee, 0, sizeof(g_menuSee));
memset(&g_menuSet1, 0, sizeof(g_menuSet1));
memset(&g_menuSet2, 0, sizeof(g_menuSet2));
/* 0 级菜单 */
g_menuRun.name = "运行界面";
g_menuRun.tip = "显示运行状态";
g_menuRun.level = 0;
g_menuRun.pos = 0;
g_menuSet.name = "定值设置";
g_menuSet.tip = "进入定值设置菜单";
g_menuSet.level = 0;
g_menuSet.pos = 1;
g_menuSee.name = "查看数据";
g_menuSee.tip = "查看测量值和记录";
g_menuSee.level = 0;
g_menuSee.pos = 2;
/* 0 级链表关系 */
g_menuRun.before = &g_menuSee;
g_menuRun.behind = &g_menuSet;
g_menuSet.before = &g_menuRun;
g_menuSet.behind = &g_menuSee;
g_menuSee.before = &g_menuSet;
g_menuSee.behind = &g_menuRun;
/* 1 级菜单:定值设置下的两个子菜单 */
g_menuSet1.name = "保护定值";
g_menuSet1.tip = "编辑保护定值";
g_menuSet1.level = 1;
g_menuSet1.pos = 0;
g_menuSet1.higher = &g_menuSet;
g_menuSet2.name = "通信参数";
g_menuSet2.tip = "编辑通信参数";
g_menuSet2.level = 1;
g_menuSet2.pos = 1;
g_menuSet2.higher = &g_menuSet;
g_menuSet1.before = &g_menuSet2;
g_menuSet1.behind = &g_menuSet2;
g_menuSet2.before = &g_menuSet1;
g_menuSet2.behind = &g_menuSet1;
g_menuSet.lower = &g_menuSet1;
/* 初始化控制结构 */
ctrl->head0 = &g_menuRun;
ctrl->current = &g_menuRun;
}
/* 控制台绘制当前菜单状态 */
static void Menu_Draw(const MenuCtrl *ctrl)
{
const MenuItem *cur = ctrl->current;
const MenuItem *p;
printf("\n==== PC HMI 菜单模拟 ====\n");
printf("使用 W/S/A/D/Enter/Esc 操作Ctrl+C 退出。\n\n");
/* 显示 0 级菜单 */
printf("[0级菜单]: ");
p = ctrl->head0;
if (!p) {
printf("(空)\n");
return;
}
const MenuItem *start = p;
do {
if (p == cur)
printf(" [*%s*] ", p->name);
else
printf(" %s ", p->name);
p = p->behind;
} while (p && p != start);
printf("\n");
/* 若当前有下级,则显示 1 级菜单 */
if (cur->lower) {
printf(" 子菜单:\n");
p = cur->lower;
start = p;
do {
printf(" - %s\n", p->name);
p = p->behind;
} while (p && p != start);
}
/* 提示信息 */
printf("\n提示: %s\n", cur->tip ? cur->tip : "");
}
void Menu_LoopStep(MenuCtrl *ctrl, int key)
{
MenuItem *cur = ctrl->current;
switch (key) {
case KEY_U:
if (cur->before)
cur = cur->before;
break;
case KEY_D:
if (cur->behind)
cur = cur->behind;
break;
case KEY_L:
if (cur->higher)
cur = cur->higher;
break;
case KEY_R:
case KEY_ENT:
if (cur->lower)
cur = cur->lower;
printf("\n>>> 进入 [%s] 功能(模拟用户层)...\n", cur->name);
break;
case KEY_ESC:
if (cur->higher)
cur = cur->higher;
break;
default:
break;
}
ctrl->current = cur;
Menu_Draw(ctrl);
}
int Menu_MapKey(int ch)
{
if (ch == 'w' || ch == 'W') return KEY_U;
if (ch == 's' || ch == 'S') return KEY_D;
if (ch == 'a' || ch == 'A') return KEY_L;
if (ch == 'd' || ch == 'D') return KEY_R;
if (ch == '\r' || ch == '\n') return KEY_ENT;
if (ch == 27) return KEY_ESC; /* ESC */
return KEY_NONE;
}

56
src/menu.h Normal file
View File

@@ -0,0 +1,56 @@
#ifndef PC_HMI_MENU_H
#define PC_HMI_MENU_H
/*
* PC 端菜单模拟环境
*
* 目标:
* - 用纯 C 语言在控制台模拟嵌入式 HMI 的菜单逻辑
* - 键值映射W/S/A/D/Enter/Esc → 上/下/左/右/确认/退出
* - 结构设计尽量贴近原工程的 g_tMenuCtrl / Menu_Route / Menu_Show_Proc
*/
#include <stdint.h>
typedef struct MenuItem MenuItem;
struct MenuItem {
const char *name; /* 菜单名称 */
const char *tip; /* 底部提示信息 */
MenuItem *higher; /* 上级菜单 */
MenuItem *lower; /* 下级第一个菜单 */
MenuItem *before; /* 同级上一个 */
MenuItem *behind; /* 同级下一个 */
uint8_t level; /* 菜单级别0/1/2/... */
uint8_t pos; /* 在同级菜单中的序号(从 0 开始) */
};
typedef struct {
MenuItem *head0; /* 0 级菜单头结点 */
MenuItem *current; /* 当前选中的菜单结点 */
} MenuCtrl;
/* 初始化一棵简单的测试菜单树 */
void Menu_InitPC(MenuCtrl *ctrl);
/* 处理一帧输入,并在控制台绘制菜单 */
void Menu_LoopStep(MenuCtrl *ctrl, int key);
/* 键值定义:与嵌入式逻辑对应的抽象键 */
enum {
KEY_NONE = 0,
KEY_U,
KEY_D,
KEY_L,
KEY_R,
KEY_ENT,
KEY_ESC,
};
/* 从键盘输入字符映射到 KEY_* 抽象键W/S/A/D/Enter/Esc */
int Menu_MapKey(int ch);
#endif /* PC_HMI_MENU_H */

53
src/remoteDisplay.c Normal file
View File

@@ -0,0 +1,53 @@
/*
* remoteDisplay.c - TCP 服务器线程实现
* 逻辑等待客户端连接循环接收消息并原样回发echo直到客户端断开或主线程请求退出
*/
#include <stdio.h>
#include <string.h>
#include "TCP/tcp.h"
#include "remoteDisplay.h"
void tcp_server_thread_fn(void* arg)
{
server_thread_arg_t* ctx = (server_thread_arg_t*)arg; /* 强转为参数结构体 */
int server_sock = TcpServer_Listen((uint16_t)ctx->port); /* 在指定端口创建监听 socket */
if (server_sock == TCP_INVALID_SOCKET) { /* 监听失败(端口占用或权限等) */
fprintf(stderr, "[TCP Server] TcpServer_Listen(%d) failed\n", ctx->port);
return; /* 线程直接退出,主线程通过 server_sock 仍为 INVALID 可知失败 */
}
*ctx->p_server_sock = server_sock; /* 回写监听 socket主线程可用来关闭以结束 Accept 阻塞 */
printf("[TCP Server] 监听端口 %d等待客户端连接...\n", ctx->port);
char recv_buf[1024]; /* 接收缓冲区,用于收客户端数据并回发 */
while (!*ctx->p_quit) { /* 主线程未请求退出时一直循环 */
int client = TcpServer_Accept(server_sock); /* 阻塞等待一个客户端连接 */
if (client == TCP_INVALID_SOCKET) { /* Accept 失败(含:主线程已关闭 server_sock */
if (*ctx->p_quit) /* 若是因退出请求导致,则跳出循环 */
break;
continue; /* 否则忽略本次错误,继续下一次 Accept */
}
printf("[TCP Server] 客户端连接成功\n");
/* 只要该客户端还连着,就循环等待消息并回发 */
while (!*ctx->p_quit) {
printf("[TCP Server] 等待客户端消息\n");
memset(recv_buf, 0, sizeof(recv_buf));
int n = TcpClient_Recv(client, recv_buf, sizeof(recv_buf) - 1);
if (n > 0) {
printf("recv: %s\n", recv_buf);
TcpClient_Send(client, recv_buf, (size_t)n);
} else {
/* n==0 表示对方关闭连接n<0 表示出错,退出本连接循环 */
printf("[TCP Server] 客户端关闭连接\n");
break;
}
}
TcpClient_Close(client);
}
TcpServer_Close(server_sock); /* 关闭监听 socket */
*ctx->p_server_sock = TCP_INVALID_SOCKET; /* 通知主线程 socket 已关闭,避免重复 Close */
printf("[TCP Server] 已退出\n");
}

21
src/remoteDisplay.h Normal file
View File

@@ -0,0 +1,21 @@
#ifndef REMOTE_DISPLAY_H
#define REMOTE_DISPLAY_H
/*
* 远程显示 / TCP 服务器相关接口
* - server_thread_arg_t: TCP 服务器线程参数
* - tcp_server_thread_fn: 服务器线程入口函数(用于 Thread_Create
*/
typedef struct {
int port; /* 监听端口号(如 7070 */
int* p_server_sock; /* 线程内赋值为监听 socket主线程用其关闭监听 */
volatile int* p_quit; /* 主线程置 1 通知线程退出 */
} server_thread_arg_t;
/* TCP 服务器线程函数:在独立线程中监听端口并处理客户端收/发 */
void tcp_server_thread_fn(void* arg);
#endif /* REMOTE_DISPLAY_H */

107
src/thread_utils.c Normal file
View File

@@ -0,0 +1,107 @@
/*
* thread_utils.c - 跨平台线程封装实现
* - Windows: _beginthreadex / WaitForSingleObject / CloseHandle
* - Linux: pthread_create / pthread_join
*/
#include "thread_utils.h"
#include <stdint.h> /* uintptr_t用于 Windows 线程句柄转换 */
#include <stdlib.h> /* malloc / free */
#ifdef _WIN32
#include <process.h> /* _beginthreadex */
#endif
#ifdef _WIN32
/*
* Windows 线程入口__stdcall参数为 Thread_Create 传入的包装结构体指针
* 从结构中取出用户函数与参数,释放结构体后调用用户函数
*/
static unsigned int __stdcall thread_wrapper(void* arg)
{
struct {
thread_fn_t fn;
void* arg;
}* p = (void*)arg;
thread_fn_t fn = p->fn; /* 用户提供的线程函数 */
void* fn_arg = p->arg; /* 传给用户函数的参数 */
free(p); /* 包装结构体仅在此处使用,用完即释 */
fn(fn_arg); /* 调用用户线程函数 */
return 0; /* _beginthreadex 要求返回 unsigned int */
}
#else
/*
* Linux 线程入口:参数为 Thread_Create 传入的包装结构体指针
* 从结构中取出用户函数与参数,释放结构体后调用用户函数
*/
static void* thread_wrapper(void* arg)
{
struct {
thread_fn_t fn;
void* arg;
}* p = arg;
thread_fn_t fn = p->fn;
void* fn_arg = p->arg;
free(p);
fn(fn_arg);
return NULL; /* pthread 入口要求返回 void* */
}
#endif
/*
* 创建新线程,在新线程中执行 fn(arg)
*
* fn: 线程函数,类型 void fn(void* arg)
* arg: 传给 fn 的参数,由调用方保证在线程使用期间有效
* out_handle: 输出线程句柄,用于后续 Thread_Join 等待
*
* 返回: 0 成功,-1 失败参数无效、malloc 失败或系统创建线程失败)
*/
int Thread_Create(thread_fn_t fn, void* arg, thread_handle_t* out_handle)
{
if (!fn || !out_handle)
return -1;
/* 在堆上分配包装结构,因线程启动前就返回,线程入口需要独立副本 */
struct {
thread_fn_t fn;
void* arg;
}* p = malloc(sizeof(*p));
if (!p)
return -1;
p->fn = fn;
p->arg = arg;
#ifdef _WIN32
{
uintptr_t t = _beginthreadex(NULL, 0, thread_wrapper, p, 0, NULL);
if (t == 0) { /* 0 表示创建失败 */
free(p);
return -1;
}
*out_handle = (HANDLE)(uintptr_t)t; /* 保存句柄供 Thread_Join 使用 */
}
#else
if (pthread_create(out_handle, NULL, thread_wrapper, p) != 0) {
free(p);
return -1;
}
#endif
return 0;
}
/*
* 等待指定线程结束,并释放系统资源
*
* handle: Thread_Create 返回的线程句柄
* Windows 下会 CloseHandle之后 handle 不可再使用
*/
void Thread_Join(thread_handle_t handle)
{
#ifdef _WIN32
if (handle != NULL && handle != (HANDLE)0)
WaitForSingleObject(handle, INFINITE); /* 阻塞直到线程退出 */
if (handle != NULL && handle != (HANDLE)0)
CloseHandle(handle); /* 释放线程内核对象 */
#else
pthread_join(handle, NULL); /* 阻塞直到线程退出,不取返回值 */
#endif
}

35
src/thread_utils.h Normal file
View File

@@ -0,0 +1,35 @@
#ifndef PC_HMI_THREAD_UTILS_H
#define PC_HMI_THREAD_UTILS_H
/*
* 跨平台线程封装
* - Windows: _beginthreadex
* - Linux: pthread
*/
#include <stddef.h>
#ifdef _WIN32
#include <process.h>
#include <windows.h>
typedef HANDLE thread_handle_t;
#else
#include <pthread.h>
typedef pthread_t thread_handle_t;
#endif
/* 线程函数类型void thread_fn(void* arg) */
typedef void (*thread_fn_t)(void* arg);
/*
* 创建线程fn 在新区程中执行arg 传给 fn
* 返回 0 成功,非 0 失败out_handle 用于后续 Thread_Join
*/
int Thread_Create(thread_fn_t fn, void* arg, thread_handle_t* out_handle);
/*
* 等待线程结束
*/
void Thread_Join(thread_handle_t handle);
#endif /* PC_HMI_THREAD_UTILS_H */