UNIX - Linux手册页
Linux程序员手册 第7部分
更新日期: 2020-06-09
名称
unix-用于本地进程间通信的套接字
语法
#包括
#包括
unix_socket =套接字(AF_UNIX,类型,0);
错误= socketpair(AF_UNIX,类型,0,int * sv);
说明
AF_UNIX(也称为AF_LOCAL)套接字系列用于在同一台计算机上的进程之间进行有效通信。传统上,UNIX域套接字可以不命名,也可以绑定到文件系统路径名(标记为套接字类型)。 Linux还支持独立于文件系统的抽象名称空间。
在UNIX域中,有效的套接字类型为:SOCK_STREAM,用于面向流的套接字; SOCK_DGRAM,用于保留消息边界的面向数据报的套接字(与大多数UNIX实现一样,UNIX域数据报套接字始终可靠,并且不对数据报进行重新排序); (从Linux 2.6.4开始)SOCK_SEQPACKET,用于面向连接的序列化数据包套接字,它保留消息边界并按消息发送的顺序传递消息。
UNIX域套接字支持使用辅助数据将文件描述符或进程凭证传递给其他进程。
Address format
UNIX域套接字地址以以下结构表示:
struct sockaddr_un { sa_family_t sun_family; /* AF_UNIX */ char sun_path[108]; /* Pathname */ };
sun_family字段始终包含AF_UNIX。在Linux上,sun_path的大小为108个字节;另请参见下面的注释。
各种系统调用(例如bind(2),connect(2)和sendto(2))都将sockaddr_un参数用作输入。其他一些系统调用(例如,getsockname(2),getpeername(2),recvfrom(2)和accept(2))返回此类型的参数。
sockaddr_un结构区分三种类型的地址:
- *
- 路径名:可以使用bind(2)将UNIX域套接字绑定到以空值结尾的文件系统路径名。当返回路径名套接字的地址时(通过上述系统调用之一),其长度为
- offsetof(struct sockaddr_un,sun_path)+ strlen(sun_path)+ 1
- sun_path包含以空值结尾的路径名。 (在Linux上,上面的offsetof()表达式与sizeof(sa_family_t)等于相同的值,但是其他一些实现在sun_path之前包括其他字段,因此offsetof()表达式更可移植地描述了地址结构的大小。)
- 有关路径名套接字的更多详细信息,请参见下文。
- *
- 未命名:尚未使用bind(2)绑定到路径名的流套接字没有名称。同样,socketpair(2)创建的两个套接字是未命名的。当返回未命名套接字的地址时,其长度为sizeof(sa_family_t),并且不应检查sun_path。
- *
- abstract:抽象套接字地址(与路径名套接字)的区别在于,sun_path [0]为空字节(aq \ 0aq)。此名称空间中套接字的地址由sun_path中的其他字节给定,这些字节由地址结构的指定长度覆盖。 (名称中的空字节没有特殊意义。)该名称与文件系统路径名没有任何关系。返回抽象套接字的地址时,返回的addrlen大于sizeof(sa_family_t)(即,大于2),并且套接字的名称包含在sun_path的前(addrlen-sizeof(sa_family_t))个字节中。
Pathname sockets
将套接字绑定到路径名时,应遵循一些规则以最大程度地提高可移植性并简化编码:
- *
- sun_path中的路径名应以空值结尾。
- *
- 路径名的长度(包括终止的空字节)不应超过sun_path的大小。
- *
- 描述封闭的sockaddr_un结构的addrlen参数的值至少应为:
offsetof(struct sockaddr_un, sun_path)+strlen(addr.sun_path)+1
- 或者,更简单地说,可以将addrlen指定为sizeof(struct sockaddr_un)。
在实现中如何处理不遵循上述规则的UNIX域套接字地址,存在一些差异。例如,如果所提供的sun_path中不存在空终止符,则某些(但不是全部)实现会附加空终止符。
对便携式应用程序进行编码时,请记住,某些实现的sun_path短至92字节。
各种系统调用(accept(2),recvfrom(2),getsockname(2),getpeername(2))返回套接字地址结构。当应用于UNIX域套接字时,应按上述方法初始化提供给调用的value-result addrlen参数。返回时,将参数设置为指示地址结构的实际大小。调用者应检查此参数中返回的值:如果输出值超过输入值,则不能保证sun_path中存在空终止符。 (请参阅错误。)
Pathname socket ownership and permissions
在Linux实现中,路径名套接字尊重其所在目录的权限。如果该进程对创建套接字的目录没有写和搜索(执行)权限,则创建新套接字失败。
在Linux上,连接到流套接字对象需要对该套接字具有写权限。向数据报套接字发送数据报同样需要对该套接字具有写权限。 POSIX不会对权限对套接字文件的影响做出任何声明,在某些系统(例如,较旧的BSD)上,套接字权限将被忽略。可移植程序不应依赖此功能来保证安全性。
创建新的套接字时,将根据通常的规则设置套接字文件的所有者和组。套接字文件已启用所有权限,但进程umask(2)关闭的权限除外。
可以更改路径名套接字的所有者,组和权限(使用chown(2)和chmod(2))。
Abstract sockets
套接字权限对抽象套接字没有意义:进程umask(2)在绑定抽象套接字时不起作用,并且更改对象的所有权和权限(通过fchown(2)和fchmod(2))对套接字无效。套接字的可访问性。
当关闭所有对套接字的打开的引用时,抽象套接字自动消失。
抽象套接字名称空间是不可移植的Linux扩展。
Socket options
由于历史原因,即使这些套接字选项是AF_UNIX特定的,也要使用SOL_SOCKET类型指定。通过将SOL_SOCKET指定为套接字系列,可以使用setsockopt(2)设置它们,并使用getsockopt(2)读取它们。
- SO_PASSCRED
- 启用此套接字选项将导致在每个后续接收到的消息中的SCM_CREDENTIALS辅助消息中接收发送过程的凭据。返回的凭证是由发送方使用SCM_CREDENTIALS指定的凭证,或者如果发送方未指定SCM_CREDENTIALS辅助数据,则默认值包括发送方的PID,真实用户ID和真实组ID。
- 设置此选项并且尚未连接套接字时,将自动生成抽象名称空间中的唯一名称。
- 作为setsockopt(2)的参数给出并作为getsockopt(2)的结果返回的值是一个整数布尔值标志。
- SO_PASSSEC
- 允许在SCM_SECURITY类型的辅助消息中接收对等套接字的SELinux安全标签(请参见下文)。
- 作为setsockopt(2)的参数给出并作为getsockopt(2)的结果返回的值是一个整数布尔值标志。
- 从Linux 2.6.18开始,UNIX域数据报套接字支持SO_PASSSEC选项。在Linux 4.2中添加了对UNIX域流套接字的支持。
- SO_PEEK_OFF
- 参见套接字(7)。
- SO_PEERCRED
- 此只读套接字选项返回连接到此套接字的对等进程的凭据。返回的凭证是在调用connect(2)或socketpair(2)时有效的凭证。
- getsockopt(2)的参数是指向ucred结构的指针;定义_GNU_SOURCE功能测试宏,以从中获取该结构的定义。
- 仅对于已连接的AF_UNIX流套接字以及使用socketpair(2)创建的AF_UNIX流和数据报套接字对,才可以使用此选项。
Autobind feature
如果bind(2)调用将addrlen指定为sizeof(sa_family_t),或者为未显式绑定到地址的套接字指定了SO_PASSCRED套接字选项,则该套接字将自动绑定到抽象地址。该地址由一个空字节组成,后跟字符集[0-9a-f]中的5个字节。因此,限制为2 ^ 20个自动绑定地址。 (从Linux 2.1.15开始,添加自动绑定功能时,使用了8个字节,因此限制为2 ^ 32个自动绑定地址。在Linux 2.3.15中更改为5个字节。)
Sockets API
以下各段描述了Linux上UNIX域套接字的套接字API的特定于域的详细信息和不受支持的功能。
UNIX域套接字不支持带外数据的传输(send(2)和recv(2)的MSG_OOB标志)。
UNIX域套接字不支持send(2)MSG_MORE标志。
在Linux 3.4之前,UNIX域套接字不支持在recv(2)的flags参数中使用MSG_TRUNC。
SO_SNDBUF套接字选项确实对UNIX域套接字有效,但是SO_RCVBUF选项无效。对于数据报套接字,SO_SNDBUF值对传出数据报的大小施加上限。此限制的计算方式是:将选项值加倍(请参阅socket(7)),再减去用于开销的32个字节。
Ancillary messages
使用sendmsg(2)和recvmsg(2)发送和接收辅助数据。由于历史原因,下面列出的辅助消息类型即使是AF_UNIX特定的,也要用SOL_SOCKET类型指定。要发送它们,请将结构cmsghdr的cmsg_level字段设置为SOL_SOCKET,并将cmsg_type字段设置为类型。有关更多信息,请参见cmsg(3)。
- SCM_RIGHTS
- 从另一个进程发送或接收一组打开的文件描述符。数据部分包含文件描述符的整数数组。
- 通常,此操作称为"将文件描述符传递给另一个进程"。但是,更准确地说,正在传递的是对打开文件描述的引用(请参见open(2)),并且在接收过程中,很可能会使用不同的文件描述符号。从语义上讲,此操作等效于将文件描述符复制(dup(2))到另一个进程的文件描述符表中。
- 如果用于接收包含文件描述符的辅助数据的缓冲区太小(或不存在),则辅助数据将被截断(或丢弃),并且多余的文件描述符会在接收过程中自动关闭。
- 如果在辅助数据中接收到的文件描述符的数量导致该过程超过其RLIMIT_NOFILE资源限制(请参阅getrlimit(2)),则多余的文件描述符会在接收过程中自动关闭。
- 内核常量SCM_MAX_FD定义了数组中文件描述符数量的限制。尝试发送大于此限制的数组会导致sendmsg(2)失败,并显示错误EINVAL。 SCM_MAX_FD的值为253(在2.6.38之前的内核中为255)。
- SCM_CREDENTIALS
- 发送或接收UNIX凭据。这可以用于身份验证。凭证作为结构辅助消息传递。此结构定义如下:
struct ucred { pid_t pid; /* Process ID of the sending process */ uid_t uid; /* User ID of the sending process */ gid_t gid; /* Group ID of the sending process */ };
- 从glibc 2.8开始,必须定义_GNU_SOURCE功能测试宏(在包含任何头文件之前),以获得该结构的定义。
- 发件人指定的凭据由内核检查。允许特权进程指定与其自身不匹配的值。发送方必须指定自己的进程ID(除非具有CAP_SYS_ADMIN功能,在这种情况下,可以指定任何现有进程的PID),其实际用户ID,有效用户ID或保存的set-user-ID(除非它具有CAP_SETUID)及其实际组ID,有效组ID或已保存的设置组ID(除非具有CAP_SETGID)。
- 要接收结构提示消息,必须在套接字上启用SO_PASSCRED选项。
- SCM_SECURITY
- 接收对等套接字的SELinux安全上下文(安全标签)。接收到的辅助数据是一个包含安全上下文的以空终止的字符串。接收者应在辅助消息的数据部分中至少为此数据分配NAME_MAX字节。
- 要接收安全上下文,必须在套接字上启用SO_PASSSEC选项(请参见上文)。
使用sendmsg(2)发送辅助数据时,发送的消息中可能仅包含上述每种类型的一项。
发送辅助数据时,至少应发送一个字节的实际数据。在Linux上,这是通过UNIX域流套接字成功发送辅助数据所必需的。通过UNIX域数据报套接字发送辅助数据时,在Linux上不必发送任何附带的实际数据。但是,当通过数据报套接字发送辅助数据时,便携式应用程序还应至少包含一个字节的实际数据。
从流套接字接收时,辅助数据对接收到的数据形成一种障碍。例如,假设发送方发送如下:
假设接收方现在执行recvmsg(2)调用,每个调用的缓冲区大小为20个字节。第一个调用将接收五个字节的数据,以及第二个sendmsg(2)调用发送的辅助数据。下一个调用将接收剩余的四个字节的数据。
如果分配用于接收传入辅助数据的空间太小,则辅助数据将被截断为将适合提供的缓冲区的标头数(或者,对于SCM_RIGHTS文件描述符列表,文件描述符列表可能是截断)。如果没有为传入的辅助数据提供缓冲区(即,提供给recvmsg(2)的msghdr结构的msg_control字段为NULL),则丢弃传入的辅助数据。在这两种情况下,都会在recvmsg(2)返回的msg.msg_flags值中设置MSG_CTRUNC标志。
Ioctls
以下ioctl(2)调用返回value中的信息。正确的语法是:
int value; error = ioctl(unix_socket, ioctl_type, &value);
ioctl_type可以是:
- SIOCINQ
- 对于SOCK_STREAM套接字,此调用返回接收缓冲区中未读字节的数量。套接字不得处于LISTEN状态,否则将返回错误(EINVAL)。 SIOCINQ在中定义。或者,您可以使用在中定义的同义词FIONREAD。对于SOCK_DGRAM套接字,返回的值与Internet域数据报套接字的返回值相同。参见udp(7)。
错误说明
- EADDRINUSE
- 指定的本地地址已经在使用中,或者文件系统套接字对象已经存在。
- EBADF
- 当通过UNIX域套接字将文件描述符作为辅助数据发送文件描述符时,sendmsg(2)可能会发生此错误(请参见上面的SCM_RIGHTS的说明),并指示正在发送的文件描述符编号无效(例如,不是打开的文件描述符)。
- ECONNREFUSED
- connect(2)指定的远程地址不是侦听套接字。如果目标路径名不是套接字,也会发生此错误。
- ECONNRESET
- 远程套接字意外关闭。
- EFAULT
- 用户内存地址无效。
- EINVAL
- 传递了无效的参数。常见原因是未在传递的地址的sun_type字段中指定值AF_UNIX,或者套接字对于所应用的操作处于无效状态。
- EISCONN
- 在已连接的套接字上调用connect(2)或在已连接的套接字上指定了目标地址。
- ENOENT
- 指定给connect(2)的远程地址中的路径名不存在。
- ENOMEM
- 内存不足。
- ENOTCONN
- 套接字操作需要目标地址,但套接字未连接。
- EOPNOTSUPP
- 在面向非流的套接字上调用流操作,或尝试使用带外数据选项。
- EPERM
- 发件人在结构中传递了无效的凭据。
- EPIPE
- 流套接字上的远程套接字已关闭。如果启用,也会发送SIGPIPE。通过将MSG_NOSIGNAL标志传递给send(2)或sendmsg(2)可以避免这种情况。
- EPROTONOSUPPORT
- 传递的协议不是AF_UNIX。
- EPROTOTYPE
- 远程套接字与本地套接字类型(SOCK_DGRAM与SOCK_STREAM)不匹配。
- ESOCKTNOSUPPORT
- 未知的套接字类型。
- ESRCH
- 在发送包含凭据的辅助消息(SCM_CREDENTIALS)时,调用方指定的PID与任何现有进程都不匹配。
- ETOOMANYREFS
- 通过UNIX域套接字将文件描述符作为辅助数据发送时,sendmsg(2)可能会发生此错误(请参见上面的SCM_RIGHTS的描述)。如果"运行中"文件描述符的数量超过RLIMIT_NOFILE资源限制,并且调用者不具有CAP_SYS_RESOURCE功能,则会发生这种情况。传输中的文件描述符是使用sendmsg(2)发送的,但尚未使用recvmsg(2)在接收者进程中接受。
- 从主线Linux 4.5(以及在某些较早的内核版本中已将该修补程序向后移植)中诊断出此错误。在较早的内核版本中,可以通过发送带有sendmsg(2)的每个文件描述符,然后关闭文件描述符,从而不考虑RLIMIT_NOFILE资源限制来放置无限数量的文件描述符。
在生成文件系统套接字对象时,其他错误可能由通用套接字层或文件系统生成。有关更多信息,请参见相应的手册页。
版本
SCM_CREDENTIALS和抽象名称空间是Linux 2.2引入的,不应在可移植程序中使用。 (某些BSD派生的系统也支持凭据传递,但是实现细节有所不同。)
备注
绑定到具有文件名的套接字会在文件系统中创建一个套接字,当不再需要该套接字时,调用方必须将其删除(使用unlink(2))。通常使用UNIX封闭式语义。套接字可以随时取消链接,并且在最后一次关闭引用时,最终将其从文件系统中删除。
要通过SOCK_STREAM套接字传递文件描述符或凭据,必须在同一sendmsg(2)或recvmsg(2)调用中发送或接收至少一个字节的非辅助数据。
UNIX域流套接字不支持带外数据的概念。
BUGS
当将套接字绑定到地址时,如果sun_path中未提供空终止符,则Linux是一种附加空终止符的实现。在大多数情况下,这没有问题:检索套接字地址时,它将比绑定套接字时提供的地址长一个字节。但是,在一种情况下,可能会导致混乱的行为:如果在绑定套接字时提供了108个非空字节,则添加空终止符会使路径名的长度超过sizeof(sun_path)。因此,在检索套接字地址时(例如,通过accept(2)),如果将用于检索调用的输入addrlen参数指定为sizeof(struct sockaddr_un),则返回的地址结构在sun_path中将不具有空终止符。
另外,某些实现在绑定套接字时不需要null终止符(addrlen参数用于确定sun_path的长度),并且在这些实现上检索套接字地址时,sun_path中没有null终止符。
检索套接字地址的应用程序可以(适当地)通过考虑路径名中有效字节数为以下事实来处理sun_path中没有空终止符的可能性:
strnlen(addr.sun_path,addrlen-offsetof(sockaddr_un,sun_path))
或者,应用程序可以通过分配大小为sizeof(struct sockaddr_un)+1的缓冲区来检索套接字地址,该缓冲区在检索之前被清零。检索调用可以将addrlen指定为sizeof(struct sockaddr_un),并且额外的零字节确保在sun_path中返回的字符串将有一个空终止符:
void *addrp; addrlen = sizeof(struct sockaddr_un); addrp = malloc(addrlen + 1); if (addrp == NULL) /* Handle error */ ; memset(addrp, 0, addrlen + 1); if (getsockname(sfd, (struct sockaddr *) addrp, &addrlen)) == -1) /* handle error */ ; printf("sun_path = %s\n", ((struct sockaddr_un *) addrp)->sun_path);
如果可以保证创建路径名套接字的应用程序遵循上面路径名套接字中概述的规则,则可以避免这种混乱情况。
示例
以下代码演示了将顺序数据包套接字用于本地进程间通信的用法。它由两个程序组成。服务器程序等待来自客户端程序的连接。客户端在单独的消息中发送其每个命令行参数。服务器将传入的消息视为整数并将它们加起来。客户端发送命令字符串" END"。服务器发回一条消息,其中包含客户端整数的总和。客户端打印总和并退出。服务器等待下一个客户端连接。要停止服务器,请使用命令行参数" DOWN"调用客户端。
在后台运行服务器并重复执行客户端时,记录了以下输出。服务器程序收到" DOWN"命令后,执行结束。
Example output
$ ./server & [1] 25887 $ ./client 3 4 Result = 7 $ ./client 11 -5 Result = 6 $ ./client DOWN Result = 0 [1]+ Done ./server $
Program source
/* * File connection.h */ #define SOCKET_NAME "/tmp/9Lq7BNBnBycd6nxy.socket" #define BUFFER_SIZE 12 /* * File server.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/un.h> #include <unistd.h> #include "connection.h" int main(int argc, char *argv[]) { struct sockaddr_un name; int down_flag = 0; int ret; int connection_socket; int data_socket; int result; char buffer[BUFFER_SIZE]; /* Create local socket. */ connection_socket = socket(AF_UNIX, SOCK_SEQPACKET, 0); if (connection_socket == -1) { perror("socket"); exit(EXIT_FAILURE); } /* * For portability clear the whole structure, since some * implementations have additional (nonstandard) fields in * the structure. */ memset(&name, 0, sizeof(struct sockaddr_un)); /* Bind socket to socket name. */ name.sun_family = AF_UNIX; strncpy(name.sun_path, SOCKET_NAME, sizeof(name.sun_path) - 1); ret = bind(connection_socket, (const struct sockaddr *) &name, sizeof(struct sockaddr_un)); if (ret == -1) { perror("bind"); exit(EXIT_FAILURE); } /* * Prepare for accepting connections. The backlog size is set * to 20. So while one request is being processed other requests * can be waiting. */ ret = listen(connection_socket, 20); if (ret == -1) { perror("listen"); exit(EXIT_FAILURE); } /* This is the main loop for handling connections. */ for (;;) { /* Wait for incoming connection. */ data_socket = accept(connection_socket, NULL, NULL); if (data_socket == -1) { perror("accept"); exit(EXIT_FAILURE); } result = 0; for (;;) { /* Wait for next data packet. */ ret = read(data_socket, buffer, BUFFER_SIZE); if (ret == -1) { perror("read"); exit(EXIT_FAILURE); } /* Ensure buffer is 0-terminated. */ buffer[BUFFER_SIZE - 1] = 0; /* Handle commands. */ if (!strncmp(buffer, "DOWN", BUFFER_SIZE)) { down_flag = 1; break; } if (!strncmp(buffer, "END", BUFFER_SIZE)) { break; } /* Add received summand. */ result += atoi(buffer); } /* Send result. */ sprintf(buffer, "%d", result); ret = write(data_socket, buffer, BUFFER_SIZE); if (ret == -1) { perror("write"); exit(EXIT_FAILURE); } /* Close socket. */ close(data_socket); /* Quit on DOWN command. */ if (down_flag) { break; } } close(connection_socket); /* Unlink the socket. */ unlink(SOCKET_NAME); exit(EXIT_SUCCESS); } /* * File client.c */ #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/un.h> #include <unistd.h> #include "connection.h" int main(int argc, char *argv[]) { struct sockaddr_un addr; int i; int ret; int data_socket; char buffer[BUFFER_SIZE]; /* Create local socket. */ data_socket = socket(AF_UNIX, SOCK_SEQPACKET, 0); if (data_socket == -1) { perror("socket"); exit(EXIT_FAILURE); } /* * For portability clear the whole structure, since some * implementations have additional (nonstandard) fields in * the structure. */ memset(&addr, 0, sizeof(struct sockaddr_un)); /* Connect socket to socket address */ addr.sun_family = AF_UNIX; strncpy(addr.sun_path, SOCKET_NAME, sizeof(addr.sun_path) - 1); ret = connect (data_socket, (const struct sockaddr *) &addr, sizeof(struct sockaddr_un)); if (ret == -1) { fprintf(stderr, "The server is down.\n"); exit(EXIT_FAILURE); } /* Send arguments. */ for (i = 1; i < argc; ++i) { ret = write(data_socket, argv[i], strlen(argv[i]) + 1); if (ret == -1) { perror("write"); break; } } /* Request result. */ strcpy (buffer, "END"); ret = write(data_socket, buffer, strlen(buffer) + 1); if (ret == -1) { perror("write"); exit(EXIT_FAILURE); } /* Receive result. */ ret = read(data_socket, buffer, BUFFER_SIZE); if (ret == -1) { perror("read"); exit(EXIT_FAILURE); } /* Ensure buffer is 0-terminated. */ buffer[BUFFER_SIZE - 1] = 0; printf("Result = %s\n", buffer); /* Close socket. */ close(data_socket); exit(EXIT_SUCCESS); }
有关使用SCM_RIGHTS的示例,请参见cmsg(3)。
出版信息
这个页面是Linux手册页项目5.08版的一部分。有关项目的说明、有关报告错误的信息以及此页面的最新版本,请访问https://www.kernel.org/doc/man-pages/。