编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

UNIX域套接字(UDS) 原理及比较

wxchong 2025-03-11 22:10:16 开源技术 38 ℃ 0 评论

什么是UDS

Unix domain socket 又叫 IPC(inter-process communication 进程间通信) socket,用于实现同一主机上的进程间通信。socket 原本是为网络通讯设计的,但后来在 socket 的框架上发展出一种 IPC 机制,就是 UNIX domain socket。虽然网络 socket 也可用于同一台主机的进程间通讯(通过 loopback 地址 127.0.0.1),但是 UNIX domain socket 用于 IPC 更有效率:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。这是因为,IPC 机制本质上是可靠的通讯,而网络协议是为不可靠的通讯设计的。

UNIX domain socket 是全双工的,API 接口语义丰富,相比其它 IPC 机制有明显的优越性,目前已成为使用最广泛的 IPC 机制,比如 X Window 服务器和 GUI 程序之间就是通过 UNIX domain socket 通讯的。

IP socket

IP socket要利用主机的传输层(tcp),可以用于同一台主机上不同进程间的通信,也可以用于网络上不同主机间的通信。

Unix domain socket vs IP socket

先来看一个使用案例,配置php-fpm与Nginx交互的socket:

fastcgi_pass 127.0.0.1:9000
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock

这个案例中,运行在同一台机器上的php和Nginx需要通信,有2种实现方式:第一种是ip socket,通过本机回环地址127.0.0.1加端口实现;第二种是通过unix domain socket实现。哪一种效率更高呢?

基于localhost的ip socket需要实现跨网络主机通讯的全部环节,包括建立socket连接,ACk开销,tcp流控,封装/解封,路由。在这个过程中还会有2个context switch,因为使用网络层传输数据需要调用system call,而调用system call会产生中断,导致context switch的;另外一个进程接受到来自网络层的连接请求,也会产生系统中断,导致context switch。以上过程导致2个context switch的开销,外加其它各种开销(overhead)。

unix域的数据报服务是否可靠

man unix 手册即可看到,unix domain socket 的数据报既不会丢失也不会乱序 (据我所知,在Linux下的确是这样)。不过最新版本的内核,仍然又提供了一个保证次序的类型 “ kernel 2.6.4 SOCK_SEQPACKET ”。

STREAM 和 DGRAM 的主要区别

既然数据报不丢失也可靠,那不是和 STREAM 很类似么?我理解也确实是这样,而且我觉得 DGRAM 相对还要好一些,因为发送的数据可以带边界。二者另外的区别在于收发时的数据量不一样,基于 STREAM 的套接字,send 可以传入超过 SO_SNDBUF 长的数据,recv 时同 TCP 类似会存在数据粘连。

采用阻塞方式使用API,在unix domain socket 下调用 sendto 时,如果缓冲队列已满,会阻塞。而UDP因为不是可靠的,无法感知对端的情况,即使对端没有及时收取数据,基本上sendto都能立即返回成功(如果发端疯狂sendto就另当别论,因为过快地调用sendto在慢速网络的环境下,可能撑爆套接字的缓冲区,导致sendto阻塞)。

SO_SNDBUF 和 SO_REVBUF

对于 unix domain socket,设置 SO_SNDBUF 会影响 sendto 最大的报文长度,但是任何针对 SO_RCVBUF 的设置都是无效的 。实际上 unix domain socket 的数据报还是得将数据放入内核所申请的内存块里面,再由另一个进程通过 recvfrom 从内核读取,因此具体可以发送的数据报长度受限于内核的 slab 策略 。在 linux 平台下,早先版本(如 2.6.2)可发送最大数据报长度约为 128 k ,新版本的内核支持更大的长度。

使用 DGRAM 时,缓冲队列的长度

有几个因素会影响缓冲队列的长度,一个是上面提到的 slab 策略,另一个则是系统的内核参数
/proc/sys/net/unix/max_dgram_qlen。缓冲队列长度是这二者共同决定的。

如 max_dgram_qlen 默认为 10,在数据报较小时(如1k),先挂起接收数据的进程后,仍可以 sendto 10 次并顺利返回;

但是如果数据报较大(如120k)时,就要看 slab “size-131072” 的 limit 了。

使用 unix domain socket 进行进程间通信 vs 其他方式

· 需要先确定操作系统类型,以及其所对应的最大 DGRAM 长度,如果有需要传送超过该长度的数据报,建议拆分成几个发送,接收后组装即可(不会乱序,个人觉得这样做比用 STREAM 再切包方便得多)

