# BSD Socket API 使用指南

## 目录

1. [概述](#1-概述)
2. [架构说明](#2-架构说明)
3. [线程安全设计](#3-线程安全设计)
4. [快速开始](#4-快速开始)
5. [TCP 编程](#5-tcp-编程)
6. [UDP 编程](#6-udp-编程)
7. [I/O 多路复用](#7-io-多路复用)
8. [事件回调机制](#8-事件回调机制)
9. [字节序处理](#9-字节序处理)
10. [API 参考](#10-api-参考)
11. [完整示例](#11-完整示例)
12. [常见问题](#12-常见问题)

---

## 1. 概述

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

### 1.1 支持的功能

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

### 1.2 与 Linux Socket 的差异

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

---

## 2. 架构说明

### 2.1 分层架构

```
┌──────────────────────────────────────────────────────────────┐
│                    应用层任务 (多个 FreeRTOS Task)            │
│  net_send() / net_recv() / net_close()  (线程安全封装)      │
├──────────────────────────────────────────────────────────────┤
│                    消息队列 (osMessageQueue)                  │
│          由 CubeMX 生成，在 MX_FREERTOS_Init() 中创建        │
├──────────────────────────────────────────────────────────────┤
│  netTask (网络任务线程)                                      │
│  net_process_messages() + net_poll() — 串行化执行            │
│  net_send_sock() / net_recv_sock() / net_close_sock()       │
├──────────────────────────────────────────────────────────────┤
│              net_socket.c (BSD Socket API 核心)               │
│              Socket 状态机 + CH395F 命令封装                  │
├──────────────────────────────────────────────────────────────┤
│              ch395f.c/h (底层 SPI 驱动)                      │
│              所有 SPI 事务必须由 netTask 发起                 │
├──────────────────────────────────────────────────────────────┤
│                    CH395F 硬件 (SPI2)                         │
└──────────────────────────────────────────────────────────────┘
```

### 2.2 文件说明

| 文件 | 说明 |
|------|------|
| `net_config.h` | 配置宏定义（最大 Socket 数、超时时间等） |
| `net_types.h` | 类型定义（地址结构、Socket 控制块等） |
| `net_socket.h` | BSD Socket API 头文件，含 `net_msg_t` 消息类型定义 |
| `net_socket.c` | API 核心实现，含消息队列处理、线程安全封装、内部 `_sock()` 版本 |
| `net_select.h` | select/poll API 头文件 |
| `net_select.c` | select/poll 实现 |

### 2.4 Socket 状态机

```
                   net_socket()
                        │
                        ▼
    ┌──────────── CREATED ────────────┐
    │              │                  │
    │         net_bind()              │
    │              │                  │
    │              ▼                  │
    │          BOUND ─────────────────┤
    │         │       │              │
    │  net_listen()  net_connect()   │
    │         │       │              │
    │         ▼       ▼              │
    │    LISTENING  CONNECTING       │
    │         │       │              │
    │  net_accept()  │ (连接成功)    │
    │         │       │              │
    │         ▼       ▼              │
    │     ESTABLISHED ◄──────────────┘
    │         │                      │
    │  net_accept() 返回监听 Socket 自身 │
    │         │                      │
    │  net_send/recv()               │
    │         │                      │
    │    net_close()                 │
    │         │                      │
    │         ▼                      │
    └────── CLOSED ──────────────────┘
```

---

## 3. 线程安全设计

### 3.1 设计背景

NET 层（`net_socket.c` + `ch395f.c`）不是线程安全的：
- Socket 控制块（`s_net_socks[]`）无互斥保护
- CH395F SPI 事务不重入（STM32 HAL SPI 无内部锁）
- 状态机在多个任务中并发访问会崩溃

引入消息队列将所有的 CH395F 操作串行化到 `netTask` 中执行。

### 3.2 消息队列模式

```
  App Task A          App Task B           netTask
      |                    |                  |
 net_send()           net_recv()        net_process_messages()
      |                    |              + net_poll()
      ▼                    ▼                  │
 ┌─────────┐         ┌─────────┐              │
 │send msg │         │recv msg │              │
 └────┬────┘         └────┬────┘              │
      │                   │                   │
      └─────────┬─────────┘                   │
                ▼                             ▼
        ┌──────────────────┐           ┌──────────────┐
        │  消息队列          │──────────│  取出消息     │
        │ osMessageQueue    │  dequeue  │  执行操作     │
        └──────────────────┘           │  写结果       │
                ▲                       │  通知调用方   │
                │                       └──────┬───────┘
                │  ulTaskNotifyTake()           │
                │  (阻塞等待结果)               │ xTaskNotifyGive()
                │                              │
          ┌─────┴──────┐                       │
          │ 返回结果给  │◄──────────────────────┘
          │ 调用方     │
          └────────────┘
```

### 3.3 关键规则

| 规则 | 说明 |
|------|------|
| **`netTask` 外调用** | 使用 `net_send()` / `net_recv()` / `net_close()` 公共 API |
| **`netTask` 内调用** | 使用 `net_send_sock()` / `net_recv_sock()` / `net_close_sock()` 内部 API |
| **创建/绑定/监听** | 在 `main()` 初始化阶段完成（`osKernelStart()` 之前），无需队列 |
| **`net_poll()` 和 `net_process_messages()`** | 必须在 `netTask` 主循环中调用 |

### 3.4 内部 API 说明

内部 API（带 `_sock` 后缀）直接操作 Socket 控制块和 CH395F，不经过消息队列。用于 `netTask` 中的网络业务逻辑，避免向自身发送消息造成死锁：

```c
/* 在 netTask 中直接操作 Socket，绕过消息队列 */
int net_send_sock(net_sock_t *p_sock, const void *buf, int len);
int net_recv_sock(net_sock_t *p_sock, void *buf, int len, int flags);
int net_close_sock(net_sock_t *p_sock);
```

### 3.5 注意事项

- **消息队列由 CubeMX 创建**：在 `MX_FREERTOS_Init()`（`osKernelInitialize()` 之后）中调用 `osMessageQueueNew(8, sizeof(net_msg_t), NULL)` 创建
- **队列句柄全局可见**：通过 `extern osMessageQueueId_t netMsgQueueHandle` 引用
- **队列满时**：`net_send()` / `net_recv()` 会阻塞等待，直到 `netTask` 处理完消息腾出空间
- **`netTask` 不可向自身发消息**：`netTask` 中直接调 `net_send()` 会死锁（等待自己处理消息），必须在 `netTask` 中使用 `net_send_sock()` 等内部版本
- **`net_socket()` / `net_bind()` / `net_listen()` 无需线程安全**：这些仅在初始化阶段调用，消息队列仅保护数据收发和连接关闭

---

## 4. 快速开始

### 4.1 包含头文件

```c
#include "net_socket.h"
#include "net_select.h"
```

### 4.2 初始化网络

```c
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();  /* 必须在主循环中调用 */
        /* 其他任务... */
    }
}
```

### 4.3 主循环要求

**重要**：本项目使用 FreeRTOS 多任务架构，`net_poll()` 和 `net_process_messages()` 均在 `netTask`（FreeRTOS 线程）中定期调用，而非主循环。

- **主循环**：系统初始化完成后进入空循环，等待 `osKernelStart()` 启动调度器
- **netTask**：在任务函数中持续运行，每 10ms 调用一次 `net_poll()` + `net_process_messages()`，负责：
  - 轮询 CH395F INT# 引脚电平（GPIO 轮询，无需 EXTI 中断）
  - 通过 `GET_GLOB_INT_STATUS_ALL`（2字节版）读取所有 8 个 Socket 的中断状态
  - 更新所有 Socket 的连接状态（CONNECT / DISCONNECT / RECV / TIMEOUT）
  - 触发事件回调
  - 处理数据接收

```c
int main(void)
{
    /* 外设初始化... */
    
    /* BSD Socket API 初始化 */
    net_init("192.168.1.100", "255.255.255.0", "192.168.1.1");
    
    /* 创建 Socket、绑定、监听... */
    
    osKernelInitialize();
    MX_FREERTOS_Init();   /* 创建 netTask 等 FreeRTOS 对象 */
    osKernelStart();      /* 启动调度器 */
    
    while (1) { }         /* 空循环，控制流已交给调度器 */
}

/* netTask 中（freertos.c）： */
void StartNetTask(void *argument)
{
    for (;;)
    {
        net_poll();           /* CH395F 中断轮询 + Socket 状态更新 */
        net_process_messages(); /* 处理其他任务发送的网络操作请求 */
        
        /* 应用层网络业务逻辑... */
        
        osDelay(10);  /* 10ms 周期 */
    }
}
```

---

## 5. TCP 编程

### 5.1 TCP Server（单连接）

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

```c
#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. 开始监听 */
    if (net_listen(listen_sock) != 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);
        }
    }
}
```

### 5.2 TCP Client

```c
#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);
}
```

---

## 6. UDP 编程

### 6.1 UDP Client

```c
#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);
}
```

### 6.2 UDP Server

```c
#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);
        }
    }
}
```

---

## 7. I/O 多路复用

### 7.1 select 使用

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

```c
#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) {
            /* 超时 */
        }
    }
}
```

### 7.2 poll 使用

`net_poll_events()` 使用 `pollfd` 结构体数组：

```c
#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);
                /* 处理数据... */
            }
        }
    }
}
```

### 7.3 事件掩码说明

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

---

## 8. 事件回调机制

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

### 8.1 定义回调函数

```c
#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;
    }
}
```

### 8.2 注册回调

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

---

## 9. 字节序处理

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

### 9.1 端口号转换

```c
uint16_t port = 8080;

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

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

### 9.2 IP 地址转换

```c
/* 字符串转网络序 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" */
```

### 9.3 填充地址结构体

```c
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;
```

### 9.4 读取地址信息

```c
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);
}
```

---

## 10. API 参考

### 10.1 核心 API

#### net_init - 网络初始化

```c
int net_init(const char *ip, const char *mask, const char *gateway);
```

| 参数 | 说明 |
|------|------|
| `ip` | IP 地址字符串，NULL 使用 DHCP |
| `mask` | 子网掩码字符串，NULL 使用默认 |
| `gateway` | 网关地址字符串，NULL 使用默认 |
| 返回值 | 0 成功，-1 失败 |

#### net_poll - 状态轮询

```c
int net_poll(void);
```

| 参数 | 说明 |
|------|------|
| 返回值 | 有事件发生的 socket 数量 |

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

#### net_socket - 创建 Socket

```c
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 - 绑定地址

```c
int net_bind(int sockfd, const struct net_sockaddr *addr, int addrlen);
```

| 参数 | 说明 |
|------|------|
| `sockfd` | socket 描述符 |
| `addr` | 地址结构体指针 |
| `addrlen` | 地址结构体长度 |
| 返回值 | 0 成功，-1 失败 |

#### net_listen - 开始监听

```c
int net_listen(int sockfd);
```

| 参数 | 说明 |
|------|------|
| `sockfd` | socket 描述符 |
| 返回值 | 0 成功，-1 失败 |

#### net_accept - 接受连接

```c
int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen);
```

| 参数 | 说明 |
|------|------|
| `sockfd` | 监听 socket 描述符 |
| `addr` | 输出：客户端地址 |
| `addrlen` | 输入输出：地址长度 |
| 返回值 | 新 socket 描述符，失败返回 -1 |

#### net_connect - 连接服务器

```c
int net_connect(int sockfd, const struct net_sockaddr *addr, int addrlen);
```

| 参数 | 说明 |
|------|------|
| `sockfd` | socket 描述符 |
| `addr` | 服务器地址 |
| `addrlen` | 地址长度 |
| 返回值 | 0 成功，-1 失败 |

#### net_send - 发送数据

```c
int net_send(int sockfd, const void *buf, int len, int flags);
```

| 参数 | 说明 |
|------|------|
| `sockfd` | socket 描述符 |
| `buf` | 数据缓冲区 |
| `len` | 数据长度 |
| `flags` | 通常为 0 |
| 返回值 | 实际发送字节数，失败返回 -1 |

#### net_recv - 接收数据

```c
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 数据

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

#### net_recvfrom - 接收 UDP 数据

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

#### net_close - 关闭 Socket

```c
int net_close(int sockfd);
```

### 10.2 消息队列 API

#### net_process_messages - 处理消息队列

```c
void net_process_messages(void);
```

处理其他任务通过 `net_send()` / `net_recv()` / `net_close()` 发送到消息队列的请求。必须在 `netTask` 主循环中调用。

#### net_send_sock - 发送数据（内部版本）

```c
int net_send_sock(net_sock_t *p_sock, const void *buf, int len);
```

直接操作 Socket 控制块进行发送，不经过消息队列。**只能在 `netTask` 中调用**，用于 `netTask` 内的网络业务逻辑。

#### net_recv_sock - 接收数据（内部版本）

```c
int net_recv_sock(net_sock_t *p_sock, void *buf, int len, int flags);
```

直接操作 Socket 控制块进行接收，不经过消息队列。**只能在 `netTask` 中调用**。

#### net_close_sock - 关闭 Socket（内部版本）

```c
int net_close_sock(net_sock_t *p_sock);
```

直接操作 Socket 控制块关闭连接，不经过消息队列。**只能在 `netTask` 中调用**。

### 10.3 辅助 API

#### net_set_event_cb - 注册事件回调

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

#### net_get_sock - 获取 Socket 控制块

```c
net_sock_t *net_get_sock(int sockfd);
```

#### net_get_errno - 获取错误码

```c
int net_get_errno(void);
```

### 10.4 字节序转换

```c
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 -> 字符串 */
```

### 10.5 select/poll

```c
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);
```

### 10.6 fd_set 操作宏

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

---

## 11. 完整示例

### 11.1 TCP Client 自动重连

```c
#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_CLOSED))
            {
                /* 连接失败或断开 */
                printf("连接失败，准备重连...\r\n");
                net_close(sock);
                sock = -1;
            }
        }
    }
}
```

---

## 12. 常见问题

### 12.1 net_poll() 必须调用吗？

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

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

### 12.2 Socket 数量有限制吗？

是的，CH395F 最多支持 8 个 Socket（索引 0~7）。其中：
- TCP Server（单连接）：1 个监听 Socket 即数据通道
- TCP Client：最多 8 个
- UDP：最多 8 个

### 12.3 发送数据有什么限制？

- 每次发送的数据长度不能超过 `NET_SEND_BUF_SIZE`（默认 4096 字节）
- TCP 需要等待发送缓冲区空闲（`send_ready` 标志）
- UDP 单包载荷最大 1472 字节（写入超过此值，CH395F 会自动分片为多个 UDP 包发送）

### 12.4 如何处理连接断开？

**方式一：阻塞接收**
```c
int len = net_recv(sock, buf, sizeof(buf), 0);
if (len <= 0) {
    /* 连接断开或错误 */
    net_close(sock);
}
```

**方式二：非阻塞接收**
```c
int len = net_recv(sock, buf, sizeof(buf), NET_MSG_DONTWAIT);
if (len < 0 && net_get_errno() == NET_ERR_WOULDBLOCK) {
    /* 没有数据 */
} else if (len <= 0) {
    /* 连接断开 */
}
```

**方式三：事件回调**
```c
void callback(int sockfd, net_event_t event, void *arg)
{
    if (event == NET_EVENT_DISCONNECTED) {
        /* 连接断开 */
    }
}
```

### 12.5 select 和 poll 的区别？

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

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

### 12.6 错误码说明

| 错误码 | 宏定义 | 说明 |
|--------|--------|------|
| 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） |

### 12.7 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 | 未知错误 |

### 12.8 故障排查

**问题：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 秒以上

### 12.9 线程安全问题

**问题：`net_send()` / `net_recv()` / `net_close()` 可以在任意任务中调用吗？**

可以。这些公共 API 通过消息队列将请求委托给 `netTask` 串行执行，自身阻塞等待结果，是线程安全的。

**问题：`netTask` 内可以直接调用 `net_send()` / `net_recv()` 吗？**

不可以。`netTask` 中必须使用 `net_send_sock()` / `net_recv_sock()` 等内部版本，否则会死锁（任务向自身发送消息，永远无人处理）。内部版本直接操作 Socket 控制块，不经过消息队列。

**问题：`net_socket()` / `net_bind()` / `net_listen()` 线程安全吗？**

这些函数仅在系统初始化阶段（`osKernelStart()` 之前）调用，不存在并发访问。消息队列仅保护数据收发和连接关闭操作。

**问题：消息队列由谁创建？在哪里创建？**

由 CubeMX 在 `MX_FREERTOS_Init()` 中创建（`osMessageQueueNew(8, sizeof(net_msg_t), NULL)`），在 `osKernelInitialize()` 之后执行，确保 FreeRTOS 内核已就绪。
