NuttX Socket 源码学习
概述
NuttX 的 socket 实现是一个精心设计的网络编程接口,提供了标准的 BSD socket API。该实现采用分层架构设计,支持多种网络协议族(如 TCP/IP、UDP、Unix域套接字等),具有良好的可扩展性和模块化特性。
整体架构设计
分层架构
NuttX socket 采用经典的分层架构设计:
应用层 API (socket, bind, connect, etc.)↓
Socket 抽象层 (socket.c, net_sockif.c)↓
协议族接口层 (inet, local, can, netlink, etc.)↓
具体协议实现层 (TCP, UDP, etc.)↓
设备接口层 (netdev)
核心组件
-
Socket 抽象层: 提供统一的接口抽象,屏蔽不同协议族的差异
-
协议族接口: 为不同协议族提供统一的接口实现
-
连接管理: 管理 socket 连接的生命周期和状态
-
数据传输: 处理数据的发送和接收
核心数据结构
1. struct socket - Socket 结构体
// 位置: nuttx/include/nuttx/net/net.h
struct socket
{uint8_t s_domain; // IP 域(协议族)uint8_t s_type; // 协议类型(SOCK_STREAM, SOCK_DGRAM等)uint16_t s_proto; // Socket 协议FAR void *s_conn; // 连接对象(继承自 socket_conn_s)// Socket 接口函数表FAR const struct sock_intf_s *s_sockif;
};
作用:
-
表示一个 socket 实例
-
包含协议族、类型、协议等基本信息
-
通过 s_sockif 指向具体的协议实现接口
2. struct socket_conn_s - Socket 连接基类
// 位置: nuttx/include/nuttx/net/net.h
struct socket_conn_s
{dq_entry_t node; // 双向链表节点// 回调函数链表(用于异步事件处理)FAR struct devif_callback_s *list;FAR struct devif_callback_s *list_tail;// Socket 选项
#ifdef CONFIG_NET_SOCKOPTSint16_t s_error; // 最后的错误码sockopt_t s_options; // 选择的 socket 选项socktimeo_t s_rcvtimeo; // 接收超时值(决秒)socktimeo_t s_sndtimeo; // 发送超时值(决秒)
#ifdef CONFIG_NET_SOLINGERsocktimeo_t s_linger; // Linger 超时值
#endif
#ifdef CONFIG_NET_BINDTODEVICEuint8_t s_boundto; // 绑定的网络接口索引
#endif
#endifuint8_t s_flags; // Socket 状态标志位uint8_t s_tos; // IPv4 服务类型/IPv6 流量类
#if defined(CONFIG_NET_IPv4) || defined(CONFIG_NET_IPv6)uint8_t s_ttl; // 默认生存时间
#endif
};
状态标志位说明:
-
_SF_INITD
: Socket 结构已初始化 -
_SF_NONBLOCK
: 非阻塞模式 -
_SF_LISTENING
: 正在监听(SOCK_STREAM) -
_SF_BOUND
: 已绑定到地址 -
_SF_CONNECTED
: 已连接 -
_SF_CLOSED
: 已关闭
3. struct sock_intf_s - Socket 接口函数表
// 位置: nuttx/include/nuttx/net/net.h
struct sock_intf_s
{// 基本操作CODE int (*si_setup)(FAR struct socket *psock);CODE sockcaps_t (*si_sockcaps)(FAR struct socket *psock);CODE void (*si_addref)(FAR struct socket *psock);// 连接管理CODE int (*si_bind)(FAR struct socket *psock,FAR const struct sockaddr *addr, socklen_t addrlen);CODE int (*si_listen)(FAR struct socket *psock, int backlog);CODE int (*si_connect)(FAR struct socket *psock,FAR const struct sockaddr *addr, socklen_t addrlen);CODE int (*si_accept)(FAR struct socket *psock,FAR struct sockaddr *addr, FAR socklen_t *addrlen,FAR struct socket *newsock, int flags);// 数据传输CODE ssize_t (*si_sendmsg)(FAR struct socket *psock,FAR struct msghdr *msg, int flags);CODE ssize_t (*si_recvmsg)(FAR struct socket *psock,FAR struct msghdr *msg, int flags);// 信息获取CODE int (*si_getsockname)(FAR struct socket *psock,FAR struct sockaddr *addr, FAR socklen_t *addrlen);CODE int (*si_getpeername)(FAR struct socket *psock,FAR struct sockaddr *addr, FAR socklen_t *addrlen);// 其他操作CODE int (*si_poll)(FAR struct socket *psock,FAR struct pollfd *fds, bool setup);CODE int (*si_close)(FAR struct socket *psock);CODE int (*si_ioctl)(FAR struct socket *psock,int cmd, unsigned long arg);CODE int (*si_shutdown)(FAR struct socket *psock, int how);// 可选功能
#ifdef CONFIG_NET_SOCKOPTSCODE int (*si_getsockopt)(FAR struct socket *psock, int level,int option, FAR void *value, FAR socklen_t *value_len);CODE int (*si_setsockopt)(FAR struct socket *psock, int level,int option, FAR const void *value, socklen_t value_len);
#endif
#ifdef CONFIG_NET_SENDFILECODE ssize_t (*si_sendfile)(FAR struct socket *psock,FAR struct file *infile, FAR off_t *offset, size_t count);
#endif
};
作用: 定义了所有 socket 操作的虚函数表,实现多态性支持不同协议族。
Socket 生命周期管理
1. Socket 创建 (socket.c)
函数: psock_socket(int domain, int type, int protocol, FAR struct socket *psock)
流程:
-
参数验证和类型掩码处理
-
初始化 socket 基本结构
-
尝试 usrsock 接口(如果启用)
-
通过
net_sockif()
获取协议族接口 -
调用协议族的
si_setup()
进行特定初始化 -
设置非阻塞标志和初始化标志
int psock_socket(int domain, int type, int protocol, FAR struct socket *psock)
{FAR const struct sock_intf_s *sockif = NULL;int ret;// 参数验证if (type & ~(SOCK_CLOEXEC | SOCK_NONBLOCK | SOCK_TYPE_MASK)){return -EINVAL;}// 初始化 socket 结构psock->s_domain = domain;psock->s_proto = protocol;psock->s_conn = NULL;psock->s_type = type & SOCK_TYPE_MASK;// 获取协议族接口sockif = net_sockif(domain, psock->s_type, psock->s_proto);if (sockif == NULL){return -EAFNOSUPPORT;}// 协议族特定初始化psock->s_sockif = sockif;ret = sockif->si_setup(psock);if (ret >= 0){FAR struct socket_conn_s *conn = psock->s_conn;if (type & SOCK_NONBLOCK){conn->s_flags |= _SF_NONBLOCK;}conn->s_flags |= _SF_INITD;}return ret;
}
2. Socket 绑定 (bind.c)
函数: psock_bind(FAR struct socket *psock, const struct sockaddr *addr, socklen_t addrlen)
流程:
-
验证 socket 和地址有效性
-
调用协议族的
si_bind()
实现 -
设置
_SF_BOUND
标志
int psock_bind(FAR struct socket *psock, const struct sockaddr *addr, socklen_t addrlen)
{int ret = OK;// 验证参数if (!psock || psock->s_conn == NULL){return -EBADF;}if (addr == NULL){return -EFAULT;}// 调用协议族的绑定实现if (psock->s_sockif->si_bind == NULL){return -EOPNOTSUPP;}ret = psock->s_sockif->si_bind(psock, addr, addrlen);// 设置绑定标志if (ret >= 0){FAR struct socket_conn_s *conn = psock->s_conn;conn->s_flags |= _SF_BOUND;}return ret;
}
3. Socket 连接 (connect.c)
函数: psock_connect(FAR struct socket *psock, FAR const struct sockaddr *addr, socklen_t addrlen)
特点:
-
支持阻塞和非阻塞连接
-
取消点支持(可被信号中断)
-
连接状态管理
4. Socket 监听和接受 (listen.c, accept.c)
监听: psock_listen()
- 设置 _SF_LISTENING
标志 接受: psock_accept()
- 创建新的连接 socket
数据传输机制
1. 发送数据架构
send() -> sendto() -> sendmsg() -> psock_sendmsg() -> si_sendmsg()
统一接口: 所有发送函数最终都通过 psock_sendmsg()
实现
// send.c 中的实现
ssize_t psock_send(FAR struct socket *psock, FAR const void *buf, size_t len, int flags)
{struct msghdr msg;struct iovec iov;// 构造 msghdr 结构iov.iov_base = (FAR void *)buf;iov.iov_len = len;msg.msg_name = NULL;msg.msg_namelen = 0;msg.msg_iov = &iov;msg.msg_iovlen = 1;msg.msg_control = NULL;msg.msg_controllen = 0;msg.msg_flags = 0;// 统一调用 sendmsgreturn psock_sendmsg(psock, &msg, flags);
}
2. 接收数据架构
recv() -> recvfrom() -> recvmsg() -> psock_recvmsg() -> si_recvmsg()
特点:
-
支持阻塞和非阻塞接收
-
超时控制
-
取消点支持
协议族支持
1. 协议族注册机制
通过 net_sockif()
函数进行协议族路由:
FAR const struct sock_intf_s *net_sockif(sa_family_t family, int type, int protocol)
{switch (family){case PF_INET: // IPv4case PF_INET6: // IPv6return inet_sockif(family, type, protocol);case PF_LOCAL: // Unix 域套接字return &g_local_sockif;case PF_CAN: // CAN 总线return &g_can_sockif;case PF_NETLINK: // Netlink 套接字return &g_netlink_sockif;case PF_PACKET: // 原始包套接字return &g_pkt_sockif;case PF_BLUETOOTH: // 蓝牙套接字return &g_bluetooth_sockif;case PF_IEEE802154: // IEEE 802.15.4return &g_ieee802154_sockif;case PF_RPMSG: // RPMSG 套接字return &g_rpmsg_sockif;default:return NULL;}
}
2. 主要协议族
-
INET 协议族 (IPv4/IPv6):
-
支持 TCP、UDP、ICMP 等协议
-
完整的网络功能支持
-
-
LOCAL 协议族 (Unix 域套接字):
-
进程间通信
-
支持 SOCK_STREAM 和 SOCK_DGRAM
-
-
CAN 协议族:
-
CAN 总线通信
-
汽车电子应用
-
-
其他协议族:
-
NETLINK: 内核通信
-
PACKET: 原始数据包
-
BLUETOOTH: 蓝牙通信
-
Socket 选项管理
1. Socket 选项位定义
// socket.h 中的定义
#define _SO_BIT(o) (1 << (o))#define _SO_BROADCAST _SO_BIT(SO_BROADCAST) // 广播权限
#define _SO_DEBUG _SO_BIT(SO_DEBUG) // 调试信息
#define _SO_DONTROUTE _SO_BIT(SO_DONTROUTE) // 绕过路由
#define _SO_KEEPALIVE _SO_BIT(SO_KEEPALIVE) // 保持连接活跃
#define _SO_LINGER _SO_BIT(SO_LINGER) // 延迟关闭
#define _SO_OOBINLINE _SO_BIT(SO_OOBINLINE) // 带外数据内联
#define _SO_RCVBUF _SO_BIT(SO_RCVBUF) // 接收缓冲区大小
#define _SO_RCVTIMEO _SO_BIT(SO_RCVTIMEO) // 接收超时
#define _SO_REUSEADDR _SO_BIT(SO_REUSEADDR) // 地址重用
#define _SO_SNDBUF _SO_BIT(SO_SNDBUF) // 发送缓冲区大小
#define _SO_SNDTIMEO _SO_BIT(SO_SNDTIMEO) // 发送超时
#define _SO_TIMESTAMP _SO_BIT(SO_TIMESTAMP) // 时间戳
#define _SO_BINDTODEVICE _SO_BIT(SO_BINDTODEVICE) // 绑定到设备
2. 选项操作宏
#define _SO_SETOPT(s,o) ((s) |= _SO_BIT(o)) // 设置选项
#define _SO_CLROPT(s,o) ((s) &= ~_SO_BIT(o)) // 清除选项
#define _SO_GETOPT(s,o) (((s) & _SO_BIT(o)) != 0) // 检查选项
3. 错误处理宏
#ifdef CONFIG_NET_SOCKOPTS
#define _SO_SETERRNO(s,e) \do { \if (s != NULL) { \_SO_CONN_SETERRNO((s)->s_conn, e); \} \} while (0)
#endif
超时控制机制
1. 超时值定义
typedef uint16_t socktimeo_t; // 超时类型(决秒为单位,1决秒=0.1秒)#define _SO_TIMEOUT(t) ((t) ? (t) * MSEC_PER_DSEC : UINT_MAX)
2. 超时检查函数
// net_timeo.c
int net_timeo(clock_t start_time, socktimeo_t timeo)
{// 检查是否超时if (timeo != 0){clock_t elapsed = clock() - start_time;if (elapsed >= timeo * MSEC_PER_DSEC){return 1; // 超时}}return 0; // 未超时
}
错误处理机制
1. 错误码设置
每个 socket 连接结构都包含错误字段:
int16_t s_error; // 最后发生的错误
2. 错误传播
-
内核错误通过返回负的错误码
-
用户空间API通过设置 errno 并返回 -1
3. 常见错误码
-
EBADF
: 无效的文件描述符 -
EAFNOSUPPORT
: 不支持的地址族 -
EADDRINUSE
: 地址已被使用 -
ECONNREFUSED
: 连接被拒绝 -
ETIMEDOUT
: 连接超时 -
EAGAIN
: 资源暂时不可用
内存管理
1. 连接结构分配
每个协议族负责分配和管理自己的连接结构:
// 例如 TCP 连接分配
FAR struct tcp_conn_s *conn = tcp_alloc();
psock->s_conn = conn;
2. 缓冲区管理
-
使用 IOB (I/O Buffer) 进行数据缓冲
-
支持零拷贝优化
-
动态内存分配和释放
同步机制
1. 网络锁
net_lock(); // 获取网络全局锁
// 临界区代码
net_unlock(); // 释放网络全局锁
2. 回调机制
使用 devif_callback_s 结构进行异步事件处理:
struct devif_callback_s
{FAR struct devif_callback_s *nxtdev; // 下一个回调uint16_t flags; // 事件标志uint16_t priv; // 私有数据devif_callback_event_t event; // 回调函数
};
调试和监控
1. 调试宏
#ifdef CONFIG_DEBUG_NET
# define ninfo(format, ...) info(format, ##__VA_ARGS__)
# define nerr(format, ...) err(format, ##__VA_ARGS__)
#else
# define ninfo(x...)
# define nerr(x...)
#endif
2. 统计信息
NuttX 提供网络统计信息用于监控和调试:
-
连接数统计
-
数据包统计
-
错误统计
性能优化
1. 零拷贝支持
-
sendfile() 实现零拷贝文件传输
-
IOB 链表减少内存拷贝
2. 异步I/O
-
非阻塞操作支持
-
poll/select 多路复用
-
回调驱动的事件处理
3. 缓冲区优化
-
可配置的缓冲区大小
-
动态缓冲区分配
-
内存池管理
配置选项
1. 主要配置项
CONFIG_NET // 启用网络支持
CONFIG_NET_SOCKOPTS // 启用 socket 选项
CONFIG_NET_SOLINGER // 启用 linger 选项
CONFIG_NET_BINDTODEVICE // 启用绑定到设备
CONFIG_NET_SENDFILE // 启用 sendfile 支持
CONFIG_NET_USRSOCK // 启用用户空间 socket
2. 协议族配置
CONFIG_NET_LOCAL // Unix 域套接字
CONFIG_NET_CAN // CAN 总线
CONFIG_NET_NETLINK // Netlink 套接字
CONFIG_NET_PKT // 原始包套接字
CONFIG_NET_BLUETOOTH // 蓝牙支持
CONFIG_NET_IEEE802154 // IEEE 802.15.4 支持
扩展新协议族的方法
1. 定义协议族接口
const struct sock_intf_s g_myproto_sockif =
{myproto_setup, // 初始化myproto_sockcaps, // 能力查询myproto_addref, // 引用计数myproto_bind, // 绑定myproto_getsockname, // 获取本地地址myproto_getpeername, // 获取对端地址myproto_listen, // 监听myproto_connect, // 连接myproto_accept, // 接受连接myproto_poll, // 轮询myproto_sendmsg, // 发送消息myproto_recvmsg, // 接收消息myproto_close, // 关闭myproto_ioctl, // 控制操作myproto_socketpair, // 创建套接字对myproto_shutdown // 关闭连接
};
2. 在 net_sockif() 中注册
case PF_MYPROTO:sockif = &g_myproto_sockif;break;
3. 实现连接结构
struct myproto_conn_s
{struct socket_conn_s sconn; // 基类(必须是第一个成员)// 协议特定的字段// ...
};
最佳实践
1. 错误处理
-
始终检查函数返回值
-
适当设置和传播错误码
-
提供有意义的错误信息
2. 内存管理
-
及时释放分配的资源
-
避免内存泄漏
-
使用引用计数管理共享资源
3. 并发安全
-
适当使用网络锁保护临界区
-
避免死锁和竞态条件
-
考虑中断上下文的安全性
4. 性能考虑
-
减少不必要的内存拷贝
-
使用适当的缓冲区大小
-
考虑异步操作的使用
总结
NuttX 的 socket 实现展现了优秀的软件架构设计:
-
分层设计: 清晰的抽象层次,良好的模块化
-
多态支持: 通过函数指针表实现协议族的多态性
-
可扩展性: 易于添加新的协议族支持
-
标准兼容: 遵循 POSIX/BSD socket API 标准
-
性能优化: 零拷贝、异步I/O等优化技术
-
健壮性: 完善的错误处理和资源管理
这个实现为嵌入式系统提供了功能完整、性能优秀的网络编程接口,是学习网络协议栈设计的优秀范例。
参考文件列表
-
nuttx/net/socket/socket.h
- Socket 模块头文件 -
nuttx/net/socket/socket.c
- Socket 创建实现 -
nuttx/net/socket/net_sockif.c
- 协议族接口路由 -
nuttx/net/socket/bind.c
- Socket 绑定实现 -
nuttx/net/socket/connect.c
- Socket 连接实现 -
nuttx/net/socket/accept.c
- 连接接受实现 -
nuttx/net/socket/send.c
- 数据发送实现 -
nuttx/net/socket/recv.c
- 数据接收实现 -
nuttx/net/socket/setsockopt.c
- Socket 选项设置 -
nuttx/net/socket/getsockopt.c
- Socket 选项获取 -
nuttx/include/nuttx/net/net.h
- 网络核心数据结构定义