· 同管道相比,unix 域的数据报不但可以维持数据的边界,还不会碰到在写入管道时的原子性问题。

· 同共享内存相比,不能独立于进程缓存大量数据,但是却避免了同步互斥的考量。

· 同普通 socket 相比,开销相对较小(不用计算报头),DGRAM 的报文长度可以大于 64k,不过不能像普通 socket 那样将进程切换到不同机器 。

UNIX域套接字使用文件系统作为地址名称空间。这意味着您可以使用UNIX文件权限来控制通信访问和他们在一起。即则可以限制其他进程可以连接到守护进程——可能一个用户可以,但是web服务器不能,或者类似的情况。有了IP套接字,连接守护进程的能力就暴露出来了目前的系统,所以可能需要采取额外的步骤安全。另一方面,网络透明。与UNIX域套接字,实际上可以检索进程的凭据创建了远程套接字,并将其用于访问控制,这在多用户系统上非常方便。-本地主机上的IP套接字基本上是在回环网络IP。故意“没有特殊知识”的事实是连接到相同的系统,所以不做任何努力来绕过基于性能原因的常规IP堆栈机制。例如,TCP上的传输总是涉及两个上下文切换远程插座,因为你必须通过netisr切换发生在通过合成的包的“环回”之后环回接口。同样地,你会得到所有的ack开销,TCP流量控制,封装/封装等路由执行,以决定包是否发送到本地主机。大的发送必须被分解成mtu大小的数据报还增加了大型写操作的开销。它实际上是TCP,它只是去一个环回接口,通过一个特殊的地址,或发现请求的地址在本地提供,而不是通过以太网提供(等)。- UNIX域套接字有明确的知识,他们正在执行同样的系统。它们避免了额外的上下文切换和发送线程将直接写入流或数据进入接收套接字缓冲区。没有计算校验和,没有插入标头,不执行路由,等等,因为它们有访问远程socket缓冲区,也可以直接提供在填写时反馈给发件人,或者更重要的是,清空,而不是增加显式的开销确认和窗口更改。一个功能UNIX域套接字不提供TCP提供的是带外数据。在实践,这是一个几乎无人关注的问题。一般来说,在TCP上实现的理由是它给你位置独立性和即时可移植性——您可以移动客户机或者这个守护进程,更新一个地址,它就会“正常工作”。套接字层提供了通信服务的合理抽象,所以编写一个连接/绑定的应用程序并不难部分了解TCP和UNIX域套接字,其余的只是使用它给出的套接字。所以如果你想在局部寻找性能,我认为UNIX域套接字可能最能满足您的需要。许多人因为性能通常不那么重要,所以无论如何都要编码到TCP吗网络可移植性的好处是巨大的。现在,UNIX域套接字代码被一个子系统锁覆盖;我有一个版本使用了更细粒度的锁定,但是还没有评估这些更改对性能的影响。你跑进来了在一个有四个处理器的SMP环境中,这些变化是可能的可能会对性能产生积极的影响,所以如果您喜欢这些补丁。现在他们在我的时间表上开始测试,但不是在包含在FreeBSD 5.4中的路径。更大的主要好处粒度应该是如果您有许多对线程/进程使用UNIX域套接字在处理器之间进行通信在UNIX域套接字子系统锁上存在大量争用。补丁不会增加正常的发送/接收操作的成本,但是在listen/accept/connect/bind路径中添加额外的互斥操作。

API操作

函数介绍

开始创建socket

 int socket(int domain, int type, int protocol)

domain(域) : AF_UNIX

type : SOCK_STREAM/ SOCK_DGRAM :

protocol : 0

SOCK_STREAM(流) : 提供有序,可靠的双向连接字节流。 可以支持带外数据传输机制,

无论多大的数据都不会截断

SOCK_DGRAM(数据报):支持数据报(固定最大长度的无连接,不可靠的消息),数据报超过最大长度,会被截断.

获取到socket文件描述符之后,还要将其绑定一个文件上

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd : 传入sock的文件描述符

addr : 用sockaddr_un表示

addrlen : 结构体长度

struct sockaddr_un {
 sa_family_t sun_family; /* AF_UNIX */
 char sun_path[UNIX_PATH_MAX]; /* pathname */
};

监听客户端的连接

int listen(int sockfd, int backlog);

sockfd : 文件描述符

backlog : 连接队列的长度

接受客户端的连接

int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);

UDS不存在客户端地址的问题,因此这里的addr和addrlen参数可以设置为NULL

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表