Unix Domain Sockets
UDS(Unix Domain Sockets)在 Linux 上表现为一个文件,相比较于普通 socket 监听在端口上,一个进程也可以监听在一个 UDS 文件上,比如 /tmp/xjjdog.sock
由于通过这个文件进行数据传输,并不需要走网卡等物理设备,所以通过 UDS 传输数据,速度是非常快的
UDS 主要用于同一主机上的进程间通信
Socket 网络通信原理
之前已经分析过了 Socket 的网络通信原理与内核对协议栈的处理
大致的流程如下:
- 驱动通知网卡有一个新的描述符(有一个即将网络包到达网卡)
- 网卡从
RX ring buffer
中取出描述符,从而获知缓冲区的地址和大小 - 网卡收到新的数据包
- 网卡将新数据包直接通过
DMA
写到内核堆中sk_buffer
结构体里,并使用中断来通知内核RX ring buffer
:网络栈接收数据环形缓存区DMA
:Direct Memory Access 直接存储器访问,外部设备不通过CPU而直接与系统内存交换数据的接口技术
- 内核调用
netif_receive_skb
来处理sk_buffer
中的数据包 - 对于网络协议,内核会遍历协议容器
ptype_base
中的所有协议packet_type
,匹配成功后调用packet_type->func
完成相应的处理 - 最后把处理的结果输出到各个目标进程中
Socket 本地通信原理
Unix Domain Sockets 也使用了差不多的原理,但差别如下:
- UDS 不需要 IP:Port,而是通过一个文件名来表示
- 使用 UDS 时,
socket
函数的domain
参数固定为 AF_UNIX - UDS 中使用
sockaddr_un
来表示sockaddr
结构体
剩下的操作就和 socket
接口的那一套东西一样了
Msg 消息队列
之前也分析过了信息队列的底层实现
消息队列,是消息的链接表,存放在内核中,一个消息队列由一个标识符(即ID)来标识
- 消息队列的标识符 key 键,它的基本类型是 key_t,使用
ftok
函数可以生成一个 key_t - 两个无关的进程,可以通过唯一标识符 key 来找到对应的 msg
msgget
会在内核堆空间中创建一个 msg_queue
结构体,用于表示一个消息队列的头
msgsnd
和 msgrcv
都依靠 msg_msg
结构体,用于表示在这个消息队列中的各个消息主体
这两者通过链表相关联:
1 | msg_queue -> msg_msg -> msg_msg -> msg_msg ... |
但现在我又了解到了信息队列的一个特殊用途:用于传递 Socket 信息
FD 迁移技术
FD 迁移技术可以把一个进程所挂载的连接(socket),转移到另外一个进程之上
主要是通过如下两个系统调用:
1 | ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags); |
- 这个两个函数都需要传入一个
sockfd
,并依赖msghdr
结构体来传递信息:
1 | struct msghdr |
- 其中的
msg_control
条目有需要指向cmsghdr
结构体:
1 | struct cmsghdr |
- 成员变量
cmsg_type
有3个类型:SCM_RIGHTS
SCM_CREDENTIALS
SCM_SECURITY
- 其中,
SCM_RIGHTS
就是我们所需要的,它允许我们从一个进程,发送一个文件句柄到另外一个进程
因此,依靠 sendmsg
和 recvmsg
两个系统调用就可以实现 FD 句柄的传输
- 依靠
sendmsg
函数,将 FD 句柄发送到另外一个进程 - 依靠
recvmsg
函数,接收这部分数据,然后将其还原成cmsghdr
结构体,然后我们就可以从cmsg_data
中获取句柄列表 - 其实 FD 句柄在某个进程里只是一个引用,真正的 FD 句柄是放在内核中的,所谓的迁移,只不过是把一个指针,从一个进程中去掉,再加到另外一个进程中罢了
测试案例如下:
- 发送 FD 句柄:
1 |
|
- 接收 FD 句柄:
1 |
|
- 结果:
1 | ➜ exp ./send flag flag2 |
1 | ➜ exp ./read |
- 在
send
程序中打开的两个文件flag flag2
被迁移到了read
程序中