Files
STM32F4-Base/docs/BSD_Socket_API_使用指南.md
2026-07-19 00:59:56 +08:00

34 KiB
Raw Blame History

BSD Socket API 使用指南

目录

  1. 概述
  2. 架构说明
  3. 快速开始
  4. TCP 编程
  5. UDP 编程
  6. I/O 多路复用
  7. 事件回调机制
  8. 字节序处理
  9. API 参考
  10. 完整示例
  11. 常见问题

1. 概述

本项目基于 CH395F 以太网协议栈芯片,提供了一套兼容 Linux BSD Socket API 的网络编程接口。这套接口屏蔽了底层硬件细节,让嵌入式网络编程与标准 Linux 网络编程保持一致。

1.1 支持的功能

功能 说明
TCP Client 主动连接远程服务器
TCP Server单连接 支持 1 个客户端连接
TCP Server多连接 支持最多 7 个并发连接
UDP Client 发送 UDP 数据报
UDP Server 接收 UDP 数据报
select/poll I/O 多路复用
事件回调 异步事件通知

1.2 与 Linux Socket 的差异

特性 Linux Socket 本实现
Socket 数量 无限制 最多 8 个
TCP Server 多连接 无限制 最多 7 个并发连接
非阻塞模式 支持 通过 net_poll() 实现
错误码 errno net_get_errno()
头文件 <sys/socket.h> "net_socket.h"

2. 架构说明

┌─────────────────────────────────────────────────────────┐
│                    应用层 (App)                          │
├─────────────────────────────────────────────────────────┤
│              net_socket.h (BSD Socket API)               │  ← 用户接口
├─────────────────────────────────────────────────────────┤
│         net_socket.c + net_select.c (状态机+select)      │  ← 核心逻辑
├─────────────────────────────────────────────────────────┤
│              ch395f.c/h (底层驱动)                       │  ← 硬件驱动
├─────────────────────────────────────────────────────────┤
│                    CH395F 硬件                           │
└─────────────────────────────────────────────────────────┘

2.1 文件说明

文件 说明
net_config.h 配置宏定义(最大 Socket 数、超时时间等)
net_types.h 类型定义地址结构、Socket 控制块等)
net_socket.h BSD Socket API 头文件
net_socket.c API 核心实现
net_select.h select/poll API 头文件
net_select.c select/poll 实现

2.1a 底层驱动改进v2.1

ch395f.c/h 已完成以下优化:

改进项 说明
命令执行等待 open/close/listen/connect/disconnect 所有长命令内部调用 poll_cmd_status 等待完成20ms × 200 次 = 4s 超时)
全局中断 使用 GET_GLOB_INT_STATUS_ALL2字节版命令码 0x19支持全部 8 个 Socket
错误码 与芯片手册完全对齐的 17 个状态码定义
FUN_PARA 标志 CH395F_FUN_PARA_TCP_SERVER / SOCKET_CLOSE / DISABLE_SEND_OK
新增函数 ch395f_set_arp() / ch395f_set_ttl() / ch395f_get_unreach_info() / ch395f_poll_cmd_status()
KeepAlive 参数单位为毫秒(非秒),必须为 500 的倍数

2.2 Socket 状态机

                   net_socket()
                        │
                        ▼
    ┌──────────── CREATED ────────────┐
    │              │                  │
    │         net_bind()              │
    │              │                  │
    │              ▼                  │
    │          BOUND ─────────────────┤
    │         │       │              │
    │  net_listen()  net_connect()   │
    │         │       │              │
    │         ▼       ▼              │
    │    LISTENING  CONNECTING       │
    │         │       │              │
    │  net_accept()  │ (连接成功)    │
    │         │       │              │
    │         ▼       ▼              │
    │     TCP_ACCEPT  ESTABLISHED ◄──┤
    │         │       │              │
    │ (分配连接)│   net_send/recv()   │
    │         │       │              │
    │         ▼       ▼              │
    │      ESTABLISHED               │
    │         │                      │
    │    net_close()                 │
    │         │                      │
    │         ▼                      │
    └────── CLOSED ──────────────────┘

3. 快速开始

3.1 包含头文件

#include "net_socket.h"
#include "net_select.h"

3.2 初始化网络

int main(void)
{
    /* 初始化网络子系统
     * 参数IP地址, 子网掩码, 网关
     * 传入 NULL 使用 DHCP 自动获取
     */
    int ret = net_init("192.168.1.100", "255.255.255.0", "192.168.1.1");
    if (ret != 0) {
        printf("网络初始化失败!\r\n");
        return -1;
    }
    
    /* 其他初始化... */
    
    while (1)
    {
        net_poll();  /* 必须在主循环中调用 */
        /* 其他任务... */
    }
}

