1269 lines
34 KiB
Markdown
1269 lines
34 KiB
Markdown
# BSD Socket API 使用指南
|
||
|
||
## 目录
|
||
|
||
1. [概述](#1-概述)
|
||
2. [架构说明](#2-架构说明)
|
||
3. [快速开始](#3-快速开始)
|
||
4. [TCP 编程](#4-tcp-编程)
|
||
5. [UDP 编程](#5-udp-编程)
|
||
6. [I/O 多路复用](#6-io-多路复用)
|
||
7. [事件回调机制](#7-事件回调机制)
|
||
8. [字节序处理](#8-字节序处理)
|
||
9. [API 参考](#9-api-参考)
|
||
10. [完整示例](#10-完整示例)
|
||
11. [常见问题](#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_ALL`(2字节版,命令码 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 包含头文件
|
||
|
||
```c
|
||
#include "net_socket.h"
|
||
#include "net_select.h"
|
||
```
|
||
|
||
### 3.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(); /* 必须在主循环中调用 */
|
||
/* 其他任务... */
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3.3 主循环要求
|
||
|
||
**重要**:`net_poll()` 必须在主循环中定期调用,它负责:
|
||
- 轮询 CH395F INT# 引脚电平(GPIO 轮询,无需 EXTI 中断)
|
||
- 通过 `GET_GLOB_INT_STATUS_ALL`(2字节版)读取所有 8 个 Socket 的中断状态
|
||
- 更新所有 Socket 的连接状态(CONNECT / DISCONNECT / RECV / TIMEOUT)
|
||
- 触发事件回调
|
||
- 处理数据接收
|
||
|
||
```c
|
||
while (1)
|
||
{
|
||
net_poll(); /* 建议至少每 10ms 调用一次 */
|
||
|
||
/* 其他应用代码... */
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. TCP 编程
|
||
|
||
### 4.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. 开始监听(单连接模式 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,支持并发连接:
|
||
|
||
```c
|
||
#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);
|
||
|
||
/* 数据 Socket(1~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
|
||
|
||
```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);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 5. UDP 编程
|
||
|
||
### 5.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);
|
||
}
|
||
```
|
||
|
||
### 5.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);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 6. I/O 多路复用
|
||
|
||
### 6.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) {
|
||
/* 超时 */
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 6.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);
|
||
/* 处理数据... */
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 6.3 事件掩码说明
|
||
|
||
| 事件 | 说明 |
|
||
|------|------|
|
||
| `NET_POLLIN` | 有数据可读,或有新连接到达(监听 Socket) |
|
||
| `NET_POLLOUT` | 发送缓冲区有空间,可以写入数据 |
|
||
| `NET_POLLERR` | 发生错误 |
|
||
| `NET_POLLHUP` | 连接挂起(对端关闭) |
|
||
| `NET_POLLNVAL` | 无效的文件描述符 |
|
||
|
||
---
|
||
|
||
## 7. 事件回调机制
|
||
|
||
可以通过注册回调函数,异步获取 Socket 事件通知:
|
||
|
||
### 7.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;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 7.2 注册回调
|
||
|
||
```c
|
||
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 端口号转换
|
||
|
||
```c
|
||
uint16_t port = 8080;
|
||
|
||
/* 主机序 -> 网络序(发送前转换) */
|
||
uint16_t net_port = net_htons(port);
|
||
|
||
/* 网络序 -> 主机序(接收后转换) */
|
||
uint16_t host_port = net_ntohs(net_port);
|
||
```
|
||
|
||
### 8.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" */
|
||
```
|
||
|
||
### 8.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;
|
||
```
|
||
|
||
### 8.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);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 9. API 参考
|
||
|
||
### 9.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, int backlog);
|
||
```
|
||
|
||
| 参数 | 说明 |
|
||
|------|------|
|
||
| `sockfd` | socket 描述符 |
|
||
| `backlog` | 最大并发连接数(0=单连接) |
|
||
| 返回值 | 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);
|
||
```
|
||
|
||
### 9.2 辅助 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);
|
||
```
|
||
|
||
### 9.3 字节序转换
|
||
|
||
```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 -> 字符串 */
|
||
```
|
||
|
||
### 9.4 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);
|
||
```
|
||
|
||
### 9.5 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 是否在集合中 */
|
||
```
|
||
|
||
---
|
||
|
||
## 10. 完整示例
|
||
|
||
### 10.1 TCP Echo Server(多连接)
|
||
|
||
```c
|
||
#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 自动重连
|
||
|
||
```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_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 如何处理连接断开?
|
||
|
||
**方式一:阻塞接收**
|
||
```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) {
|
||
/* 连接断开 */
|
||
}
|
||
}
|
||
```
|
||
|
||
### 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 中断**
|
||
- 检查多连接模式下数据 Socket(1~7)是否设置了 `SET_PROTO_TCP` + `SET_SOUR_PORT`(与监听端口相同)
|
||
- 如果不设置,CH395F 不知道哪些 Socket 可用于自动分配,TCP SYN 被静默丢弃
|
||
|
||
**问题:数据 Socket OPEN 返回 0x1B(已被使用)**
|
||
- CH395F 多连接模式下,数据 Socket 由芯片自动管理,软件不应手动 `OPEN_SOCKET`
|
||
- 只需 `SET_PROTO_TCP` + `SET_SOUR_PORT`,CH395F 在有连接时自动打开
|
||
|
||
**问题: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 秒以上
|