7.2.1 Socket 基础网络编程
2026/7/18大约 9 分钟常用组件组件网络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 │ SIM7600 │ ... │
└─────────────────────────────────────────────────────┘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,释放资源 */
int close(int s);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/os_kernel.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);
close(server_fd);
return;
}
/* 4. 开始监听 */
ret = listen(server_fd, BACKLOG);
if (ret < 0) {
os_kprintf("[TCP Server] listen failed, ret=%d\r\n", ret);
close(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);
}
close(client_fd);
}
}2.6 TCP 客户端完整示例
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <oneos/os_kernel.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);
close(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. 关闭连接 */
close(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/os_kernel.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");
close(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);
}
close(sock_fd);
}2.8 select IO 多路复用示例
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[256];
int ret;
/* 创建监听 Socket(省略绑定和监听步骤) */
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
/* ... bind & listen ... */
while (1) {
FD_ZERO(&read_fds);
FD_SET(listen_fd, &read_fds);
max_fd = listen_fd;
/* 将已连接的客户端也加入监控集合 */
/* FD_SET(client_fd, &read_fds); */
/* 设置超时时间 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\r\n");
continue;
}
/* 检查监听 Socket 是否有新连接 */
if (FD_ISSET(listen_fd, &read_fds)) {
client_fd = accept(listen_fd, NULL, NULL);
os_kprintf("new connection: fd=%d\r\n", client_fd);
}
/* 检查客户端 Socket 是否有数据 */
/* if (FD_ISSET(client_fd, &read_fds)) { ... } */
}
}📝 本节小结
本节介绍了 OneOS 网络协议栈的整体架构和 BSD Socket 基础编程。OneOS 基于 LWIP 2.1.2 提供完整的 TCP/IP 协议栈能力,通过 BSD Socket 兼容层为开发者提供标准的网络编程接口。
核心要点回顾:
- OneOS 网络协议栈采用 LWIP + Socket + 应用协议 + Molink 四层架构
- BSD Socket API 与 POSIX 兼容,支持 TCP/UDP 通信、select 多路复用
- TCP 服务端需经过 socket → bind → listen → accept 流程
- TCP 客户端需经过 socket → connect 流程
- UDP 使用 sendto / recvfrom 进行无连接通信
- select 可实现单线程管理多个 Socket 连接