SylixOS 中的 Unix Socket
Unix Domain Socket(简称 Unix Socket)是一种在同一主机内进行进程间通信(IPC)的高效机制。与网络套接字不同,它不依赖 IP 协议栈,而是通过文件系统路径(或抽象命名空间)作为地址标识,实现本地进程间的可靠或不可靠通信。零网络开销;支持流式(SOCK_STREAM)和数据报(SOCK_DGRAM)两种语义可传递文件描述符、凭证等高级特性(本文暂不展开)。本文将从接口使
1、简介
Unix Domain Socket(简称 Unix Socket)是一种在同一主机内进行进程间通信(IPC)的高效机制。与网络套接字不同,它不依赖 IP 协议栈,而是通过文件系统路径(或抽象命名空间)作为地址标识,实现本地进程间的可靠或不可靠通信。
其核心优势在于:
- 零网络开销;
- 支持流式(SOCK_STREAM)和数据报(SOCK_DGRAM)两种语义
- 可传递文件描述符、凭证等高级特性(本文暂不展开)。
本文将从接口使用、内核对象模型和通信流程三个层面,深入剖析 Unix Socket 的实现机制。
2、核心编程接口
Unix Socket 使用标准 BSD 套接字 API,服务端与客户端流程如下:
服务端
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
bind(sockfd, (struct sockaddr*)&addr, addrlen);
listen(sockfd, backlog);
int connfd = accept(sockfd, NULL, NULL); // 返回新连接的 fd
客户端
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
connect(sockfd, (struct sockaddr*)&addr, addrlen);
3、内部实现模型
在 SylixOS 系统中,每个 Unix Socket 由两个核心结构体协同管理:
- struct af_unix_t:表示 AF_UNIX 协议层的控制块,包含连接状态、对端指针、接收队列等;
- struct socket_t:表示通用套接字抽象,持有文件描述符(fd),负责与用户空间交互。
二者通过指针相互绑定,共同构成一个完整的 Unix Socket 实例。
typedef struct af_unix_t {
LW_LIST_LINE UNIX_lineManage;
LW_LIST_LINE UNIX_linePath;
unix_temp_mode_t UNIX_tempmode; /* 无文件模式 DGRAM */
BOOL UNIX_bHasConn; /* DGRAM 被连接过 */
LW_LIST_RING UNIX_ringConnect; /* 连接队列 */
LW_LIST_RING_HEADER UNIX_pringConnect; /* 等待连接的队列 */
INT UNIX_iConnNum; /* 等待连接的数量 */
INT UNIX_iReuse; /* REUSE flag */
INT UNIX_iFlag; /* NONBLOCK or NOT */
INT UNIX_iType; /* STREAM / DGRAM / SEQPACKET */
#define __AF_UNIX_STATUS_NONE 0 /* SOCK_DGRAM 永远处于此状态 */
#define __AF_UNIX_STATUS_LISTEN 1
#define __AF_UNIX_STATUS_CONNECT 2
#define __AF_UNIX_STATUS_ESTAB 3
INT UNIX_iStatus; /* 当前状态 (仅针对 STREAM) */
#define __AF_UNIX_SHUTD_R 0x01
#define __AF_UNIX_SHUTD_W 0x02
INT UNIX_iShutDFlag; /* 当前 shutdown 状态 */
INT UNIX_iBacklog; /* 等待的背景连接数量 */
struct af_unix_t *UNIX_pafunxPeer; /* 连接的远程节点 */
AF_UNIX_Q UNIX_unixq; /* 接收数据队列 */
size_t UNIX_stMaxBufSize; /* 最大接收缓冲大小 */
/* 超过此数量写入时, 需要阻塞 */
LW_OBJECT_HANDLE UNIX_hCanRead; /* 可读 */
LW_OBJECT_HANDLE UNIX_hCanWrite; /* 可写 (也可用作 accept) */
ULONG UNIX_ulSendTimeout; /* 发送超时 tick */
ULONG UNIX_ulRecvTimeout; /* 读取超时 tick */
ULONG UNIX_ulConnTimeout; /* 连接超时 tick */
struct linger UNIX_linger; /* 延迟关闭 */
INT UNIX_iPassCred; /* 是否允许传送认证信息 */
PVOID UNIX_sockFile; /* socket file */
CHAR UNIX_cFile[MAX_FILENAME_LENGTH];
} AF_UNIX_T;
typedef struct {
LW_LIST_LINE SOCK_lineManage; /* 管理链表 */
INT SOCK_iFamily; /* 协议簇 */
union {
INT SOCKF_iLwipFd; /* lwip 文件描述符 */
#if LW_CFG_NET_UNIX_EN > 0
AF_UNIX_T *SOCKF_pafunix; /* AF_UNIX 控制块 */
#endif
#if LW_CFG_NET_ROUTER > 0
AF_ROUTE_T *SOCKF_pafroute; /* AF_ROUTE 控制块 */
#endif
#if LW_CFG_NET_SCTP_EN > 0
AF_SCTP_T *SOCKF_pafsctp; /* AF_SCTP 控制块 */
#endif
AF_PACKET_T *SOCKF_pafpacket; /* AF_PACKET 控制块 */
PVOID SOCKF_pvContext;
} SOCK_family;
INT SOCK_iSoErr; /* 最后一次错误 */
LW_SEL_WAKEUPLIST SOCK_selwulist;
} SOCKET_T;
3.1 socket():创建套接字
调用 socket(AF_UNIX, type, 0) 时:
- 分配 af_unix_t 结构,并初始化为未绑定状态
- 创建对应的 socket_t 对象,返回其文件描述符(fd)
- 根据 type 设置套接字类型:
- SOCK_STREAM:面向连接、可靠、有序(由内核模拟 TCP 语义)
- SOCK_DGRAM:无连接、基于消息、不可靠(类似 UDP)
注意:此时套接字尚未绑定地址,也无法通信
3.2 bind():绑定地址
bind(s, (struct sockaddr_un*)&addr, addrlen);
/*********************************************************************************************************
Definitions for UNIX IPC domain
*********************************************************************************************************/
struct sockaddr_un {
uint8_t sun_len; /* sockaddr len including null */
uint8_t sun_family; /* AF_UNIX */
char sun_path[104]; /* path name (gag) */
};
执行过程:
- 解析 addr.sun_path 作为该套接字的唯一标识(路径名或抽象名)
- 将当前 af_unix_t 实例注册到全局哈希表 _G_plineAfUnixPath 中,以 sun_path 为键
- 记录绑定状态,后续可通过该路径被其他任务寻址
此步骤仅适用于服务端(或需显式命名的客户端)
3.3 listen():进入监听状态
LW_API int listen(int s, int backlog);
作用:
- 将 af_unix_t 的状态置为
__AF_UNIX_STATUS_LISTEN - 初始化连接请求队列(UNIX_pringConnect),容量由 backlog 限制
此后,该套接字可接收来自客户端的连接请求。
3.4 accept():接受连接(服务端)
LW_API int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
在 UNIX domain socket 中,accept() 实际涉及 三类 socket 对象:
| 角色 | 内核/实现层常见称呼 |
|---|---|
| 监听 | Listening socket(listen_unix) |
| 主动发起连接的 socket | Connecting socket / Client socket(connecting_unix) |
| accept 创建的 socket | Accepted socket / Connected socket(accepted_unix) |
当服务端调用 accept() 时,系统根据监听套接字的状态分两种情况处理:
1️⃣ 无待处理连接的情况
如果监听 socket(listen_unix)的等待连接队列为空,即当前不存在处于 connect() 阻塞或已完成握手但尚未被 accept 的客户端连接请求,则:
- 当前线程调用
API_SemaphoreBPend进入阻塞态 - 直到有新的连接请求被放入等待队列,或发生异常唤醒
2️⃣ 存在待处理连接的情况
若监听 socket 的等待连接队列非空,则 accept() 立即返回,并完成如下操作:
a. 取出等待连接节点
- 从监听套接字的 UNIX_pringConnect 队列中取出一个客户端连接请求(记为 connecting_unix)
b. 创建服务端连接 socket
- 分配新的 af_unix_t(记为 accepted_unix),继承监听套接字的类型与属性。
c. 建立双向关联关系(关键步骤)
- 通过 UNIX_pafunxPeer 字段,将:
connecting_unix->UNIX_pafunxPeer = accepted_unix;
accepted_unix->UNIX_pafunxPeer = connecting_unix;
📌 该双向引用标志着一条唯一的 UNIX domain socket 通信通道正式建立
d. 更新连接状态
- 双方状态均设为
__AF_UNIX_STATUS_ESTAB(已建立)。
e. 唤醒客户端连接线程
- 唤醒因 connect() 阻塞在 connecting_unix 上的客户端线程,使其 connect() 调用返回成功
f. 通知可写事件
- 唤醒所有阻塞在 connecting_unix 上、等待写事件的 select / poll 任务,表示该 socket 已具备可写能力
g. 创建用户态 fd
- 为 accepted_unix 分配新的 socket_t,返回其 fd 给应用层。服务端后续通过此 fd 与客户端通信。
关键点:对于面向连接的 socket 来说,每次 accept() 都会创建一对新的 af_unix_t 实例,实现“监听套接字 ≠ 通信套接字”的经典模型。
typedef struct af_unix_t {
......
struct af_unix_t *UNIX_pafunxPeer; /* 连接的远程节点 */
......
LW_LIST_RING UNIX_ringConnect; /* 连接队列 */
LW_LIST_RING_HEADER UNIX_pringConnect; /* 等待连接的队列 */
......
}
3.5 connect():发起连接(客户端)
LW_API int connect(int s, const struct sockaddr *name, socklen_t namelen);
流程如下:
- 根据 addr.sun_path 在全局表 _G_plineAfUnixPath 中查找对应的监听套接字(listen_unix);
- 若目标不存在或非监听状态,返回错误;
- 根据套接字类型处理:
- SOCK_DGRAM:直接将本端 af_unix_t 与 listen_unix 绑定(仅限首次连接);
- SOCK_STREAM:将本端 af_unix_t(connecting_unix)加入 listen_unix 的连接请求队列;
- 若为流式套接字,客户端线程阻塞,等待服务端 accept() 唤醒;
- 唤醒后,connecting_unix 与 accepted_unix 已建立 peer 关系,可开始通信。
4、数据通信
一旦连接建立(ESTAB 状态),双方即可通过 send()/recv() 交换数据。以下以客户端发送、服务端接收为例说明。
4.1 send():发送数据
LW_API ssize_t send(int s, const void *data, size_t size, int flags);
执行逻辑:
- 通过 af_unix_t->UNIX_pafunxPeer 找到对端的 af_unix_t
- 将数据拷贝至对端的接收队列 UNIX_unixq
- 若队列满(超过 UNIX_stMaxBufSize),发送方阻塞
- 若写入成功,尝试唤醒因 recv() 阻塞的对端线程
数据传输本质是内核内存中的拷贝,无网络协议开销
typedef struct af_unix_t {
......
AF_UNIX_Q UNIX_unixq; /* 接收数据队列 */
size_t UNIX_stMaxBufSize; /* 最大接收缓冲大小 */
...... /* 超过此数量写入时, 需要阻塞 */
}
4.2 recv():接收数据
LW_API ssize_t recv(int s, void *mem, size_t len, int flags);
执行逻辑:
- 检查本端 af_unix_t 的 UNIX_unixq 是否有数据
- 若有,出队并返回
- 若无,调用
API_SemaphoreBPend阻塞,直到有数据到达或连接关闭
5、关键设计总结
-
地址即索引
Unix Socket 的 sun_path(虽然根文件系统会创建该文件),但本质上来说,sun_path 只是一个全局唯一的命名标识,用于在内核中定位目标套接字。这解决了“无 IP 地址如何寻址”的问题。 -
连接模型分离
监听套接字(listening)与通信套接字(accepted / connected)严格分离。每次 accept() 都生成一对新的 af_unix_t 实例,确保并发连接互不干扰。
-
通信基于内存拷贝
数据通过 af_unix_t 之间的接收队列传递,高效安全。 -
状态驱动行为
套接字的状态(未绑定、监听、已建立等)决定了其可执行的操作,是整个协议逻辑的核心。
更多推荐


所有评论(0)