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/O
    EPOLLHUP 挂起事件 对端挂起(如连接断开、设备被移除) 检测异常断开,清理资源
    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_createinsertdelete 等操作

  • 头文件

    #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:服务器模式下的客户端链表


二、核心要点总结

  1. 多进程架构:主进程(监控配置) + 子进程(网络转发)。子进程崩溃或被杀死后,主进程根据最新配置可重新拉起,实现热更新。

  2. 热配置更新

    • 串口参数变化:关闭原串口,打开新串口(子进程中直接生效,无需重启进程)

    • 网络参数变化:杀死子进程,主进程再创建新子进程(完全重启网络服务)

    • 配置监控基于 inotify,响应文件修改事件

  3. I/O 多路复用

    • 服务器模式采用 epoll(边缘触发)处理大量客户端连接和串口数据

    • 客户端模式采用 poll(简单高效,仅两个 fd)

  4. 数据流向

    • 服务器模式:服务器读串口数据 → 广播到所有客户端;任一客户端 → 串口

    • 客户端模式:客户端读串口数据 → 服务器;服务器 → 串口

  5. 容错处理

    • 客户端发送失败时自动剔除并关闭连接

    • 串口异常时尝试重开(最多 3 次),失败则退出进程(由主进程重建)

    • 客户端模式连接服务器时循环重试(直到成功)

  6. 非阻塞 I/O:所有 socket 和串口 fd 都设置为非阻塞,配合 epoll/poll 避免阻塞。

  7. 链式存储:服务器模式动态维护客户端列表,支持任意数量客户端(上限由 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,不重启进程。


四、潜在问题与改进建议

  1. 竞态条件:主进程修改全局 g_uart_status 和 g_inet_status 时,子进程正在读取这些变量,未加锁保护。建议通过信号或管道传递配置变更,而非共享内存。

  2. 资源释放:服务器模式下,退出时未释放链表、关闭 epoll fd 和 socket;客户端模式下未关闭串口和 socket。

  3. 僵尸进程kill_child_fork 中 waitpid 非阻塞?实际是阻塞的,但若子进程已退出可能出错,建议使用 SIGCHLD 信号处理。

  4. 硬编码限制CLIENT_INSERT 未给出定义,缓冲区大小固定 1024 可能溢出。

  5. 守护进程:当前未真正 daemonize,日志输出到 stdout,生产环境建议启用。

  6. 断线重连:客户端模式在服务器断开后直接退出,应由主进程根据配置重新创建子进程(依赖配置监控触发),但若网络闪断未改配置则无法恢复。建议在客户端内部实现重连逻辑。

以上分析基于代码实际行为,对设计思想和实现细节进行了系统梳理。


依赖说明

  • cJSON 库:用于解析 JSON 配置文件,请从 cJSON 官方仓库 下载 cJSON.h 和 cJSON.c 并一同编译。

  • 编译命令中需要包含 -lpthread(cJSON 内部可能使用了线程局部存储,虽然本程序未直接多线程)。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