3.3 主循环要求

重要net_poll() 必须在主循环中定期调用,它负责:

  • 轮询 CH395F INT# 引脚电平GPIO 轮询,无需 EXTI 中断)
  • 通过 GET_GLOB_INT_STATUS_ALL2字节版读取所有 8 个 Socket 的中断状态
  • 更新所有 Socket 的连接状态CONNECT / DISCONNECT / RECV / TIMEOUT
  • 触发事件回调
  • 处理数据接收
while (1)
{
    net_poll();  /* 建议至少每 10ms 调用一次 */
    
    /* 其他应用代码... */
}

4. TCP 编程

4.1 TCP Server单连接

单连接模式下1 个 Socket 既做监听又做数据通信:

#include "net_socket.h"

void tcp_server_single(void)
{
    int listen_sock;
    struct net_sockaddr_in addr;
    struct net_sockaddr_in client_addr;
    int client_addr_len;
    char buf[256];
    int len;
    
    /* 1. 创建 Socket */
    listen_sock = net_socket(AF_INET, SOCK_STREAM, 0);
    if (listen_sock < 0) {
        printf("创建 Socket 失败\r\n");
        return;
    }
    
    /* 2. 绑定地址和端口 */
    addr.sin_family = AF_INET;
    addr.sin_port = net_htons(8080);  /* 端口号 8080 */
    addr.sin_addr.s_addr = net_inet_addr("0.0.0.0");  /* 监听所有网卡 */
    
    if (net_bind(listen_sock, (struct net_sockaddr *)&addr, sizeof(addr)) != 0) {
        printf("绑定失败\r\n");
        net_close(listen_sock);
        return;
    }
    
    /* 3. 开始监听(单连接模式 backlog=0 */
    if (net_listen(listen_sock, 0) != 0) {
        printf("监听失败\r\n");
        net_close(listen_sock);
        return;
    }
    
    printf("TCP Server 启动,监听端口 8080\r\n");
    
    /* 4. 主循环 */
    while (1)
    {
        net_poll();
        
        /* 尝试接受新连接 */
        client_addr_len = sizeof(client_addr);
        int client_sock = net_accept(listen_sock, 
            (struct net_sockaddr *)&client_addr, &client_addr_len);
        
        if (client_sock >= 0)
        {
            char ip_str[16];
            printf("新连接: %s:%d\r\n", 
                net_inet_ntoa(client_addr.sin_addr.s_addr, ip_str),
                net_ntohs(client_addr.sin_port));
            
            /* 接收和发送数据 */
            while (1)
            {
                len = net_recv(client_sock, buf, sizeof(buf) - 1, 0);
                if (len <= 0) {
                    printf("连接断开\r\n");
                    break;
                }
                
                buf[len] = '\0';
                printf("收到: %s\r\n", buf);
                
                /* 回显数据 */
                net_send(client_sock, buf, len, 0);
            }
            
            net_close(client_sock);
        }
    }
}

4.2 TCP Server多连接

多连接模式使用 1 个监听 Socket + N 个数据 Socket支持并发连接

#include "net_socket.h"
#include "net_select.h"

#define MAX_CLIENTS  4

