7.2.1 Socket 基础网络编程
2026/7/18大约 10 分钟常用组件组件网络SocketTCPUDP
7.2.1 Socket 基础网络编程
📚 本节导读
学习时长:约 40 分钟
难度级别:⭐⭐⭐⭐☆
前置知识:TCP/IP 基础知识、Socket 编程、第 4 章 任务与 IPC
🎯 学习目标
- 理解 OneOS 网络协议栈的整体架构(LWIP + Socket + 应用协议 + Molink)
- 掌握 BSD Socket API(socket / bind / listen / accept / connect / send / recv)
- 掌握 TCP 客户端/服务端编程
- 掌握 UDP 通信编程
- 掌握 select IO 多路复用
一、网络协议栈概述
OneOS 网络协议栈采用分层架构设计,自底向上依次为:硬件模组层(Molink)-> TCP/IP 协议栈(LWIP 2.1.2)-> Socket 接口层 -> 应用协议层。各层职责清晰,上层无需关心底层硬件差异,极大地提高了开发效率和应用可移植性。
┌─────────────────────────────────────────────────────┐
│ 应用协议层 │
│ HTTP Client │ MQTT Client │ CoAP │ LwM2M │ WebSocket │
├─────────────────────────────────────────────────────┤
│ Socket 接口层 │
│ BSD Socket API (socket.h) │
├─────────────────────────────────────────────────────┤
│ TCP/IP 协议栈 │
│ LWIP 2.1.2 (IPv4 / IPv6) │
├─────────────────────────────────────────────────────┤
│ Molink 蜂窝模组抽象层 │
│ EC20 │ BC26 │ ESP8266 │ M5311 │ SIM7600CE │ ... │
└─────────────────────────────────────────────────────┘1.1 核心组件说明
| 组件 | 路径 | 说明 |
|---|---|---|
| LWIP 2.1.2 | components/net/ip/lwip-2.1.2/ | 轻量级 TCP/IP 协议栈,支持 IPv4/IPv6、TCP、UDP、ICMP、DHCP、DNS 等 |
| Socket 层 | components/net/socket/ | 标准 BSD Socket 兼容 API,提供 socket / bind / listen / accept / connect / send / recv 等接口 |
| HTTP 客户端 | components/net/protocols/http/httpclient-v1.1.0/ | 轻量级 HTTP/HTTPS 客户端,支持 GET / POST / PUT / DELETE / HEAD 方法 |
| Paho MQTT | components/net/protocols/mqtt/pahomqtt-v1.1.0/ | Eclipse Paho 嵌入式 MQTT 客户端,功能完整,支持 QoS 0/1/2 |
| Mini MQTT | components/net/protocols/mqtt/minimqtt/ | 轻量级 MQTT 客户端,资源占用极小,适合资源受限设备 |
| libcoap | components/net/protocols/coap/libcoap-v4.3.0/ | 完整 CoAP 协议实现,支持 RFC 7252,具备资源发现和块传输能力 |
| freecoap | components/net/protocols/coap/freecoap/ | 精简 CoAP 实现,代码量小,适合低资源场景 |
| Molink | components/net/molink/ | 蜂窝模组抽象层,统一管理 4G / NB-IoT / WiFi 等通信模组,支持 30+ 种模组型号 |
1.2 数据流路径
应用程序
│
▼
BSD Socket API (socket / send / recv)
│
▼
LWIP 协议栈 (TCP/UDP/IP 处理)
│
▼
网络设备接口 (netif)
│
├──► 以太网 (eth) ────► 有线网络
│
└──► Molink 适配层 ──► 4G / NB-IoT / WiFi 模组二、Socket 基础网络编程
2.1 概述
OneOS 提供标准的 BSD Socket API,接口定义位于 components/net/socket/include/ 目录。API 与 POSIX 标准高度兼容,开发者在 Linux 上编写的 Socket 程序可以几乎零成本地移植到 OneOS 平台。
2.2 核心 API 参考
2.2.1 Socket 创建与关闭
/* 创建 Socket
* domain: AF_INET (IPv4) 或 AF_INET6 (IPv6)
* type: SOCK_STREAM (TCP) 或 SOCK_DGRAM (UDP)
* protocol: 通常为 0,由系统自动选择
* 返回值: >= 0 表示 Socket 描述符,< 0 表示失败
*/
int socket(int domain, int type, int protocol);
/* 关闭 Socket,释放资源(注意:OneOS 的 BSD Socket 层使用 closesocket,不是 close) */
int closesocket(int fd);关键理解(易错点):OneOS 的 BSD Socket 层(
socket.h第42行)声明的是closesocket(fd),而非 POSIX 的close()。应用在关闭 Socket 时必须调用closesocket,调用close将导致编译错误或未定义行为。
2.2.2 地址绑定与监听(TCP 服务端)
/* 将 Socket 绑定到本地地址和端口 */
int bind(int s, const struct sockaddr *name, socklen_t namelen);
/* 开始监听连接请求,backlog 指定最大等待连接数 */
int listen(int s, int backlog);
/* 接受客户端连接,返回新的 Socket 描述符 */
int accept(int s, struct sockaddr *addr, socklen_t *addrlen);2.2.3 连接管理(TCP 客户端)
/* 连接到远程服务器 */
int connect(int s, const struct sockaddr *name, socklen_t namelen);2.2.4 数据传输
/* TCP 发送数据 */
int send(int s, const void *data, size_t size, int flags);
/* TCP 接收数据 */
int recv(int s, void *mem, size_t len, int flags);
/* UDP 发送数据(指定目标地址) */
int sendto(int s, const void *data, size_t size, int flags,
const struct sockaddr *to, socklen_t tolen);
/* UDP 接收数据(获取源地址) */
int recvfrom(int s, void *mem, size_t len, int flags,
struct sockaddr *from, socklen_t *fromlen);2.2.5 Socket 选项与 IO 多路复用
/* 设置 Socket 选项 */
int setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen);
/* 获取 Socket 选项 */
int getsockopt(int s, int level, int optname, void *optval, socklen_t *optlen);
/* IO 多路复用:同时监控多个 Socket 的可读/可写/异常状态 */
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,
struct timeval *timeout);
/* 辅助宏 */
FD_ZERO(fd_set *set); /* 清空描述符集 */
FD_SET(int fd, fd_set *set); /* 添加描述符到集合 */
FD_CLR(int fd, fd_set *set); /* 从集合中移除描述符 */
FD_ISSET(int fd, fd_set *set); /* 检查描述符是否在集合中 */2.3 地址结构体
/* 通用 Socket 地址结构 */
struct sockaddr {
uint8_t sa_len; /* 地址长度 */
sa_family_t sa_family; /* 地址族:AF_INET */
char sa_data[14]; /* 地址数据 */
};
/* IPv4 地址结构 */
struct sockaddr_in {
uint8_t sin_len; /* 结构体长度 */
sa_family_t sin_family; /* AF_INET */
in_port_t sin_port; /* 16 位端口号(网络字节序) */
struct in_addr sin_addr; /* 32 位 IPv4 地址 */
char sin_zero[8]; /* 填充字段 */
};
/* IPv4 地址 */
struct in_addr {
uint32_t s_addr; /* 32 位 IPv4 地址(网络字节序) */
};2.4 地址转换函数
/* 点分十进制字符串 -> 网络字节序 32 位值 */
uint32_t inet_addr(const char *cp);
/* 网络字节序 32 位值 -> 点分十进制字符串 */
char *inet_ntoa(struct in_addr in);
/* 主机字节序 -> 网络字节序(16 位) */
uint16_t htons(uint16_t hostshort);
/* 网络字节序 -> 主机字节序(16 位) */
uint16_t ntohs(uint16_t netshort);
/* 主机字节序 -> 网络字节序(32 位) */
uint32_t htonl(uint32_t hostlong);
/* 网络字节序 -> 主机字节序(32 位) */
uint32_t ntohl(uint32_t netlong);2.5 TCP 服务端完整示例
本示例创建一个 TCP 回显服务器,监听 8080 端口,将收到的数据原样返回给客户端。
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <oneos_config.h>
#define SERVER_PORT 8080
#define BUF_SIZE 1024
#define BACKLOG 5
void tcp_server_task(void *arg)
{
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len;
char buf[BUF_SIZE];
int ret;
/* 1. 创建 TCP Socket */
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
os_kprintf("[TCP Server] socket create failed\r\n");
return;
}
/* 2. 设置地址重用选项(避免 TIME_WAIT 导致绑定失败) */
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
/* 3. 绑定本地地址和端口 */
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = INADDR_ANY; /* 监听所有网络接口 */
ret = bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (ret < 0) {
os_kprintf("[TCP Server] bind failed, ret=%d\r\n", ret);
closesocket(server_fd);
return;
}
/* 4. 开始监听 */
ret = listen(server_fd, BACKLOG);
if (ret < 0) {
os_kprintf("[TCP Server] listen failed, ret=%d\r\n", ret);
closesocket(server_fd);
return;
}
os_kprintf("[TCP Server] listening on port %d, backlog=%d\r\n", SERVER_PORT, BACKLOG);
/* 5. 主循环:接受连接并处理 */
while (1) {
client_len = sizeof(client_addr);
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
if (client_fd < 0) {
os_kprintf("[TCP Server] accept failed\r\n");
continue;
}
os_kprintf("[TCP Server] client connected: %s:%d\r\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
/* 6. 接收并回显数据 */
while (1) {
ret = recv(client_fd, buf, BUF_SIZE - 1, 0);
if (ret <= 0) {
if (ret == 0) {
os_kprintf("[TCP Server] client disconnected\r\n");
} else {
os_kprintf("[TCP Server] recv error: %d\r\n", ret);
}
break;
}
buf[ret] = '\0';
os_kprintf("[TCP Server] received %d bytes: %s\r\n", ret, buf);
/* 回显数据 */
send(client_fd, buf, ret, 0);
}
closesocket(client_fd);
}
}2.6 TCP 客户端完整示例
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <oneos_config.h>
#define SERVER_IP "192.168.1.100"
#define SERVER_PORT 8080
#define BUF_SIZE 256
void tcp_client_task(void *arg)
{
int sock_fd;
struct sockaddr_in server_addr;
char buf[BUF_SIZE];
int ret;
/* 1. 创建 TCP Socket */
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd < 0) {
os_kprintf("[TCP Client] socket create failed\r\n");
return;
}
/* 2. 设置服务器地址 */
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
/* 3. 连接服务器 */
os_kprintf("[TCP Client] connecting to %s:%d...\r\n", SERVER_IP, SERVER_PORT);
ret = connect(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (ret < 0) {
os_kprintf("[TCP Client] connect failed, ret=%d\r\n", ret);
closesocket(sock_fd);
return;
}
os_kprintf("[TCP Client] connected!\r\n");
/* 4. 发送数据 */
const char *msg = "Hello from OneOS TCP Client!";
ret = send(sock_fd, msg, strlen(msg), 0);
if (ret > 0) {
os_kprintf("[TCP Client] sent %d bytes: %s\r\n", ret, msg);
}
/* 5. 接收服务器响应 */
ret = recv(sock_fd, buf, BUF_SIZE - 1, 0);
if (ret > 0) {
buf[ret] = '\0';
os_kprintf("[TCP Client] received %d bytes: %s\r\n", ret, buf);
}
/* 6. 关闭连接 */
closesocket(sock_fd);
os_kprintf("[TCP Client] connection closed\r\n");
}2.7 UDP 通信示例
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <oneos_config.h>
#define UDP_PORT 9090
#define BUF_SIZE 256
/* UDP 回显服务器 */
void udp_echo_server(void *arg)
{
int sock_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len;
char buf[BUF_SIZE];
int ret;
/* 1. 创建 UDP Socket */
sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (sock_fd < 0) {
os_kprintf("[UDP Server] socket create failed\r\n");
return;
}
/* 2. 绑定地址 */
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(UDP_PORT);
server_addr.sin_addr.s_addr = INADDR_ANY;
ret = bind(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (ret < 0) {
os_kprintf("[UDP Server] bind failed\r\n");
closesocket(sock_fd);
return;
}
os_kprintf("[UDP Server] listening on port %d\r\n", UDP_PORT);
/* 3. 接收并回显 */
while (1) {
client_len = sizeof(client_addr);
ret = recvfrom(sock_fd, buf, BUF_SIZE, 0,
(struct sockaddr *)&client_addr, &client_len);
if (ret > 0) {
os_kprintf("[UDP Server] received %d bytes from %s:%d\r\n",
ret, inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
/* 回显数据 */
sendto(sock_fd, buf, ret, 0,
(struct sockaddr *)&client_addr, client_len);
}
}
}
/* UDP 客户端 */
void udp_client_task(void *arg)
{
int sock_fd;
struct sockaddr_in server_addr;
socklen_t addr_len;
char buf[BUF_SIZE];
int ret;
sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(UDP_PORT);
server_addr.sin_addr.s_addr = inet_addr("192.168.1.100");
const char *msg = "Hello UDP!";
sendto(sock_fd, msg, strlen(msg), 0,
(struct sockaddr *)&server_addr, sizeof(server_addr));
addr_len = sizeof(server_addr);
ret = recvfrom(sock_fd, buf, BUF_SIZE, 0,
(struct sockaddr *)&server_addr, &addr_len);
if (ret > 0) {
buf[ret] = '\0';
os_kprintf("[UDP Client] echo: %s\r\n", buf);
}
closesocket(sock_fd);
}2.8 select IO 多路复用示例
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <oneos_config.h>
#define SELECT_PORT 7070
#define SELECT_BACKLOG 5
#define BUF_SIZE 256
void select_example(void *arg)
{
int listen_fd, client_fd, max_fd;
struct sockaddr_in addr;
fd_set read_fds;
struct timeval timeout;
char buf[BUF_SIZE];
int ret;
/* 1. 创建监听 Socket */
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd < 0) {
os_kprintf("socket create failed\r\n");
return;
}
/* 2. 绑定本地端口 */
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(SELECT_PORT);
addr.sin_addr.s_addr = INADDR_ANY;
ret = bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr));
if (ret < 0) {
os_kprintf("bind failed\r\n");
closesocket(listen_fd);
return;
}
/* 3. 开始监听 */
ret = listen(listen_fd, SELECT_BACKLOG);
if (ret < 0) {
os_kprintf("listen failed\r\n");
closesocket(listen_fd);
return;
}
/* 4. 主循环:用 select 同时监控监听 Socket 的可读状态 */
while (1) {
FD_ZERO(&read_fds);
FD_SET(listen_fd, &read_fds);
max_fd = listen_fd;
/* 设置超时时间 5 秒 */
timeout.tv_sec = 5;
timeout.tv_usec = 0;
ret = select(max_fd + 1, &read_fds, NULL, NULL, &timeout);
if (ret < 0) {
os_kprintf("select error\r\n");
break;
} else if (ret == 0) {
os_kprintf("select timeout, no new connection\r\n");
continue;
}
/* 5. 检查监听 Socket 是否有新连接可读 */
if (FD_ISSET(listen_fd, &read_fds)) {
client_fd = accept(listen_fd, NULL, NULL);
if (client_fd >= 0) {
os_kprintf("new connection: fd=%d\r\n", client_fd);
closesocket(client_fd); /* 示例仅演示 accept,立即关闭 */
}
}
}
}📝 本节小结
核心要点回顾
- 四层架构:应用协议层(HTTP/MQTT/CoAP/LwM2M/WebSocket)→ Socket 接口层 → TCP/IP 协议栈(LWIP 2.1.2,IPv4/IPv6)→ Molink 蜂窝模组抽象层(EC20/BC26/ESP8266/M5311/SIM7600CE 等 30+ 模组)。
- BSD Socket 兼容层:位于
components/net/socket/include/sys_socket/,<sys/socket.h>/<netinet/in.h>/<arpa/inet.h>均有效,与 POSIX 高度兼容,Linux 程序可低成本移植。 - TCP 服务端流程:
socket→bind→listen→accept→recv/send→closesocket。 - TCP 客户端流程:
socket→connect→send/recv→closesocket。 - UDP 无连接通信:
socket→bind→sendto/recvfrom(每次收发都带目标/源地址)→closesocket。 - select IO 多路复用:
FD_ZERO/FD_SET/select/FD_ISSET实现单线程同时监控多个 Socket 可读/可写/异常状态。 - 地址结构体:
sockaddr(通用)、sockaddr_in(IPv4,含sin_len/sin_family/sin_port/sin_addr/sin_zero)、in_addr(32 位 IPv4 地址,网络字节序)。 - 字节序转换:
htons/ntohs(16 位)、htonl/ntohl(32 位)、inet_addr/inet_ntoa(点分十进制与 32 位值互转)。
关键易错点速查
| 易错点 | 正确做法 |
|---|---|
关闭 Socket 用 close() | OneOS BSD Socket 层声明的是 closesocket(fd) → 必须用 closesocket |
包含 <oneos/os_kernel.h> | 该头文件不存在 → 应用统一包含 <oneos_config.h> |
bind 失败后不释放 | 失败路径要先 closesocket(fd) 再 return,避免描述符泄漏 |
忘记 memset(&addr, 0, ...) | sockaddr_in 未清零就赋值,sin_zero 残留脏数据 → 先清零再填字段 |
| 服务端地址重用冲突 | 启用 SO_REUSEADDR(setsockopt)避免 TIME_WAIT 导致绑定失败 |
| 端口用主机字节序直接赋值 | sin_port 须 htons() 转网络字节序;sin_addr 用 inet_addr() 或 htonl() |
recv 返回值处理不严谨 | >0 收到数据、==0 对端关闭、<0 出错 → 三种都要处理 |
用 select 但忘了 maxfdp1 | 第一参数须是最大描述符 + 1,而不是连接数 |
UDP 用 send/recv | UDP 无连接须用 sendto/recvfrom 携带地址参数 |
| select 只监控监听 Socket 不监控已连接 Socket | 服务端若要同时处理多个连接,须把各 client_fd 也 FD_SET 进集合 |
下一步
- 7.2.2 HTTP 协议:Socket 已提供传输层能力,下一节学习基于它的 HTTP 应用协议,掌握 OneOS HTTP 客户端的 GET/POST 请求与响应解析,实现设备联网上报与云端交互。