Unix域协议

简介

若是咱们的目的仅是在同一台主机上的不一样进程之间进行通讯,那么除了TCP/UDP套接字之外咱们还可使用Unix域协议。Unix域协议是IPC(进程间通讯)的方式之一,Unix域协议使用套接字API,支持同一台主机的不一样进程之间进行通讯。直观上来讲Unix域协议有点相似使用本地回环接口(lo)的TCP/UDP。可是Unix域协议比起TCP/UDP套接字还有几个其余优点:1.比起TCP协议一般要更快;2.支持在同一台主机上的不一样进程之间传递描述符;3.支持传递客户端凭证。java

使用Unix域协议的套接字(如下简称uds[unix domain socket])用到的API与TCP/UDP套接字API彻底一致,即服务端须要进行bind、listen、accpet等操做才能读写,客户端须要先connect才能进行读写。与TCP/UDP套接字不一样的一点是uds绑定的地址是一个文件系统的绝对路径,好比"/tmp/myuds",而TCP/UDP套接字使用的地址则包含了地址和端口号。uds使用的路径并非普通的文件,须要和uds关联才能对其进行读写。Unix域套接字有两种类型,字节流套接字(相似TCP)和数据报套接字(相似UDP)。linux

相关API

unix域协议的地址结构

前面提到uds并不使用地址加端口号做为协议地址,而是用一个文件路径来做为地址,因此uds使用的地址结构也有一点不一样:macos

struct sockaddr_un {
    sa_family_t     sun_family;     /* 协议族,一般为AF_UNIX或者AF_LOCAL */
    char            sun_path[104];  /* 地址路径 */
};
复制代码

uds使用的地址结构叫作sockaddr_un,后面的un即unix,而TCP/UDP套接字使用的地址结构叫作sockaddr_in,in表示internet。bash

unix域协议虽然名字里有unix,但它是POSIX的一部分,并不与unix系统强绑定,POSIX将unix域协议从新命名为“本地IPC”,把AF_UNIX改成了AF_LOCAL,但更多的时候咱们仍是称其为unix域协议,咱们常见的linux和macos都支持unix域协议。网络

unix域协议配合套接字API

unix域协议的使用方式与TCP/UDP套接字的方式相似,只须要将协议族替换为AF_LOCAL(或者AF_UNIX),而后将地址替换为sockaddr_un便可。下面是一个使用uds进行bind,而后经过getsockname获取套接字名称并打印的例子:数据结构

#include "unp.h"
int main(int argc, char **argv) {
    int                 sockfd;
    socklen_t           len;
    struct sockaddr_un addr1, addr2;

    if (argc != 2)
        err_quit("usage: unixbind <pathname>");

    //先调用socket建立套接字
    sockfd = Socket(AF_LOCAL, SOCK_STREAM, 0);
    //对已存在的路径进行bind会致使失败,因此预先调用unlink删除文件
    unlink(argv[1]);
    //调用bzero初始化地址结构体
    bzero(&addr1, sizeof(addr1));
    //设置协议族为AF_LOCAL,AF_UNIX也能够
    addr1.sun_family = AF_LOCAL;
    //设置地址的文件路径
    strncpy(addr1.sun_path, argv[1], sizeof(addr1.sun_path)-1);
    //调用bind,经过SUN_LEN计算bind所需的长度这个参数
    Bind(sockfd, (SA *) &addr1, SUN_LEN(&addr1));

    len = sizeof(addr2);
    //得到socket的名字
    Getsockname(sockfd, (SA *) &addr2, &len);
    printf("bound name = %s, returned len = %d\n", addr2.sun_path, len);
    exit(0);
}
复制代码

执行上面的程序,咱们就能够看到控制台会有相似这样的输出:dom

bound name = xxx, returned len = yy
复制代码

程序会输出咱们绑定的路径以及对应的socket长度,这时候也能够看到对应路径也自动建立了同名的文件。若是用ls -lF命令查看,能够看到对应的文件类型为socket。socket

socketpair

socketpair函数能够建立两个链接起来的unix域套接字:ide

#include <sys/socket.h>
int socketpair(int family, int type, int protocol, int sockfd[2]);
复制代码

socketpair的参数中family必须为AF_LOCAL,protocol必须为0,type能够为SOCK_STREAM或者SOCK_DGRAM,新建立的两个套接字描述符将做为sockfd[0]和sockfd[1]返回。函数

套接字函数

使用uds时,套接字函数中存在一些差别和限制,具体列举以下:

  • 有bind建立的路径名默认的权限为0777(全部者、组用户和其余用户均可读、可写、可执行),并按照当前umask进行修正。

umask和chmod中的权限配合使用,是权限的“补码”。好比在个人电脑上umask的值是022,因此uds建立出来的路径权限为777-022=755,表示全部者可读可写可执行,组用户和其余用户可读可写。而一般新建立的目录默认的权限为0777,新建立的文件默认的权限为0666.

  • uds绑定的路径应使用绝对路径。避免使用相对路径的缘由是相对路径的解析会依赖调用者的当前路径。