void tcp_server_multi(void)
{
    int listen_sock;
    struct net_sockaddr_in addr;
    int i;
    
    /* 1. 创建监听 Socket */
    listen_sock = net_socket(AF_INET, SOCK_STREAM, 0);
    if (listen_sock < 0) {
        printf("创建 Socket 失败\r\n");
        return;
    }
    
    /* 2. 绑定 */
    addr.sin_family = AF_INET;
    addr.sin_port = net_htons(8080);
    addr.sin_addr.s_addr = net_inet_addr("0.0.0.0");
    
    if (net_bind(listen_sock, (struct net_sockaddr *)&addr, sizeof(addr)) != 0) {
        printf("绑定失败\r\n");
        return;
    }
    
    /* 3. 启动监听backlog 设为并发连接数) */
    if (net_listen(listen_sock, MAX_CLIENTS) != 0) {
        printf("监听失败\r\n");
        return;
    }
    
    printf("TCP Server 启动,支持 %d 个并发连接\r\n", MAX_CLIENTS);
    
    /* 4. 使用 select 等待事件 */
    while (1)
    {
        net_fd_set readfds;
        struct net_timeval timeout;
        
        NET_FD_ZERO(&readfds);
        
        /* 监听 Socket 加入读集合 */
        NET_FD_SET(listen_sock, &readfds);
        
        /* 数据 Socket1~4加入读集合 */
        for (i = 1; i <= MAX_CLIENTS; i++) {
            NET_FD_SET(i, &readfds);
        }
        
        timeout.tv_sec = 0;
        timeout.tv_usec = 100000;  /* 100ms 超时 */
        
        int ret = net_select(listen_sock + MAX_CLIENTS + 1, 
            &readfds, NULL, NULL, &timeout);
        
        if (ret > 0)
        {
            /* 检查数据 Socket */
            for (i = 1; i <= MAX_CLIENTS; i++)
            {
                if (NET_FD_ISSET(i, &readfds))
                {
                    net_sock_t *sock = net_get_sock(i);
                    if (sock != NULL && sock->state == NET_SOCK_STATE_ESTABLISHED)
                    {
                        char buf[256];
                        int len = net_recv(i, buf, sizeof(buf) - 1, 0);
                        if (len > 0) {
                            buf[len] = '\0';
                            printf("Socket %d: %s\r\n", i, buf);
                            net_send(i, buf, len, 0);  /* 回显 */
                        } else {
                            printf("Socket %d 断开\r\n", i);
                        }
                    }
                }
            }
        }
    }
}

4.3 TCP Client

#include "net_socket.h"

void tcp_client(void)
{
    int sock;
    struct net_sockaddr_in addr;
    char buf[256];
    int len;
    
    /* 1. 创建 Socket */
    sock = net_socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        printf("创建 Socket 失败\r\n");
        return;
    }
    
    /* 2. 设置服务器地址 */
    addr.sin_family = AF_INET;
    addr.sin_port = net_htons(8080);
    addr.sin_addr.s_addr = net_inet_addr("192.168.1.200");
    
    /* 3. 连接服务器 */
    printf("正在连接服务器...\r\n");
    if (net_connect(sock, (struct net_sockaddr *)&addr, sizeof(addr)) != 0) {
        printf("连接失败\r\n");
        net_close(sock);
        return;
    }
    
    /* 4. 等待连接建立 */
    while (1)
    {
        net_poll();
        
        net_sock_t *sock_info = net_get_sock(sock);
        if (sock_info != NULL && sock_info->state == NET_SOCK_STATE_ESTABLISHED) {
            printf("连接成功!\r\n");
            break;
        }
        
        HAL_Delay(10);
    }
    
    /* 5. 发送数据 */
    const char *msg = "Hello, CH395F!";
    net_send(sock, msg, strlen(msg), 0);
    printf("发送: %s\r\n", msg);
    
    /* 6. 接收数据 */
    len = net_recv(sock, buf, sizeof(buf) - 1, 0);
    if (len > 0) {
        buf[len] = '\0';
        printf("收到: %s\r\n", buf);
    }
    
    /* 7. 关闭连接 */
    net_close(sock);
}

5. UDP 编程

5.1 UDP Client

#include "net_socket.h"

void udp_client(void)
{
    int sock;
    struct net_sockaddr_in local_addr;
    struct net_sockaddr_in dest_addr;
    char buf[256];
    int len;
    
    /* 1. 创建 UDP Socket */
    sock = net_socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0) {
        printf("创建 Socket 失败\r\n");
        return;
    }
    
    /* 2. 绑定本地端口(可选) */
    local_addr.sin_family = AF_INET;
    local_addr.sin_port = net_htons(12345);
    local_addr.sin_addr.s_addr = net_inet_addr("0.0.0.0");
    
    net_bind(sock, (struct net_sockaddr *)&local_addr, sizeof(local_addr));
    
    /* 3. 设置目标地址 */
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_port = net_htons(8888);
    dest_addr.sin_addr.s_addr = net_inet_addr("192.168.1.200");
    
    /* 4. 发送数据 */
    const char *msg = "UDP Hello!";
    net_sendto(sock, msg, strlen(msg), 0, 
        (struct net_sockaddr *)&dest_addr, sizeof(dest_addr));
    printf("发送: %s\r\n", msg);
    
    /* 5. 接收数据 */
    struct net_sockaddr_in src_addr;
    int src_addr_len = sizeof(src_addr);
    
    len = net_recvfrom(sock, buf, sizeof(buf) - 1, 0,
        (struct net_sockaddr *)&src_addr, &src_addr_len);
    
    if (len > 0) {
        buf[len] = '\0';
        char ip_str[16];
        printf("收到: %s (from %s:%d)\r\n", buf,
            net_inet_ntoa(src_addr.sin_addr.s_addr, ip_str),
            net_ntohs(src_addr.sin_port));
    }
    
    net_close(sock);
}

