TCP 的逻辑实现,可以和客户端连接发送数据
This commit is contained in:
13058
src/Drv/Ascii.c
Normal file
13058
src/Drv/Ascii.c
Normal file
File diff suppressed because it is too large
Load Diff
165
src/TCP/tcp.c
Normal file
165
src/TCP/tcp.c
Normal 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
46
src/TCP/tcp.h
Normal 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
119
src/main.c
Normal 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"
|
||||
/* TCP:Tcp_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
164
src/menu.c
Normal 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
56
src/menu.h
Normal 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
53
src/remoteDisplay.c
Normal 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
21
src/remoteDisplay.h
Normal 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
107
src/thread_utils.c
Normal 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
35
src/thread_utils.h
Normal 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 */
|
||||
Reference in New Issue
Block a user