若是咱们的目的仅是在同一台主机上的不一样进程之间进行通讯,那么除了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
前面提到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域协议的使用方式与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函数能够建立两个链接起来的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时,套接字函数中存在一些差别和限制,具体列举以下:
umask和chmod中的权限配合使用,是权限的“补码”。好比在个人电脑上umask的值是022,因此uds建立出来的路径权限为777-022=755,表示全部者可读可写可执行,组用户和其余用户可读可写。而一般新建立的目录默认的权限为0777,新建立的文件默认的权限为0666.
POSIX声称使用相对路径绑定到uds将致使不可预计的结果
当咱们须要在本机通讯时,可使用uds来代替本地回环接口。uds相比TCP/UDP套接字性能会更好,由于它不须要通过网络协议栈,省去了各类解析和应答等步骤,而是直接在内核拷贝传递数据。好比最近很热的service mesh,业务进程和sidecar就能够经过uds来通讯。
当咱们须要传递描述符时,一般可使用方法有:
第一种方式里,咱们能够把描述符从父进程传递到子进程,然而咱们也可能须要在子进程传递描述符到父进程。unix系统提供了用于从一个进程向其余任意进程传递描述符的方式,而这两个进程不须要有任何亲缘关系。这种技术要求在两个进程之间建立一个uds,而后使用sendmsg经过这个uds发送特殊结构的消息。这个特殊的消息会由内核处理,把打开的描述符从发送进程传递到接收进程。
经过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是客户端和服务端在同一台主机上的IPC方法之一,与其余IPC方法(pipe,共享内存等)相比,uds的优点在于其使用的API几乎等同于网络通信中使用的API,与客户端和服务端在同一台主机上的TCP相比,unix域字节流套接字的性能要更优。
此外,uds还支持传递其余辅助数据,好比描述符和用户凭证。
java中并不支持直接使用uds,多是由于java标榜跨平台,而uds则只在部分操做系统中才能使用。要在java中使用uds,一般须要使用第三方提供的类库,好比著名的网络通信组件netty就提供了uds通信的支持。