5.2 UDP Server

#include "net_socket.h"

void udp_server(void)
{
    int sock;
    struct net_sockaddr_in addr;
    char buf[256];
    
    /* 1. 创建 UDP Socket */
    sock = net_socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0) {
        printf("创建 Socket 失败\r\n");
        return;
    }
    
    /* 2. 绑定本地端口 */
    addr.sin_family = AF_INET;
    addr.sin_port = net_htons(8888);
    addr.sin_addr.s_addr = net_inet_addr("0.0.0.0");
    
    if (net_bind(sock, (struct net_sockaddr *)&addr, sizeof(addr)) != 0) {
        printf("绑定失败\r\n");
        net_close(sock);
        return;
    }
    
    printf("UDP Server 启动,监听端口 8888\r\n");
    
    /* 3. 循环接收数据 */
    while (1)
    {
        net_poll();
        
        struct net_sockaddr_in src_addr;
        int src_addr_len = sizeof(src_addr);
        
        int len = net_recvfrom(sock, buf, sizeof(buf) - 1, 0,
            (struct net_sockaddr *)&src_addr, &src_addr_len);
        
        if (len > 0) {
            buf[len] = '\0';
            char ip_str[16];
            printf("收到: %s (from %s:%d)\r\n", buf,
                net_inet_ntoa(src_addr.sin_addr.s_addr, ip_str),
                net_ntohs(src_addr.sin_port));
            
            /* 回显数据 */
            net_sendto(sock, buf, len, 0,
                (struct net_sockaddr *)&src_addr, src_addr_len);
        }
    }
}

6. I/O 多路复用

6.1 select 使用

net_select() 可以同时监控多个 Socket 的读写事件:

#include "net_select.h"

void select_example(void)
{
    int sock1, sock2;
    net_fd_set readfds;
    struct net_timeval timeout;
    
    /* 创建并连接两个 Socket ... */
    
    while (1)
    {
        NET_FD_ZERO(&readfds);
        NET_FD_SET(sock1, &readfds);
        NET_FD_SET(sock2, &readfds);
        
        timeout.tv_sec = 0;
        timeout.tv_usec = 500000;  /* 500ms 超时 */
        
        int ret = net_select(sock2 + 1, &readfds, NULL, NULL, &timeout);
        
        if (ret > 0) {
            if (NET_FD_ISSET(sock1, &readfds)) {
                char buf[128];
                int len = net_recv(sock1, buf, sizeof(buf), 0);
                /* 处理数据... */
            }
            
            if (NET_FD_ISSET(sock2, &readfds)) {
                char buf[128];
                int len = net_recv(sock2, buf, sizeof(buf), 0);
                /* 处理数据... */
            }
        } else if (ret == 0) {
            /* 超时 */
        }
    }
}

6.2 poll 使用

net_poll_events() 使用 pollfd 结构体数组:

#include "net_select.h"

void poll_example(void)
{
    net_pollfd fds[2];
    
    fds[0].fd = sock1;
    fds[0].events = NET_POLLIN;
    
    fds[1].fd = sock2;
    fds[1].events = NET_POLLIN;
    
    while (1)
    {
        int ret = net_poll_events(fds, 2, 500);  /* 500ms 超时 */
        
        if (ret > 0) {
            if (fds[0].revents & NET_POLLIN) {
                char buf[128];
                int len = net_recv(fds[0].fd, buf, sizeof(buf), 0);
                /* 处理数据... */
            }
            
            if (fds[1].revents & NET_POLLIN) {
                char buf[128];
                int len = net_recv(fds[1].fd, buf, sizeof(buf), 0);
                /* 处理数据... */
            }
        }
    }
}

6.3 事件掩码说明