POSIX声称使用相对路径绑定到uds将致使不可预计的结果

  • 调用conenct时传入的路径必须是和一个已经打开的uds绑定的路径。而且两个套接字的type(数据报或者字节流)必须相同。出错的条件有几个:a)路径已存在,但不是一个uds;b)路径已存在且是一个uds,可是没有与之关联的打开的描述符;c)路径已存在而且是一个打开的uds,可是类型不一样。
  • 调用connect链接一个uds涉及的权限检查等同于调用open以只读模式访问对应路径。
  • unix域字节流套接字与TCP套接字相似,它们都提供无记录边界的字节流接口。
  • 若是对一个uds进行connect时发现监听套接字的队列已满,调用会当即返回一个ECONNECTREFUSED错误;而TCP监听套接字在队列满时则会忽略新到达的SYNC,进而链接发起端发起端进行重试。
  • unix域数据报套接字与UDP套接字相似,它们都提供保留记录边界的不可靠的数据报服务。
  • 在未绑定的uds上发送数据不会自动为其绑定一个路径,这一点不一样于UDP套接字:在一个未绑定的UDP套接字上发送数据会为其绑定一个临时端口。这意味着除非数据报发送端已经绑定到一个路径,不然数据报接收端没法发回应答数据报。相似的,对于uds的connect调用不会为其绑定一个路径,这一点不一样于TCP/UDP。

什么场景下能够选择uds

本机通讯

当咱们须要在本机通讯时,可使用uds来代替本地回环接口。uds相比TCP/UDP套接字性能会更好,由于它不须要通过网络协议栈,省去了各类解析和应答等步骤,而是直接在内核拷贝传递数据。好比最近很热的service mesh,业务进程和sidecar就能够经过uds来通讯。

传递描述符

当咱们须要传递描述符时,一般可使用方法有:

  1. fork调用返回之后,子进程共享父进程的全部描述符
  2. exec调用执行后,全部的描述符一般保持打开状态

第一种方式里,咱们能够把描述符从父进程传递到子进程,然而咱们也可能须要在子进程传递描述符到父进程。unix系统提供了用于从一个进程向其余任意进程传递描述符的方式,而这两个进程不须要有任何亲缘关系。这种技术要求在两个进程之间建立一个uds,而后使用sendmsg经过这个uds发送特殊结构的消息。这个特殊的消息会由内核处理,把打开的描述符从发送进程传递到接收进程。

经过uds传递描述符的步骤具体以下:

  1. 建立一个字节流或数据报的uds。这能够经过调用socketpair而后父子进程之间的链接;也可使用套接字API。一般建议使用字节流套接字而不是数据报套接字,由于使用数据报套接字并无什么好处,反而还存在数据报被丢弃的可能。
  2. 发送端打开描述符。uds能够传递各类类型的描述符,而不是仅包括文件描述符。
  3. 发送端进程建立一个msghdr的结构,其中含有待传递的描述符,而后调用sendmsg将其发送出去。发送一个描述符会使其引用计数加一。
  4. 接收端进程调用recvmsg在建立的uds上接收描述符。这个过程会在接收进程建立一个新的描述符,而后将其指向和发送进程发送的描述符指向的同一个内核文件选项。因此接收端收到的描述符不一样于发送端发送端描述符时很正常的。

msghdr的结构定义:

/* * [XSI] Message header for recvmsg and sendmsg calls. * Used value-result for recvmsg, value only for sendmsg. */
struct msghdr {
	void		*msg_name;	/* [XSI] optional address */
	socklen_t	msg_namelen;	/* [XSI] size of address */
	struct iovec *msg_iov;	/* [XSI] scatter/gather array */
	int		msg_iovlen;	/* [XSI] # elements in msg_iov */
	void		*msg_control;	/* [XSI] ancillary data, see below */
	socklen_t	msg_controllen;	/* [XSI] ancillary data buffer len */
	int		msg_flags;	/* [XSI] flags on received message */
};
复制代码

具体的例子就暂时不列举了。

验证发送者的身份

能够用uds传递的另外一种辅助数据就是用户凭证。用户凭证的数据结构在不一样的操做系统中并不一致,这里就再也不详细介绍了。

uds的优点

uds是客户端和服务端在同一台主机上的IPC方法之一,与其余IPC方法(pipe,共享内存等)相比,uds的优点在于其使用的API几乎等同于网络通信中使用的API,与客户端和服务端在同一台主机上的TCP相比,unix域字节流套接字的性能要更优。

此外,uds还支持传递其余辅助数据,好比描述符和用户凭证。

java中的uds

java中并不支持直接使用uds,多是由于java标榜跨平台,而uds则只在部分操做系统中才能使用。要在java中使用uds,一般须要使用第三方提供的类库,好比著名的网络通信组件netty就提供了uds通信的支持。

相关文章
相关标签/搜索