串口服务器— 设计方案
UART转以太网服务器解析:完整代码解析与流程图
一、项目概述
本项目实现了一个嵌入式Linux下的串口转以太网服务器,它可以:
-
通过JSON配置文件动态指定工作模式(TCP Server 或 TCP Client)
-
实时监听配置文件变化(inotify),无需重启进程即可应用新配置
-
作为 TCP Server 时,支持多个客户端同时连接,将串口数据广播给所有客户端,并将任意客户端发来的数据转发到串口
-
作为 TCP Client 时,主动连接远程服务器,实现串口与远程网络的双向透传
-
提供串口参数配置(波特率、数据位、校验位、停止位等)
程序完全使用 C 语言编写,依赖 cJSON 库解析配置,使用 epoll 处理高并发网络,使用双向循环链表管理客户端连接,支持守护进程模式。
二、系统流程图

三、代码模块详解
-
事件标志速查表
事件标志 含义 触发条件 代码中的用途 EPOLLIN文件描述符可读 有数据到达(socket 接收缓冲区非空) 监听客户端或串口是否有数据可读 EPOLLRDHUP对方关闭连接(或半关闭) TCP 连接收到 FIN 包(对端调用 close或shutdown)快速检测客户端正常关闭,避免多一次 recv调用EPOLLET边缘触发模式 状态发生变化时只通知一次 提高性能,减少 epoll_wait调用次数,配合非阻塞 I/OEPOLLHUP挂起事件 对端挂起(如连接断开、设备被移除) 检测异常断开,清理资源 EPOLLERR错误事件 文件描述符发生错误(如连接被重置 ECONNRESET)检测连接错误,关闭并清理 EPOLLOUT文件描述符可写 发送缓冲区有空闲空间(对于非阻塞 socket) 代码中未使用,但在需要异步发送大数据时很有用 -
主循环:
-
新连接到来:
accept后设置非阻塞,注册EPOLLIN | EPOLLRDHUP | EPOLLET(边缘触发),将客户端信息插入链表,计数增加。 -
串口可读:调用
read_serial读取数据,然后遍历链表向所有客户端send。若发送失败(对方已关闭),则从链表中删除该客户端并关闭 socket。 -
客户端可读:
recv数据,然后写入串口。 -
客户端断开/异常:
EPOLLRDHUP或EPOLLERR触发,关闭 socket 并从链表中删除。
-
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/inotify.h>// inotify_init, inotify_add_watch, inotify_event
#include <sys/stat.h> // stat, struct stat
#include <syslog.h>
#include <cJSON.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <errno.h>
#include <pthread.h>
#include <UART.h>
#include <sys/epoll.h>
#include <poll.h>
#include <string.h>
#include "double_link_list.h"
#include "inotify_create_server_clinet.h"
#define MAX_EVENTS 64
int create_fork(int status);
void kill_child_fork(int pid);
int parse_config_file(const char *file_path);
int reload_server_create(char *ip,int port);
int reload_client_create(char *ip,int port);
enum
{
SERVER = 1,
CLIENT,
LINK_CLIENT,
UNLINK_CLIENT
};
struct server_or_client
{
pid_t pid;
int status;
char server_ip[64];
int server_port;
char client_ip[64];
int client_port;
};
struct uart_status
{
int fd_uart;
char device[20];
int baudrate;
int data_bits;
char parity[10];
int stop_bits;
};
struct info_client_status
{
int client_socket;
int link_status;
};
// 全局结构体实例
static struct server_or_client g_inet_status = {0};
static struct uart_status g_uart_status = {0};
//链表表头
LLIST *list_head;
//fd设置为非阻塞
static int set_nonblocking(int fd)
{
int flags = fcntl(fd, F_GETFL, 0);
if(flags == -1)
return -1;
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
//删除链表(查找回调函数)
static int client_id_cmp(const void *key,const void *record)
{
const int *socket = key;
const struct info_client_status *p = record;
return (*socket - p->client_socket);
}
/******* 串口重开 ******/
int reopen_serial(void)
{
// 从配置文件重读串口参数并重开
int new_fd = open_port(g_uart_status.device);
g_uart_status.fd_uart = 0;
if(new_fd > 0)
{
set_opt(new_fd, g_uart_status.baudrate,\
g_uart_status.data_bits, g_uart_status.parity,\
g_uart_status.stop_bits);
set_nonblocking(new_fd);
}
else
return -1;
return new_fd;
}
/******* 串口重开校验 ***********/
int handle_serial_check(int epfd, int *fd, struct epoll_event *ev_server)
{
static int reopen_serial_num = 0;
epoll_ctl(epfd, EPOLL_CTL_DEL, *fd, NULL);
close(*fd);
*fd = -1;
while(reopen_serial_num < 3)
{
int new_fd = reopen_serial();
if(new_fd >= 0)
{
reopen_serial_num = 0;
ev_server->events = EPOLLIN;
ev_server->data.fd = new_fd;
*fd = new_fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, new_fd, ev_server);
return 0; // 重开成功
}
reopen_serial_num++;
sleep(1);
}
return -1;
}
static int daemonize()
{
int fd;
pid_t pid;
pid = fork();
if(pid < 0)
return -1;
if(pid > 0)
exit(0);
fd = open("/dev/null",O_RDWR);
if(fd < 0)
return -1;
dup2(fd,0);
dup2(fd,1);
dup2(fd,2);
if(fd > 2)
close(fd);
//创建守护进程
setsid();
chdir("/");
umask(0);
return 0;
}
/* 1.监视文件 是/否 修改 */
void watch_file_modify(const char *file_path)
{
struct stat statbuf;
int fd,wd;
int r_len;
char buf[BUF_LEN];
//openlog("inotify_daemonize",LOG_PID,LOG_DAEMON);
// if(daemonize())
// {
// syslog(LOG_ERR,"daemonize failed!");
// exit(1);
// }
// else
// syslog(LOG_INFO,"daemonize() success!");
while(1)
{
if(parse_config_file(file_path) == 0)
break;
sleep(1);
}
// 1.等待文件创建
while(stat(file_path,&statbuf) == -1)
{
printf("文件未创建 等待文件创建, 行 %d\n",__LINE__);
sleep(1);
}
printf("文件已完成创建开始监听(配置文件), 行 %d\n",__LINE__);
// 2.初始化inotify + 设置监听事件
fd = inotify_init();
if(fd < 0)
{
printf("inotify 初始化失败, 行 %d\n",__LINE__);
exit(1);
}
wd = inotify_add_watch(fd,file_path, IN_CLOSE_WRITE);
if(wd < 0)
{
printf("inotify 无法监听文件:%s, 行 %d\n",WATCH_PATH,__LINE__);
closelog();
close(fd);
exit(1);
}
printf("开始监听文件:%s, 行 %d\n",WATCH_PATH,__LINE__);
// 3.循环读取事件
while(1)
{
r_len = read(fd,buf,BUF_LEN);
if(r_len < 0)
{
printf("read 读取修改事件失败, 行 %d\n",__LINE__);
break;
}
int i = 0;
while(i < r_len)
{
struct inotify_event *event = (struct inotify_event *)&buf[i];
if(event->mask & IN_CLOSE_WRITE)
{
printf("文件以被修改 %s, 行 %d\n",WATCH_PATH,__LINE__);
int ret = parse_config_file(WATCH_PATH);
if(ret < 0)
{
printf("配置文件空, 行 %d\n",__LINE__);
continue;
}
}
i += EVENT_SIZE + event->len;
}
}
inotify_rm_watch(fd,wd);
close(fd);
//closelog();
exit(0);
}
/* 2.配置文件 被修改 解析配置文件 */
int parse_config_file(const char *file_path)
{
FILE *fp_cfg;
int len;
char *data;
pthread_t tid;
// 1.读配置文件
fp_cfg = fopen(file_path,"r");
if(fp_cfg == NULL)
{
printf("pares配置文件,打开失败%s\n",file_path);
return -1;
}
fseek(fp_cfg,0,SEEK_END);
len = ftell(fp_cfg);
fseek(fp_cfg,0,SEEK_SET);
data = (char *)malloc(len + 1);
if(data == NULL)
{
fclose(fp_cfg);
return -1;
}
fread(data,1,len,fp_cfg);
data[len] = '\0';
fclose(fp_cfg);
// 2.解析
cJSON *root = cJSON_Parse(data);
free(data);
if (!root)
{
fprintf(stderr, "ERROR: 解析cJSON_Parse 失败\n");
return -1;
}
cJSON *Item;
/* 解析串口配置是否被更改 */
char uart_name[50];
int baudrate = g_uart_status.baudrate;
int data_bits = g_uart_status.data_bits;
char parity[10];
int stop_bits = g_uart_status.stop_bits;
strncpy(uart_name, g_uart_status.device, sizeof(uart_name)-1);
uart_name[sizeof(uart_name)-1] = '\0';
strncpy(parity, g_uart_status.parity, sizeof(parity)-1);
parity[sizeof(parity)-1] = '\0';
cJSON *uart = cJSON_GetObjectItem(root,"serial");
if((Item = cJSON_GetObjectItem(uart, "device")) && cJSON_IsString(Item))
strncpy(uart_name, Item->valuestring, sizeof(uart_name)-1);
if((Item = cJSON_GetObjectItem(uart, "baudrate")) && cJSON_IsNumber(Item))
baudrate = Item->valueint;
if((Item = cJSON_GetObjectItem(uart, "data_bits")) && cJSON_IsNumber(Item))
data_bits = Item->valueint;
if((Item = cJSON_GetObjectItem(uart, "parity")) && cJSON_IsString(Item))
strncpy(parity, Item->valuestring, sizeof(parity)-1);
if((Item = cJSON_GetObjectItem(uart, "stop_bits")) && cJSON_IsNumber(Item))
stop_bits = Item->valueint;
//判断串口设备配置是否被更改
if( !g_uart_status.fd_uart || (g_uart_status.fd_uart ||\
strcmp(uart_name,g_uart_status.device) != 0 ||\
strcmp(parity,g_uart_status.parity) != 0 || baudrate != g_uart_status.baudrate ||
data_bits != g_uart_status.data_bits || stop_bits != g_uart_status.stop_bits))
{
if (g_uart_status.fd_uart > 0)
{
close(g_uart_status.fd_uart);
}
g_uart_status.fd_uart = open_port(uart_name);
if(g_uart_status.fd_uart < 0)
{
printf("串口设备打开失败: [%s], 行 %d\n",uart_name,__LINE__);
return -1;
}
else
printf("串口设备打开成功: [%s], 行 %d\n",uart_name,__LINE__);
int ret = set_opt(g_uart_status.fd_uart,baudrate,data_bits,parity,stop_bits);
if(ret != 0)
printf("串口配置失败,%d\n",__LINE__);
//更新全局结构体的串口配置
memcpy(g_uart_status.device,uart_name,sizeof(uart_name));
g_uart_status.baudrate = baudrate;
g_uart_status.data_bits = data_bits;
memcpy(g_uart_status.parity,parity,sizeof(parity));
g_uart_status.stop_bits = stop_bits;
printf("【%s】串口设备打开,波特率: %d、数据位: %d、校验位: %s、停止位: %d, 行 %d\n",uart_name,baudrate,data_bits,parity,stop_bits,__LINE__);
}
// 2.1解析网络参数是否更改,并创建网络
char work_mode[10];
char ip[64];
char addrs[20];
char ports[20];
int port;
cJSON *network = cJSON_GetObjectItem(root,"network");
if((Item = cJSON_GetObjectItem(network, "work_mode")) && cJSON_IsString(Item))
strncpy(work_mode, Item->valuestring, sizeof(work_mode)-1);
// 2.2判断是否由服务器改为客户端,如更改杀死服务器进程 或 服务器、客户端口改变重新创建进程
if(g_inet_status.status && strcmp(work_mode,"server") == 0 && g_inet_status.status == CLIENT)
kill_child_fork(g_inet_status.pid);
if(strcmp(work_mode,"server") == 0)
{
strncpy(addrs,"local_ip",sizeof("local_ip"));
strncpy(ports,"local_port",sizeof("local_port"));
g_inet_status.status = SERVER;
}
else if(strcmp(work_mode,"client") == 0)
{
strncpy(addrs,"remote_addr",sizeof("remote_addr"));
strncpy(ports,"remote_port",sizeof("remote_port"));
g_inet_status.status = CLIENT;
}
else
{
fprintf(stderr, "输入参数错误 '%s' 无对应网络模式, 行 %d\n", work_mode,__LINE__);
return -1;
}
if((Item = cJSON_GetObjectItem(network, addrs)) && cJSON_IsString(Item))
strncpy(ip, Item->valuestring, sizeof(ip)-1);
else
{
fprintf(stderr, "网络IP错误 '%s' ,行 %d\n", addrs,__LINE__);
return -1;
}
if((Item = cJSON_GetObjectItem(network, ports)) && cJSON_IsNumber(Item))
port = Item->valueint;
else
{
fprintf(stderr, "网络端口输入错误 '%s' ,行 %d\n", ports,__LINE__);
return -1;
}
if(g_inet_status.status == SERVER)
{
strncpy(g_inet_status.server_ip,ip,sizeof(ip));
//判断SERVER端IP是否更改
if(g_inet_status.server_port == port)
return 0;
else
{
int only_one_create = 0;
g_inet_status.server_port = port;
// 首次启动
if (g_inet_status.pid == 0)
{
only_one_create = 1;
create_fork(g_inet_status.status);
}
if(g_inet_status.pid)
kill_child_fork(g_inet_status.pid);
if(!only_one_create)
create_fork(g_inet_status.status);
}
}
else if(g_inet_status.status == CLIENT)
{
//判断CLIENT端 IP是否更改
if((strcmp(g_inet_status.client_ip,ip) != 0) || g_inet_status.client_port != port)
{
strncpy(g_inet_status.client_ip,ip,sizeof(ip));
g_inet_status.client_port = port;
if (g_inet_status.pid == 0)
create_fork(g_inet_status.status);
else
{
kill_child_fork(g_inet_status.pid);
create_fork(g_inet_status.status);
}
}
}
return 0;
}
/* 3.创建一个进程 */
int create_fork(int status)
{
pid_t pid_s;
printf("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
pid_s = fork();
if(pid_s < 0)
{
printf("配置文件改写后,服务器进程创建失败, 行 %d\n",__LINE__);
return -1;
}
else if(pid_s > 0)
{
printf("配置文件修改,服务器进程创建成功, 行 %d\n",__LINE__);
}
else
{
g_inet_status.pid = getpid();
if(g_inet_status.status == SERVER)
{
printf("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
reload_server_create(g_inet_status.server_ip,g_inet_status.server_port);
}
else if(g_inet_status.status == CLIENT)
reload_client_create(g_inet_status.client_ip,g_inet_status.client_port);
}
}
// 处理串口读 + 广播
void handle_serial_event(int epfd, int *uart_fd, struct epoll_event *ev, char *buf, int buf_len)
{
int total_read = 0;
int n;
printf("串口事件触发,串口以读取数据 %d\n",*uart_fd);
while (1)
{
n = read(*uart_fd, buf + total_read, buf_len - total_read);
if (n > 0)
{
total_read += n;
// 广播给所有客户端
struct llist_node_st *cur, *next;
for (cur = list_head->head.next; cur != &list_head->head; cur = next)
{
next = cur->next;
struct info_client_status *data = (struct info_client_status*)cur->data;
int s = send(data->client_socket, buf, n, MSG_NOSIGNAL);
printf("服务器将串口数据 发送至 客户端[%d] %s\n",data->client_socket,buf);
if (s < 0 && errno != EAGAIN)
{
epoll_ctl(epfd, EPOLL_CTL_DEL, data->client_socket, NULL);
printf("客户端退出 成员 :%d, 行 %d\n",data->client_socket,__LINE__);
close(data->client_socket);
list_head->delete(list_head, &data->client_socket, client_id_cmp);
}
}
if (total_read >= buf_len)
break;
continue;
}
// 无数据
if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
return;
// 出错或断开 ,无法重启,重开
printf("串口设备异常, revents=0x%x, 尝试重开, 行 %d\n", ev->events, __LINE__);
if (handle_serial_check(epfd, uart_fd, ev) < 0)
{
fprintf(stderr, "串口重开失败,退出\n");
exit(1);
}
}
}
// 处理客户端读 + 转发串口 + 断开
void handle_client_event(int epfd, int fd, uint32_t events, int *client_num)
{
printf("客户端事件 fd=%d 行%d\n", fd,__LINE__);
// 断开/异常
if (events & (EPOLLHUP | EPOLLRDHUP | EPOLLERR))
{
printf("客户端异常事件 fd=%d revents=0x%x\n", fd, events);
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
printf("Delete 客户端成员[%d] 链表位置[%d], 行 %d\n",fd,*client_num,__LINE__);
close(fd);
list_head->delete(list_head, &fd, client_id_cmp);
(*client_num)--;
return;
}
// 可读
if (events & EPOLLIN)
{
char buf[1024];
int n;
while (1)
{
n = recv(fd, buf, sizeof(buf), MSG_NOSIGNAL);
if (n > 0)
{
write(g_uart_status.fd_uart, buf, n);
printf("服务器 成功将 客户端信息发送到 串口 %s, %d\n",buf, __LINE__);
}
else if (n == 0 || (n < 0 && errno != EAGAIN))
{
printf("客户端异常事件 fd=%d revents=0x%x\n", fd, events);
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
printf("Delete 客户端成员[%d] 链表位置[%d], 行 %d\n",fd,*client_num,__LINE__);
close(fd);
list_head->delete(list_head, &fd, client_id_cmp);
(*client_num)--;
break;
}
else
break;
}
}
}
/* 4.根据配置文件创建 (server or client) */
int reload_server_create(char *ip,int port)
{
int iSocketServer;
int iRet,ret;
struct sockaddr_in tSocketServerAddr;
struct sockaddr_in tSocketClientAddr;
int iAddrLen;
int iSocketClient;
int iClientNum = 0;
int epfd;
printf("服务器进程创建成功 %d, 行 %d\n",g_inet_status.pid,__LINE__);
//创建socket,监听事件
iSocketServer = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == iSocketServer)
{
printf("socket error!, 行 %d\n",__LINE__);
return -1;
}
//服务器开启端口复用 + 非阻塞
int opt = 1;
setsockopt(iSocketServer, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if(set_nonblocking(iSocketServer) < 0)
{
printf("设置服务器描述符iSocket (nonblocking) 失败, 行 %d\n",__LINE__);
close(iSocketServer);
return -1;
}
//绑定 端口 + IP
memset(&tSocketServerAddr, 0 ,sizeof(tSocketServerAddr));
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(port);
tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
memset(tSocketServerAddr.sin_zero, 0, 8);
iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (-1 == iRet)
{
printf("bind error!, 行 %d\n",__LINE__);
return -1;
}
printf("开始监听端口");
//创建监听队列
iRet = listen(iSocketServer, 16);
if (-1 == iRet)
{
printf("listen error!, 行 %d\n",__LINE__);
return -1;
}
printf("服务器创建成功IP[%s]:[%d], 行 %d\n",ip,port,__LINE__);
//创建epoll实例,将socket加入监听
epfd = epoll_create(1);
if(epfd < 0)
{
printf("epoll_create1 failed, 行 %d\n",__LINE__);
close(iSocketServer);
return -1;
}
struct epoll_event ev_server;
ev_server.events = EPOLLIN;
ev_server.data.fd = iSocketServer;
//添加server 可读事件检测
if(epoll_ctl(epfd, EPOLL_CTL_ADD, iSocketServer, &ev_server) < 0)
{
printf("添加服务器到epoll失败 , 行 %d\n",__LINE__);
close(iSocketServer);
close(epfd);
return -1;
}
else
printf("添加服务器到epoll成功 , 行 %d\n",__LINE__);
//添加serial 可读事件检测
printf("准备添加串口 fd=%d 到 epoll, 行 %d\n", g_uart_status.fd_uart,__LINE__);
ev_server.events = EPOLLIN; //| EPOLLET;
ev_server.data.fd = g_uart_status.fd_uart;
if(g_uart_status.fd_uart > 0)
{
if(epoll_ctl(epfd, EPOLL_CTL_ADD, g_uart_status.fd_uart, &ev_server) < 0)
{
printf("添加串口到epoll失败 , 行 %d\n",__LINE__);
close(iSocketServer);
close(epfd);
exit(1);
}
else
printf("添加串口到epoll成功 !, 行 %d\n",__LINE__);
}
//创建链表 (保存客户端信息)
list_head = llist_create(sizeof(struct info_client_status));
if(list_head == NULL)
{
printf("创建客户端成员链表失败 , 行 %d\n",__LINE__);
close(iSocketServer);
close(epfd);
exit(1);
}
//主事件循环
struct epoll_event events[MAX_EVENTS];
char recv_buf[1024];
char send_buf[1024];
int nfds;
while(1)
{
//阻塞等待事件
nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
if(nfds < 0)
{
if(errno == EINTR)
continue;
printf("epoll_wait err: %s, 行 %d\n",strerror(errno),__LINE__);
close(iSocketServer);
close(epfd);
exit(1);
}
//有事件到来,开始处理事件
for (int i = 0; i < nfds; i++)
{
int fd = events[i].data.fd;
printf("EVENT事件[%d]: fd=%d, revents=0x%x\n", i, events[i].data.fd, events[i].events);
// 1. 新客户端连接
if (fd == iSocketServer)
{
iAddrLen = sizeof(struct sockaddr);
iSocketClient = accept(iSocketServer, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
if (iSocketClient < 0) continue;
if (iClientNum >= CLIENT_INSERT)
{
close(iSocketClient);
continue;
}
set_nonblocking(iSocketClient);
ev_server.events = EPOLLIN | EPOLLRDHUP | EPOLLET;
ev_server.data.fd = iSocketClient;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, iSocketClient, &ev_server) == 0)
{
struct info_client_status info;
info.client_socket = iSocketClient;
info.link_status = LINK_CLIENT;
list_head->insert(list_head, &info, BACKWARD);
iClientNum++;
printf("NEW 客户端成员[%d] 链表位置[%d], 行 %d\n",iSocketClient,iClientNum,__LINE__);
}
else
{
printf("服务器已满,当前成员 :%d, 行 %d\n",CLIENT_INSERT,__LINE__);
close(iSocketClient);
}
continue;
}
// 2. 串口事件
if (fd == g_uart_status.fd_uart)
{
handle_serial_event(epfd, &g_uart_status.fd_uart, &ev_server, recv_buf, sizeof(recv_buf));
continue;
}
// 3. 客户端数据 / 断开
handle_client_event(epfd, fd, events[i].events, &iClientNum);
}
}
close(iSocketServer);
close(epfd);
return 0;
}
/* 配置为客户端 */
int reload_client_create(char *ip,int port)
{
int iSocketClient;
struct sockaddr_in tSocketServerAddr;
int ret;
unsigned char ucSendBuf[1000];
int iSendLen;
int iRecvLen;
char recv_buf[1024];
char send_buf[1024];
printf("客户端进程创建成功 %d, 行 %d\n",g_inet_status.pid,__LINE__);
iSocketClient = socket(AF_INET, SOCK_STREAM, 0);
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(port);
if (0 == inet_aton(ip, &tSocketServerAddr.sin_addr))
{
printf("invalid server_ip, 行 %d\n",__LINE__);
return -1;
}
memset(tSocketServerAddr.sin_zero, 0, 8);
while(1)
{
printf("Client: connecting to %s:%d, 行 %d\n", ip, port,__LINE__);
ret = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (ret == -1)
{
printf("connect error, 两秒从新连接一次\n");
sleep(2);
continue; // 需要循环重试,而不是直接返回
}
else
break;
}
printf("客户端连接成功 %s:%d, 行 %d\n", ip, port,__LINE__);
while(1)
{
struct pollfd poll_fd[2];
int nfds;
poll_fd[0].fd = g_uart_status.fd_uart;
poll_fd[0].events = POLLIN;
poll_fd[1].fd = iSocketClient;
poll_fd[1].events = POLLIN;
nfds = poll(poll_fd, 2, 1000);
if (nfds < 0)
{
if (errno == EINTR)
continue;
printf("poll err(Client), 行 %d\n",__LINE__);
return -1;
}
else if(nfds == 0)
continue;
//读串口 发服务器
if(poll_fd[0].events & POLLIN)
{
int n = read(g_uart_status.fd_uart,recv_buf,sizeof(recv_buf));
if(n <= 0)
printf("Client 串口读取失败, 行 %d\n",__LINE__);
else
{ //TCP防止程序崩溃
int size = send(iSocketClient, recv_buf, n, MSG_NOSIGNAL);
if(size < 0 && errno != EAGAIN)
{
printf("服务器退出 关闭客户端, 行 %d\n",__LINE__);
break;
}
}
}
//读服务器 发串口
if(poll_fd[1].events & POLLIN)
{
int n = recv(iSocketClient,send_buf,sizeof(send_buf),0);
printf("服务器写客户端: %s, 行 %d\n",send_buf,__LINE__);
if(n < 0 && errno != EAGAIN)
{
printf("服务器退出 关闭客户端, 行 %d\n",__LINE__);
break;
}
else
{ //TCP防止程序崩溃
int size = write(g_uart_status.fd_uart, send_buf, n);
printf("客户端写串口: %s, 行 %d\n",send_buf,__LINE__);
if(n < 0)
printf("Client 写串口失败, 行 %d\n",__LINE__);
}
}
}
close(iSocketClient);
return 0;
}
/* 5.关闭一个进程 (server or client)*/
void kill_child_fork(int pid)
{
kill(pid,SIGKILL);
waitpid(pid,NULL,0);
printf("进程被主动杀死 %d, 行 %d\n",pid,__LINE__);
}
int main()
{
watch_file_modify(WATCH_PATH);
return 0;
}
该程序实现了一个基于配置文件的串口转以太网服务(串口服务器),支持动态修改配置、TCP服务器/客户端模式切换。代码可划分为以下功能模块:
1. 守护进程模块(daemonize)
-
功能:将进程转为守护进程(后台运行)
-
实现:fork 后结束父进程,重定向标准输入/输出/错误到
/dev/null,调用setsid创建新会话,切换工作目录为根,重置 umask -
注意:当前代码中该函数被注释未使用,进程直接在前台运行
2. 配置文件监控模块(watch_file_modify)
-
功能:使用 inotify 监控 JSON 配置文件的修改事件(
IN_CLOSE_WRITE) -
流程:
-
等待文件创建(
stat轮询) -
初始化 inotify,添加监控描述符
-
循环读取事件,当文件被写入并关闭后,调用
parse_config_file重新解析
-
-
关键点:采用
while(1)永久运行,不断监听配置文件变化
3. 配置解析与热更新模块(parse_config_file)
-
功能:解析 JSON 配置文件,更新串口和网络配置,动态重启网络服务
-
子功能:
-
串口参数热更:读取
serial节点(device, baudrate, data_bits, parity, stop_bits)。若任一参数与全局g_uart_status不同,则关闭原串口、打开新串口、重新配置并更新全局状态。 -
网络模式切换:读取
work_mode(server/client)、对应 IP 和端口。根据模式保存到全局g_inet_status。 -
子进程管理:若网络参数变化(IP/端口/模式),杀死已有子进程(
kill_child_fork),再创建新子进程(create_fork)。首次启动时也创建子进程。
-
-
依赖:cJSON 库(需注意调用
cJSON_Delete,否则存在内存泄漏)
4. 子进程管理模块(create_fork / kill_child_fork)
-
create_fork(status):fork 出子进程,父进程返回,子进程根据g_inet_status.status调用reload_server_create或reload_client_create。 -
kill_child_fork(pid):向子进程发送SIGKILL,并调用waitpid回收。
5. TCP 服务器模块(reload_server_create)
-
功能:以 TCP 服务器模式运行,实现:串口数据 → 所有客户端广播,客户端数据 → 串口
-
实现技术:
-
创建非阻塞 TCP 监听 socket,绑定任意地址(
INADDR_ANY)和指定端口 -
使用 epoll(
EPOLLIN | EPOLLRDHUP | EPOLLET)同时监听:监听 socket、串口 fd、所有客户端 socket -
维护全局链表
list_head(类型LLIST,存储info_client_status,包含 client_socket 和 link_status) -
事件处理:
-
新连接:
accept后设置非阻塞,加入 epoll,插入链表,计数 -
串口可读:调用
handle_serial_event,循环读取串口数据,遍历链表向所有客户端转发(send),若发送失败则踢出客户端并删除链表节点 -
客户端可读/异常:调用
handle_client_event,接收数据并写入串口;若连接断开或出错则从 epoll 和链表中删除
-
-
-
容错:当串口读取错误(非
EAGAIN)时,调用handle_serial_check尝试重开串口(最多 3 次),若失败则退出进程
6. TCP 客户端模块(reload_client_create)
-
功能:以 TCP 客户端模式运行,连接远程服务器,实现双向数据转发
-
实现技术:
-
循环尝试连接服务器(成功则跳出)
-
使用
poll同时监听串口 fd 和 socket fd,超时 1000ms -
事件处理:
-
串口可读:读取串口数据,通过 socket 发送给服务器
-
socket 可读:接收服务器数据,写入串口
-
-
-
局限性:未实现断线重连机制(连接建立后若服务器断开,会退出循环并结束子进程,需由主进程根据配置重建)
7. 串口操作辅助模块
-
reopen_serial():根据全局g_uart_status重开串口并配置参数,返回新 fd -
handle_serial_check():在服务器模式下,发生串口异常时从 epoll 移除旧 fd,尝试重开(最多 3 次),成功则重新加入 epoll,失败返回 -1 -
set_nonblocking():将 fd 设置为非阻塞模式
8. 链表工具模块
-
client_id_cmp():供链表删除时使用,比较客户端 socket 值 -
链表库为外部
double_link_list.h,提供llist_create,insert,delete等操作 -
头文件
#ifndef DOUBLE_LINK_LIST_H__ #define DOUBLE_LINK_LIST_H__ #define FORWARD 1 //头插入 #define BACKWARD 2 //尾插入 typedef void (*llist_op)(const void *); typedef int (*llist_cmp)(const void *, const void *); //【性能】传任何类型,完成双向链表的机制 //链表节点 struct llist_node_st { struct llist_node_st *prev; struct llist_node_st *next; char data[1]; }; //头节点 typedef struct llist_head { int size; //单链表存储大小可由用户指定 struct llist_node_st head; int (*insert)(struct llist_head * , const void *, int ); void *(*find)(struct llist_head * , const void * , llist_cmp ); int (*delete)(struct llist_head * , const void * , llist_cmp ); int (*fetch)(struct llist_head * , const void * , llist_cmp , void *); void (*travel)(struct llist_head * ,llist_op ); void (*destroy)(struct llist_head * ); }LLIST; /* 1.创建链表()带头节点的双向循环链表 */ LLIST *llist_create(int initsize); /* 2.插入一个链表(头/尾) */ int llist_insert(LLIST *ptr, const void *data, int mode); /* 3.查找一个链表数据 */ void *llist_find(LLIST *ptr, const void *key, llist_cmp cmp); /* 4.删除一个链表 */ int llist_delete(LLIST *ptr, const void *key, llist_cmp cmp); /* 5.提取一个链表 */ int llist_fetch(LLIST *ptr, const void *key, llist_cmp cmp, void *data); /* 6.显示所有链表数据 */ void llist_travel(LLIST *ptr, llist_op); /* 7.释放所有链表 */ void llist_destroy(LLIST *ptr); #endif -
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include "double_link_list.h" //【性能】传任何类型,完成双向链表的机制 //公用查找函数 static struct llist_node_st *find_(LLIST *ptr, const void *key, llist_cmp cmp) { struct llist_node_st *cur; for(cur = ptr->head.next; cur != &ptr->head; cur = cur->next) { if(cmp(key,cur->data) == 0) break; } return cur; //没找到cur为头节点 } /* 1.创建链表()带头节点的双向循环链表 */ LLIST *llist_create(int initsize) { LLIST *new; new = malloc(sizeof(*new)); if(new == NULL) return NULL; new->size = initsize; new->head.prev = &new->head; new->head.next = &new->head; new->insert = llist_insert; new->find = llist_find; new->delete = llist_delete; new->fetch = llist_fetch; new->travel = llist_travel; new->destroy = llist_destroy; return new; } /* 2.插入一个链表 */ int llist_insert(LLIST *ptr, const void *data, int mode) { struct llist_node_st *newnode; if (ptr == NULL || data == NULL) { errno = EINVAL; return -1; } newnode = malloc(sizeof(*newnode) + ptr->size); if(newnode == NULL) { errno = ENOMEM; return -1; } else memcpy(newnode->data,data,ptr->size); if(mode == FORWARD) { newnode->prev = &ptr->head; newnode->next = ptr->head.next; } else if(mode == BACKWARD) { newnode->next = &ptr->head; newnode->prev = ptr->head.prev; } else { errno = EINVAL; return -1; } newnode->prev->next = newnode; newnode->next->prev = newnode; return 0; } /* 3.删除一个链表 */ int llist_delete(LLIST *ptr, const void *key, llist_cmp cmp) { struct llist_node_st *node; node = find_(ptr,key,cmp); if(node == &ptr->head) return -1; node->next->prev = node->prev; node->prev->next = node->next; free(node); return 0; } /* 4.查找数据 */ void *llist_find(LLIST *ptr, const void *key, llist_cmp cmp) { struct llist_node_st *node; node = find_(ptr,key,cmp); if(node == &ptr->head) return NULL; return node->data; } /* 5.拿出一个链表 */ int llist_fetch(LLIST *ptr, const void *key, llist_cmp cmp, void *data) { struct llist_node_st *node; node = find_(ptr,key,cmp); if(node == &ptr->head) return -1; node->next->prev = node->prev; node->prev->next = node->next; if(data != NULL) memcpy(data,node->data,ptr->size); free(node); return 0; } /* 6.推送一个链表 (推送给有读标志的成员)*/ void llist_travel(LLIST *ptr,llist_op operate) { struct llist_node_st *cur; for(cur = ptr->head.next; cur != &ptr->head; cur = cur->next) operate(cur->data); } /* 7.销毁*/ void llist_destroy(LLIST *ptr) { struct llist_node_st *cur,*next; for(cur = ptr->head.next; cur != &ptr->head; cur = next) { next = cur->next; free(cur); } free(ptr); }
9. 全局数据结构
-
struct server_or_client:保存网络模式、子进程 PID、IP 和端口(区分 server/client 两套变量) -
struct uart_status:保存串口 fd、设备名、波特率、数据位、校验位、停止位 -
struct info_client_status:保存客户端 socket 和连接状态(仅服务器模式使用) -
LLIST *list_head:服务器模式下的客户端链表
二、核心要点总结
-
多进程架构:主进程(监控配置) + 子进程(网络转发)。子进程崩溃或被杀死后,主进程根据最新配置可重新拉起,实现热更新。
-
热配置更新:
-
串口参数变化:关闭原串口,打开新串口(子进程中直接生效,无需重启进程)
-
网络参数变化:杀死子进程,主进程再创建新子进程(完全重启网络服务)
-
配置监控基于 inotify,响应文件修改事件
-
-
I/O 多路复用:
-
服务器模式采用 epoll(边缘触发)处理大量客户端连接和串口数据
-
客户端模式采用 poll(简单高效,仅两个 fd)
-
-
数据流向:
-
服务器模式:服务器读串口数据 → 广播到所有客户端;任一客户端 → 串口
-
客户端模式:客户端读串口数据 → 服务器;服务器 → 串口
-
-
容错处理:
-
客户端发送失败时自动剔除并关闭连接
-
串口异常时尝试重开(最多 3 次),失败则退出进程(由主进程重建)
-
客户端模式连接服务器时循环重试(直到成功)
-
-
非阻塞 I/O:所有 socket 和串口 fd 都设置为非阻塞,配合 epoll/poll 避免阻塞。
-
链式存储:服务器模式动态维护客户端列表,支持任意数量客户端(上限由
CLIENT_INSERT宏限制)。
三、整体流程
text
[主进程启动]
|
v
watch_file_modify()
|
+--> 等待配置文件创建
|
v
inotify_add_watch() 监听 IN_CLOSE_WRITE
|
+--------------------------+
| 配置文件修改事件 |
v v
parse_config_file() [继续监听]
|
+--> 解析串口参数 -> 变化则重开串口(重置 g_uart_status.fd_uart)
|
+--> 解析网络参数(mode/ip/port)
|
+--> 若配置与当前 g_inet_status 不同:
| (1) 杀死已有子进程 (kill_child_fork)
| (2) 创建新子进程 (create_fork)
|
+--> 子进程运行:
|
+-- [服务器模式] reload_server_create()
| |
| +--> 创建监听socket -> epoll循环
| - 新连接 -> 加入客户端列表
| - 串口可读 -> 广播数据
| - 客户端可读 -> 写入串口
|
+-- [客户端模式] reload_client_create()
|
+--> 连接服务器 -> poll循环
- 串口可读 -> 发送给服务器
- socket可读 -> 写入串口
关键路径说明:
-
主进程只负责配置监控和子进程生命周期管理,不处理实际数据转发。
-
当配置文件修改时,子进程可能被销毁并重新创建,实现网络服务的动态切换。
-
串口的热更只涉及子进程内部重新打开 fd,不重启进程。
四、潜在问题与改进建议
-
竞态条件:主进程修改全局
g_uart_status和g_inet_status时,子进程正在读取这些变量,未加锁保护。建议通过信号或管道传递配置变更,而非共享内存。 -
资源释放:服务器模式下,退出时未释放链表、关闭 epoll fd 和 socket;客户端模式下未关闭串口和 socket。
-
僵尸进程:
kill_child_fork中waitpid非阻塞?实际是阻塞的,但若子进程已退出可能出错,建议使用SIGCHLD信号处理。 -
硬编码限制:
CLIENT_INSERT未给出定义,缓冲区大小固定 1024 可能溢出。 -
守护进程:当前未真正 daemonize,日志输出到 stdout,生产环境建议启用。
-
断线重连:客户端模式在服务器断开后直接退出,应由主进程根据配置重新创建子进程(依赖配置监控触发),但若网络闪断未改配置则无法恢复。建议在客户端内部实现重连逻辑。
以上分析基于代码实际行为,对设计思想和实现细节进行了系统梳理。
依赖说明
-
cJSON 库:用于解析 JSON 配置文件,请从 cJSON 官方仓库 下载
cJSON.h和cJSON.c并一同编译。 -
编译命令中需要包含
-lpthread(cJSON 内部可能使用了线程局部存储,虽然本程序未直接多线程)。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)