事件 说明
NET_POLLIN 有数据可读,或有新连接到达(监听 Socket
NET_POLLOUT 发送缓冲区有空间,可以写入数据
NET_POLLERR 发生错误
NET_POLLHUP 连接挂起(对端关闭)
NET_POLLNVAL 无效的文件描述符

7. 事件回调机制

可以通过注册回调函数,异步获取 Socket 事件通知:

7.1 定义回调函数

#include "net_socket.h"

void my_event_callback(int sockfd, net_event_t event, void *arg)
{
    switch (event)
    {
        case NET_EVENT_CONNECTED:
            printf("Socket %d: 连接建立\r\n", sockfd);
            break;
            
        case NET_EVENT_DISCONNECTED:
            printf("Socket %d: 连接断开\r\n", sockfd);
            break;
            
        case NET_EVENT_DATA_RECEIVED:
            printf("Socket %d: 收到数据\r\n", sockfd);
            /* 注意:这里只是通知,需要调用 net_recv() 读取 */
            break;
            
        case NET_EVENT_SEND_COMPLETE:
            printf("Socket %d: 发送完成\r\n", sockfd);
            break;
            
        case NET_EVENT_TIMEOUT:
            printf("Socket %d: 操作超时\r\n", sockfd);
            break;
            
        case NET_EVENT_ERROR:
            printf("Socket %d: 发生错误\r\n", sockfd);
            break;
    }
}

7.2 注册回调

void event_callback_example(void)
{
    int sock;
    
    /* 创建 Socket ... */
    
    /* 注册事件回调 */
    net_set_event_cb(sock, my_event_callback, NULL);
    
    /* 主循环 */
    while (1)
    {
        net_poll();
    }
}

8. 字节序处理

网络协议使用大端序(网络字节序),而 STM32 使用小端序(主机字节序)。发送和接收数据时需要进行字节序转换。

8.1 端口号转换

uint16_t port = 8080;

/* 主机序 -> 网络序(发送前转换) */
uint16_t net_port = net_htons(port);

/* 网络序 -> 主机序(接收后转换) */
uint16_t host_port = net_ntohs(net_port);

8.2 IP 地址转换

/* 字符串转网络序 IP */
uint32_t ip = net_inet_addr("192.168.1.100");

/* 网络序 IP 转字符串 */
char ip_str[16];
net_inet_ntoa(ip, ip_str);  /* ip_str = "192.168.1.100" */

8.3 填充地址结构体

struct net_sockaddr_in addr;

addr.sin_family = AF_INET;
addr.sin_port = net_htons(8080);                    /* 端口转换 */
addr.sin_addr.s_addr = net_inet_addr("192.168.1.100");  /* IP 转换 */

/* 如果要绑定任意地址 */
addr.sin_addr.s_addr = net_inet_addr("0.0.0.0");
/* 或者直接 */
addr.sin_addr.s_addr = INADDR_ANY;

8.4 读取地址信息

struct net_sockaddr_in client_addr;
int addr_len = sizeof(client_addr);

int client_sock = net_accept(listen_sock, 
    (struct net_sockaddr *)&client_addr, &addr_len);

if (client_sock >= 0)
{
    /* 转换端口到主机序 */
    uint16_t port = net_ntohs(client_addr.sin_port);
    
    /* 转换 IP 到字符串 */
    char ip_str[16];
    net_inet_ntoa(client_addr.sin_addr.s_addr, ip_str);
    
    printf("客户端: %s:%d\r\n", ip_str, port);
}

9. API 参考

9.1 核心 API

net_init - 网络初始化

int net_init(const char *ip, const char *mask, const char *gateway);
参数 说明
ip IP 地址字符串NULL 使用 DHCP
mask 子网掩码字符串NULL 使用默认
gateway 网关地址字符串NULL 使用默认
返回值 0 成功,-1 失败

net_poll - 状态轮询

int net_poll(void);
参数 说明
返回值 有事件发生的 socket 数量

注意:必须在主循环中定期调用。

net_socket - 创建 Socket

int net_socket(int domain, int type, int protocol);
参数 说明
domain 地址族,仅支持 AF_INET
type SOCK_STREAM(TCP) 或 SOCK_DGRAM(UDP)
protocol 通常为 0
返回值 socket 描述符 (0~7),失败返回 -1

net_bind - 绑定地址

int net_bind(int sockfd, const struct net_sockaddr *addr, int addrlen);
参数 说明
sockfd socket 描述符
addr 地址结构体指针
addrlen 地址结构体长度
返回值 0 成功,-1 失败

net_listen - 开始监听

int net_listen(int sockfd, int backlog);
参数 说明
sockfd socket 描述符
backlog 最大并发连接数0=单连接)
返回值 0 成功,-1 失败

net_accept - 接受连接

int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen);
参数 说明
sockfd 监听 socket 描述符
addr 输出:客户端地址
addrlen 输入输出:地址长度
返回值 新 socket 描述符,失败返回 -1

