Unix域协议

  1、Unix域协议并不是一个实际的协议族,它只是在同一台主机上进行客户-服务器通信时,使用与在不同主机上的客户和服务器间通信时相同的API(套接口或XTI)的一种方法。当客户和服务器在同一台主机上时,Unix域协议是IPC通信方式的一种替代品。

  Unix域提供了两种类型的套接口:字节流套接口(与TCP类似)和数据报套接口(与UDP类似)。尽管还提供了原始套接口,但比较少见/用。

  使用Unix域套接口的一个例子是MySQL:netstat -alnp | more结果显示mysqld.sock是Unix域字节流套接口(下图最后一行)。执行service mysql stop之后,就看不到mysqld.sock这一行,而且登录MySQL时会提示Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)。

  

  登录MySQL之后,再执行netstat -alnp |grep unix|grep mysql,观察下面各行。

  

  使用Unix域套接口有三个原因:

  1)在源自Berkeley的实现中,当通信双方位于同一台主机上时,Unix域套接口的速度通常是TCP套接口的两倍。

  2)Unix域套接口可以用来在同一台主机上的各个进程之间传递描述字。

  3)Unix域套接口的较新实现中可以向服务器提供客户的凭证(用户ID和组ID),这能提供附加的安全检查。

  Unix域用来标识客户和服务器的协议地址是在通常的文件系统里的路径名。

  2、Unix域套接口地址结构(定义可参考man 7 unix或/usr/include/sys/un.h)

struct sockaddr_un
{
    uint8_t sun_len;           // 有些实现中可能没有这个成员
    sa_family_t sun_family;    // AF_LOCAL
    char sun_path[104];        // null-terminated pathname. 有些实现中该值可能为108
};

  un.h中定义了SUN_LEN宏,它以一个指向sockaddr_un结构的指针为参数,返回该结构的实际长度:包括sun_path前面成员占用的空间和sun_path的非空字节数。sun_path[0]为0表示未指定地址,这与IPv4的INADDR_ANY或IPv6的IN6ADDR_ANY_INIT类似。

  Posix.1g将Unix域协议改名为“本地IPC”,以消除对Unix操作系统的依赖。原来的常值AF_UNIX变成AF_LOCAL。

  3、socketpair函数:建立一对相互连接的套接口。该函数仅适用于Unix域套接口(即family必须为AF_LOCAL,protocol必须为0,type可以是SOCK_STREAM或SOCK_DGRAM。新创建的两个套接口描述字作为sockfd[0]和sockfd[1]返回)。以SOCK_STREAM作为type调用socketpair所得到的结果称作流管道。这和一般的Unix管道(由pipe函数生成)类似,但流管道是全双工的。

int socketpair(int family, int type, int protocol, int sockfd[2]);

  创建的两个套接口是没有名字的,即没有涉及隐式bind。

  4、套接口函数:当用于Unix域套接口时,套接口函数有一些差别和限制。下面列出Posix.1g对此的一些要求,需要注意的是并不是所有的实现中都达到了这些要求。

  1)bind建立的路径名的缺省访问权限应为0777,并被当前的umask值修改。

  2)与Unix域套接口相关联的路径名应为一个绝对路径名,而不是相对路径名。

  3)connect使用的路径名必须是一个绑定在某个已打开的Unix域套接口上的路径名,而且套接口的类型(字节流或数据报)也必须一致。

  4)用connect连接Unix域套接口时的权限检查和用open以只写方式访问路径名时完全相同。

  5)Unix域字节流套接口和TCP套接口类似:它们都为进程提供一个没有记录边界的字节流接口。

  6)如果Unix域字节流套接口的connect调用发现监听套接口的队列已满,会立刻返回一个ECONNREFUSED错误。这和TCP有所不同:如果监听套接口的队列已满,它将忽略到来的SYN,TCP连接的发起方会接着发送几次SYN重试。

  7)Unix域数据报套接口和UDP套接口类似:它们都提供一个保留记录边界的不可靠的数据报服务。

  8)与UDP套接口不同的是,在未绑定的Unix域套接口上发送数据报不会给它捆绑一个路径名(UDP会绑定临时端口)。这意味着,数据报的发送者除非绑定一个路径名,否则接收者无法发回应答数据报。同样,与TCP和UDP不同的是,给Unix域数据报套接口调用connect不会捆绑一个路径名。

  5、描述字传递

  当考虑从一个进程向另一个进程传递打开的描述字时,我们通常会想到:在fork调用后,子进程共享父进程的所有打开的描述字(随后父进程可能关闭描述字)。子进程调用exec后所有描述字也会保持打开。但如果想让子进程打开一个描述字并将其传给父进程,怎么做呢?

  Unix域套接口可以在任何进程间传递打开的描述字,它使用sendmsg/recvmsg来发送/接收一个特殊的消息。这个消息由内核做特殊处理,以将打开的描述字从发送方传递到接收方。

  在两个进程间传递描述字的步骤如下:

  1)创建一个(字节流或数据报)Unix域套接口。

  如果目标是fork一个子进程,让子进程打开描述字并将它传回给父进程,那么父进程可以用socketpair创建一个流管道,用它来传递描述字。

  如果进程之间没有亲缘关系,那么服务器必须创建一个Unix域套接口,bind一个路径名,让客户connect到这个套接口。然后客户可以向服务器发送一个请求以打开某个描述字,服务器将描述字通过Unix域套接口传回。

  2)发送进程可以用任何返回描述字的Unix函数打开一个描述字(如open、socket等)。描述字可以是任何类型的。

  3)发送进程建立一个msghdr结构(见/usr/include/bits/socket.h),其中包含要传递的描述字。Posix.1g中说明该描述字作为辅助数据(msghdr结构的msg_control成员)发送,但老的实现使用msg_accrights成员。发送进程调用sendmsg通过第一步得到的Unix域套接口发出描述字。这时该描述字是“在飞行中”的:即使在发送进程调用sendmsg之后,但在接收进程调用recvmsg之前将描述字关闭,它仍会为接收进程保持打开状态。描述字的发送导致它的访问计数加1。

  4)接收进程调用recvmsg在Unix域套接口上接收描述字。通过接收进程收到的描述字的编号和发送进程中的描述字的编号不同:传递描述字不是传递描述字的编号,而是在接收进程中创建一个新的描述字,指向内核的文件表中与发送进程发送的描述字相同的项。

  客户和服务器之间必须有某种应用协议,使接收方知道何时接收描述字。如果接收方调用recvmsg但没有分配接收描述字的空间,而且有一个描述字已被传递并正待读出,这个已传递的描述字就会被关闭。在用recvmsg接收描述字时还要避免使用MSG_PEEK标志,否则后果不可预料。

  参考资料

  《Unix网络编程(卷1):套接字联网API》

不断学习中。。。

原文地址:https://www.cnblogs.com/hanerfan/p/4284890.html