欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Unix 域套接字概述

程序员文章站 2022-03-12 11:27:30
...
        Unix 域协议并不是一个实际的协议族,而是在单个主机上执行客户/服务器通信的一种方法,所用的 API 就是在不同主机上执行客户/服务器通信所用的套接字 API,可视为进程间通信(IPC)方法之一(POSIX 也把 Unix 域协议称为“本地 IPC”)。
        Unix 域提供了两类套接字:字节流套接字(类似 TCP)和数据报套接字(类似 UDP)。尽管也提供原始套接字,不过不曾见过使用。使用 Unix 域套接字有以下 3 个理由。
        (1)Unix 域套接字往往比位于同一个主机的 TCP 套接字快一倍。X Window System 就发挥了这个优势。当一个 X11 客户启动并打开到 X11 服务器的连接时,该客户会检查 DISPLAY 环境变量的值,其中指定了服务器的主机名、窗口和屏幕。如果服务器与客户处于同一个主机,客户就打开一个到服务器的 Unix 域字节流连接,否则打开一个 TCP 连接。
        (2)Unix 域套接字可用于同一个主机上的不同进程之间传递描述符。
        (3)Unix 域套接字较新的实现把客户的凭证(用户 ID 和组 ID)提供给服务器,从而能够提供额外的安全检查措施。
        Unix 域中用于标识客户和服务器的协议地址是普通文件系统中的路径名,不过这些路径名不是普通的 Unix 文件:除非把它们关联到 Unix 域套接字,否则无法读写这些文件。
        Unix 域套接字地址结构如下。
#include <sys/un.h>

struct sockaddr_un{
    sa_family_t  sun_family;      // AF_LOCAL
    char         sun_path[104];   // null-terminated pathname
};

        其中要注意的是,存放在 sun_path 数组中的路径名必须以空字符结尾。此外,因历史原因,虽然这里使用 104 来指明了 sun_path 的大小,但 POSIX 规范并没有明确规定 sun_path 的长度,因此应用程序应该在运行时使用 sizeof 运算符来计算 sockaddr_un 结构的大小。实现提供的 SUN_LEN 宏以一个指向 sockaddr_un 结构的指针为参数,并返回该结构的长度,其中包括路径名中的非空字节数。未指定地址通过以空字符串作为路径名指示,即 sun_path[0] 值为 0 的地址结构,它等价于 IPv4 的 INADDR_ANY 常值以及 IPv6 的 IN6ADDR_ANY_INIT 常值。
        下面这个示例创建一个 Unix 域套接字,往其上 bind 一个路径名,再调用 getsockname 输出这个绑定的路径名。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <sys/un.h>
#include <sys/socket.h>

typedef struct sockaddr	SA;

int main(int argc, char **argv){
	if(argc != 2){
		printf("Usage: unixBind <pathname>\n");
		exit(2);
	}
	unlink(argv[1]);		// OK if this fails

	struct sockaddr_un	addr1, addr2;
	bzero(&addr1, sizeof(addr1));
	addr1.sun_family = AF_LOCAL;
	strncpy(addr1.sun_path, argv[1], sizeof(addr1.sun_path)-1);
	int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
	bind(sockfd, (SA *)&addr1, SUN_LEN(&addr1));

	socklen_t len = sizeof(addr2);
	getsockname(sockfd, (SA *)&addr2, &len);
	printf("bound name = %s, returned len = %d\n", addr2.sun_path, len);
	
	exit(0);
}

        程序运行结果如下:
$ umask
0022
$ ./unixBind /tmp/moose
bound name = /tmp/moose, returned len = 13
$ 
$ ./unixBind /tmp/moose                         # 再运行一次
bound name = /tmp/moose, returned len = 13
$ 
$ ls -l /tmp/moose
srwxr-xr-x. 1 lei root 0 3月  12 00:37 /tmp/moose
$ 
$ ls -lF /tmp/moose
srwxr-xr-x. 1 lei root 0 3月  12 00:37 /tmp/moose=
$ 

        创建的 Unix 域套接字的文件类型显示为“s”(有些系统把 Unix 域套接字视为 FIFO,从而显示为 p)。
        为了方便,Unix 域套接字提供了 socketpair 函数,它可以创建两个随后连接起来的套接字。
#include <sys/socket.h>
int socketpair(int family, int type, int protocol, int sockfds[2]);
                                    /* 返回值:若成功则为 0,否则为 -1 */

        这里,family 参数必须为 AF_LOCAL,protocol 参数必须为 0。type 参数可以是 SOCK_STREAM,也可以是 SOCK_DGRAM。sockfds 数组里就是保存的新创建的两个套接字描述符。
        这样创建的两个套接字不曾命名,也就是说其中没有设计隐式的 bind 调用。指定 type 参数为 SOCK_STREAM 得到的结果称为流管道,它与调用 pipe 创建的普通 Unix 管道类似,差别在于流管道是全双工的,即两个描述符都是既可读又可写的,而 POSIX 并不要求 pipe 返回两个全双工的描述符。
        虽然 Unix 域套接字用的也是套接字 API 函数,不过其中也还是存在如下一些差异和限制。
        (1)由 bind 创建的路径名默认访问权限应为 0777,并按照当前的 umask 值进行修正。
        (2)与 Unix 域套接字关联的路径名应该是一个绝对路径名,因为使用相当路径的话,对它的解析依赖于调用者的当前工作目录。
        (3)在 connect 调用中指定的路径名必须是一个当前已经绑定在某个打开的 Unix 域套接字上的路径名,而且它们的套接字类型(字节流或数据报)也必须一致(即 Unix 域字节流套接字不能连接到与 Unix 域数据报套接字关联的路径名,反之亦然)。
        (4)调用 connect 连接一个 Unix 域套接字涉及的权限测试等同于调用 open 以只写方式访问相应的路径名。
        (5)Unix 域字节流套接字类似于 TCP 套接字:它们都为进程提供一个无记录边界的字节流接口。
        (6)如果对于某个 Unix 域字节流套接字的 connect 调用发现该监听套接字的队列已满,调用就立即返回一个 ECONNREFUSED 错误。这一点不同于 TCP:如果 TCP 监听套接字的队列已满,TCP 监听端就忽略新到达的 SYN,而 TCP 连接发起端将数次发送 SYN 进行重试。
        (7)Unix 域数据报套接字类似于 UDP 套接字:它们都提供一个保留记录边界的不可靠的数据报服务。
        (8)在一个未绑定的 Unix 域套接字上发送数据报不会自动给这个套接字捆绑一个路径名,这一点不同于 UDP 套接字:在一个未绑定的 UDP 套接字上发送 UDP 数据报导致给这个套接字捆绑一个临时端口。这意味着除非数据报发送端已经捆绑一个路径名到它的套接字,否则数据报接收端无法发回应答数据报。类似地,对于某个 Unix 域数据报套接字的 connect 调用不会给本套接字捆绑一个路径名,这一点不同于 TCP 和 UDP。