net_connect - 连接服务器

int net_connect(int sockfd, const struct net_sockaddr *addr, int addrlen);
参数 说明
sockfd socket 描述符
addr 服务器地址
addrlen 地址长度
返回值 0 成功,-1 失败

net_send - 发送数据

int net_send(int sockfd, const void *buf, int len, int flags);
参数 说明
sockfd socket 描述符
buf 数据缓冲区
len 数据长度
flags 通常为 0
返回值 实际发送字节数,失败返回 -1

net_recv - 接收数据

int net_recv(int sockfd, void *buf, int len, int flags);
参数 说明
sockfd socket 描述符
buf 接收缓冲区
len 缓冲区大小
flags 0=阻塞,NET_MSG_DONTWAIT=非阻塞
返回值 实际接收字节数0=对端关闭,-1=错误

net_sendto - 发送 UDP 数据

int net_sendto(int sockfd, const void *buf, int len, int flags,
               const struct net_sockaddr *dest_addr, int addrlen);

net_recvfrom - 接收 UDP 数据

int net_recvfrom(int sockfd, void *buf, int len, int flags,
                 struct net_sockaddr *src_addr, int *addrlen);

net_close - 关闭 Socket

int net_close(int sockfd);

9.2 辅助 API

net_set_event_cb - 注册事件回调

int net_set_event_cb(int sockfd, net_event_cb_t cb, void *arg);

net_get_sock - 获取 Socket 控制块

net_sock_t *net_get_sock(int sockfd);

net_get_errno - 获取错误码

int net_get_errno(void);

9.3 字节序转换

uint16_t net_htons(uint16_t hostshort);  /* 主机序 -> 网络序 */
uint16_t net_ntohs(uint16_t netshort);  /* 网络序 -> 主机序 */
uint32_t net_htonl(uint32_t hostlong);  /* 主机序 -> 网络序 */
uint32_t net_ntohl(uint32_t netlong);   /* 网络序 -> 主机序 */
uint32_t net_inet_addr(const char *cp); /* 字符串 -> 网络序 IP */
char *net_inet_ntoa(uint32_t addr, char *buf); /* 网络序 IP -> 字符串 */

9.4 select/poll

int net_select(int nfds, net_fd_set *readfds, net_fd_set *writefds,
               net_fd_set *exceptfds, net_timeval *timeout);

int net_poll_events(net_pollfd *fds, int nfds, int timeout);

9.5 fd_set 操作宏

NET_FD_ZERO(fdset)           /* 清空集合 */
NET_FD_SET(fd, fdset)        /* 添加 fd */
NET_FD_CLR(fd, fdset)        /* 移除 fd */
NET_FD_ISSET(fd, fdset)      /* 检查 fd 是否在集合中 */

10. 完整示例

10.1 TCP Echo Server多连接

#include "net_socket.h"
#include "net_select.h"
#include <stdio.h>
#include <string.h>

#define SERVER_PORT     8080
#define MAX_CLIENTS     4

void tcp_echo_server(void)
{
    int listen_sock;
    struct net_sockaddr_in addr;
    int i;
    
    /* 初始化网络 */
    net_init("192.168.1.100", "255.255.255.0", "192.168.1.1");
    
    /* 创建监听 Socket */
    listen_sock = net_socket(AF_INET, SOCK_STREAM, 0);
    if (listen_sock < 0) {
        printf("创建 Socket 失败\r\n");
        while(1);
    }
    
    /* 绑定 */
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = net_htons(SERVER_PORT);
    addr.sin_addr.s_addr = net_inet_addr("0.0.0.0");
    
    if (net_bind(listen_sock, (struct net_sockaddr *)&addr, sizeof(addr)) != 0) {
        printf("绑定失败\r\n");
        while(1);
    }
    
    /* 监听 */
    if (net_listen(listen_sock, MAX_CLIENTS) != 0) {
        printf("监听失败\r\n");
        while(1);
    }
    
    printf("TCP Echo Server 启动,端口 %d\r\n", SERVER_PORT);
    
    /* 主循环 */
    while (1)
    {
        net_fd_set readfds;
        struct net_timeval timeout;
        
        NET_FD_ZERO(&readfds);
        NET_FD_SET(listen_sock, &readfds);
        
        for (i = 1; i <= MAX_CLIENTS; i++) {
            NET_FD_SET(i, &readfds);
        }
        
        timeout.tv_sec = 0;
        timeout.tv_usec = 100000;
        
        int ret = net_select(listen_sock + MAX_CLIENTS + 1,
            &readfds, NULL, NULL, &timeout);
        
        if (ret > 0)
        {
            for (i = 1; i <= MAX_CLIENTS; i++)
            {
                if (NET_FD_ISSET(i, &readfds))
                {
                    net_sock_t *sock = net_get_sock(i);
                    
                    if (sock == NULL) continue;
                    
                    if (sock->state == NET_SOCK_STATE_ESTABLISHED)
                    {
                        char buf[256];
                        int len = net_recv(i, buf, sizeof(buf) - 1, 0);
                        
                        if (len > 0) {
                            buf[len] = '\0';
                            printf("客户端 %d: %s\r\n", i, buf);
                            net_send(i, buf, len, 0);
                        } else {
                            printf("客户端 %d 断开\r\n", i);
                            net_close(i);
                        }
                    }
                }
            }
        }
    }
}

10.2 TCP Client 自动重连

#include "net_socket.h"
#include <stdio.h>
#include <string.h>

#define SERVER_IP   "192.168.1.200"
#define SERVER_PORT 8080

void tcp_client_reconnect(void)
{
    int sock = -1;
    struct net_sockaddr_in addr;
    uint32_t last_try = 0;
    uint32_t reconnect_interval = 5000;  /* 5 秒重连间隔 */
    
    /* 初始化网络 */
    net_init("192.168.1.100", "255.255.255.0", "192.168.1.1");
    
    while (1)
    {
        net_poll();
        
        /* 尝试重连 */
        if (sock < 0 && (HAL_GetTick() - last_try) >= reconnect_interval)
        {
            last_try = HAL_GetTick();
            
            sock = net_socket(AF_INET, SOCK_STREAM, 0);
            if (sock >= 0) {
                memset(&addr, 0, sizeof(addr));
                addr.sin_family = AF_INET;
                addr.sin_port = net_htons(SERVER_PORT);
                addr.sin_addr.s_addr = net_inet_addr(SERVER_IP);
                
                if (net_connect(sock, (struct net_sockaddr *)&addr, sizeof(addr)) == 0) {
                    printf("正在连接服务器...\r\n");
                } else {
                    net_close(sock);
                    sock = -1;
                }
            }
        }
        
        /* 检查连接状态 */
        if (sock >= 0)
        {
            net_sock_t *sock_info = net_get_sock(sock);
            
            if (sock_info != NULL && sock_info->state == NET_SOCK_STATE_ESTABLISHED)
            {
                /* 已连接,发送数据 */
                static uint32_t last_send = 0;
                if ((HAL_GetTick() - last_send) >= 1000) {
                    last_send = HAL_GetTick();
                    net_send(sock, "heartbeat", 9, 0);
                }
                
                /* 检查接收 */
                char buf[128];
                int len = net_recv(sock, buf, sizeof(buf) - 1, NET_MSG_DONTWAIT);
                if (len > 0) {
                    buf[len] = '\0';
                    printf("收到: %s\r\n", buf);
                } else if (len == 0) {
                    /* 连接断开 */
                    printf("连接断开\r\n");
                    net_close(sock);
                    sock = -1;
                }
            }
            else if (sock_info != NULL && 
                     (sock_info->state == NET_SOCK_STATE_TCP_ACCEPT ||
                      sock_info->state == NET_SOCK_STATE_CLOSED))
            {
                /* 连接失败或断开 */
                printf("连接失败,准备重连...\r\n");
                net_close(sock);
                sock = -1;
            }
        }
    }
}

11. 常见问题

11.1 net_poll() 必须调用吗?

是的net_poll() 负责轮询 CH395F 的中断状态并更新所有 Socket 的状态。如果不调用,连接状态不会更新,数据也无法接收。

建议至少每 10ms 调用一次。

11.2 Socket 数量有限制吗?

是的CH395F 最多支持 8 个 Socket索引 0~7。其中

  • TCP Server 多连接模式1 个监听 Socket + 最多 7 个数据 Socket
  • TCP Client最多 8 个
  • UDP最多 8 个

11.3 TCP Server 多连接模式有什么要求?

模式工作原理:

  • CH395F 多连接模式下Socket 0 专职监听Socket 1~7 由芯片自动分配
  • 客户端连接时CH395F 自动查找协议类型为 TCP、源端口与监听端口相同、处于关闭状态的 Socket
  • 找到后自动打开该 Socket 并分配连接,触发 CONNECT 中断

硬件要求:

  • CH395F 固件版本 >= 0x44支持 Socket 4~7

11.4 发送数据有什么限制?

  • 每次发送的数据长度不能超过 NET_SEND_BUF_SIZE(默认 4096 字节)
  • TCP 需要等待发送缓冲区空闲(send_ready 标志)
  • UDP 单次发送最大 1460 字节

11.5 如何处理连接断开?

方式一:阻塞接收

int len = net_recv(sock, buf, sizeof(buf), 0);
if (len <= 0) {
    /* 连接断开或错误 */
    net_close(sock);
}

方式二:非阻塞接收

int len = net_recv(sock, buf, sizeof(buf), NET_MSG_DONTWAIT);
if (len < 0 && net_get_errno() == NET_ERR_WOULDBLOCK) {
    /* 没有数据 */
} else if (len <= 0) {
    /* 连接断开 */
}

方式三:事件回调

void callback(int sockfd, net_event_t event, void *arg)
{
    if (event == NET_EVENT_DISCONNECTED) {
        /* 连接断开 */
    }
}

11.6 select 和 poll 的区别?

特性 select poll
描述符集合 fd_set位掩码 pollfd 数组
最大 fd 数 32 无限制
使用方式 NET_FD_SET 宏 直接操作结构体

两者功能相同,推荐使用 net_select(),更接近 Linux 编程习惯。

11.7 错误码说明

错误码 宏定义 说明
0 NET_OK 成功
-1 NET_ERR 通用错误
-2 NET_ERR_BADF 无效的 socket 描述符
-3 NET_ERR_INVAL 无效参数
-4 NET_ERR_NOMEM 内存不足
-5 NET_ERR_NOTCONN 未连接
-6 NET_ERR_ISCONN 已连接
-7 NET_ERR_WOULDBLOCK 非阻塞模式下操作未完成
-8 NET_ERR_CONNRESET 连接被重置
-9 NET_ERR_TIMEDOUT 操作超时
-10 NET_ERR_NOSPACE 发送缓冲区无空间
-11 NET_ERR_BUSY 操作未完成BUSY

11.8 CH395F 命令执行状态码

CH395F 底层驱动返回的状态码(ch395f_get_cmd_status()ch395f_poll_cmd_status()

代码 宏定义 说明
0x00 CH395F_ERR_SUCCESS 成功
0x10 CH395F_ERR_BUSY 忙,命令正在执行
0x11 CH395F_ERR_MEM 内存管理错误
0x12 CH395F_ERR_BUF 缓冲区错误
0x13 CH395F_ERR_TIMEOUT 超时
0x14 CH395F_ERR_RTE 路由错误
0x15 CH395F_ERR_ABRT 连接中止
0x16 CH395F_ERR_RST 连接复位
0x17 CH395F_ERR_CLSD 连接关闭
0x18 CH395F_ERR_CONN 无连接
0x19 CH395F_ERR_VAL 值错误
0x1A CH395F_ERR_ARG 参数错误
0x1B CH395F_ERR_USE 已被使用(常见于重复 OPEN 或冲突配置)
0x1C CH395F_ERR_IF MAC 错误
0x1D CH395F_ERR_ISCONN 已连接
0x20 CH395F_ERR_OPEN 已打开
0x5F CH395F_CMD_RET_ABORT 命令中止
0xFA CH395F_ERR_UNKNOW 未知错误

11.9 故障排查

问题TCP Client 连接超时,无 CONNECT 中断

  • 检查多连接模式下数据 Socket1~7是否设置了 SET_PROTO_TCP + SET_SOUR_PORT(与监听端口相同)
  • 如果不设置CH395F 不知道哪些 Socket 可用于自动分配TCP SYN 被静默丢弃

问题:数据 Socket OPEN 返回 0x1B已被使用

  • CH395F 多连接模式下,数据 Socket 由芯片自动管理,软件不应手动 OPEN_SOCKET
  • 只需 SET_PROTO_TCP + SET_SOUR_PORTCH395F 在有连接时自动打开

问题Socket 4~7 的 CONNECT/RECV 中断不触发

  • 需使用 GET_GLOB_INT_STATUS_ALL(命令码 0x19返回 2 字节)才能检测 Socket 4~7 的中断
  • 1 字节版 GET_GLOB_INT_STATUS(命令码 0x29仅支持 Socket 0~3

问题CH395F 初始化超时

  • 芯片 INIT 需要约 200ms轮询间隔应为 20ms过短的查询可能干扰内部处理
  • 超时值建议设为 4 秒以上