AF_UNIX 域协议
UNIX 域套接字是一种高级的 IPC 机制,这种形式的 IPC 可以在同一计算机系统上运行的两个进程之间进行通信。虽然因特网域套接字可用于同一目的,但 UNIX 域套接字的效率更高。UNIX 域套接字仅仅复制数据,并不执行协议处理,因此,无需添加或者删除网络报头,无需计算校验和,无需产生序列号,无需发送确认报文等。
AF_UNIX 简介
SylixOS 中的 UNIX 域套接字提供流(SOCK_STREAM)、数据报(SOCK_DGRAM)和连续数据报(SOCK_SEQPACKET)三种类型。UNIX 域数据报服务是可靠的,既不会丢失报文也不会传递出错。UNIX 域套接字就像是套接字和管道的混合,可以使用它们面向网络的域套接字接口或者使用 socketpair 函数来创建一对无命名的、相互连接的 UNIX 域套接字。
#include <sys/socket.h>
int socketpair(int domain, int type, int protocol, int sv[2]);
函数 socketpair 原型分析:
- 此函数成功返回 0,失败返回-1 并设置错误号。
- 参数 domain 是协议域(仅支持 AF_UNIX)。
- 参数 type 是协议类型。
- 参数 protocol 是协议。
- 输出参数 sv[2] 返回文件描述符组。
虽然接口足够通用,但 SylixOS 中此函数仅支持 UNIX 域,一对相互连接的 UNIX 域套接字可以起到全双工管道的作用:两端对读、写开放。
我们会发现 socketpair 函数创建的套接字是无名的,这意味着无关进程不能使用它们。
因特网域套接字可以通过调用 bind 函数将一个地址绑定到一个套接字上,同样可以将一个地址绑定到 UNIX 域套接字上,不同的是,UNIX 域套接字使用的地址有别于因特网套接字。
在“socket 地址”中我们介绍了 UNIX 域套接字的地址结构是 sockaddr_un,该结构的 sun_path 成员包含了一个路径名,当我们将一个地址绑定到一个 UNIX 域套接字时,系统会用该路径名创建一个 S_IFSOCK 类型的文件。
该文件仅用于向客户进程告示套接字名字,该文件无法打开,也不能由应用程序用于通信。
如果我们试图绑定同一地址时,该文件已经存在,那么 bind 请求会失败。当关闭套接字时,并不自动删除该文件,所以必须确保在应用程序退出前,对该文件解除连接操作。
当通信双方位于同一台主机时,使用 UNIX 域套接字的速度通常是 TCP 套接字的两倍。UNIX 域套接字可以用来在同一台主机上的两个进程之间传递描述符。UNIX 域套接字可以向服务器提供客户的凭证,这能提供附加的安全检查。
使用 UNIX 域套接字时,以下是需要注意的地方:
- connect 函数使用的路径名必须是一个绑定在某个已打开的 UNIX 域套接字上的路径名,而且套接字的类型也必须一致。
- UNIX 域流式套接字和 TCP 套接字类似,它们都为进程提供了一个没有记录边界的字节流接口。
- 如果 UNIX 域字节流套接字的 connect 函数调用发现监听套接字的队列已满,会立刻返回一个 ECONNREFUSED 错误码。这和 TCP 不同:如果监听套接字的队列已满,它将忽略到来的 SYN,TCP 连接的发起方会接着发送几次 SYN 重试。
- UNIX 域数据报套接字和 UDP 套接字类似,它们都提供了一个保留记录边界的不可靠数据服务。
- SylixOS UNIX 域套接字实现了 SOCK_SEQPACKET 数据报,这种类型保证了连接性和保留记录边界双向功能。
- 与 UDP 不同的是,在未绑定的 UNIX 域套接字上发送数据报不会给它捆绑一个路径名(在未绑定的 UDP 套接字上发送数据会为该套接字捆绑一个临时的端口),这意味着,数据报的发送者除非绑定一个路径名,否则接收者无法发回应答数据报。同样,与 TCP 和 UDP 不同的是,给 UNIX 域数据报套接字调用 connect 函数不会捆绑一个路径名。
AF_UNIX 传递文件描述符
在 AF_UNIX 套接字中,可以使用传送文件描述符的方式在进程之间传递打开的文件描述符。
传送文件描述符的编程步骤包括以下几个步骤:
- 创建套接字:调用 socket 函数创建一个 AF_UNIX 套接字,在创建套接字时,需要指定地址族为 AF_UNIX。
- 绑定地址:调用 bind 函数将套接字与一个路径名绑定。这个路径名在文件系统中是唯一的,用于标识套接字。
- 监听连接:调用 listen 函数开始监听连接请求,等待其他进程连接到该套接字。
- 接受连接:调用 accept 函数接受其他进程的连接请求。此时,会创建一个新的套接字来与连接的进程进行通信。
- 传送文件描述符:一旦建立连接,可以调用 sendmsg 和 recvmsg 函数 来传送消息。通过传送消息的辅助数据(ancillary data),可以在消息中包含文件描述符。
在发送端,可以使用以下步骤传送文件描述符:
- 创建辅助数据结构:使用 struct msghdr 结构来表示消息,并为其分配内存。
- 创建控制消息:使用 CMSG_SPACE宏计算出控制消息的大小,并为其分配内存。
- 填充控制消息:使用 CMSG_FIRSTHDR和 CMSG_NXTHDR宏来遍历控制消息的头部,使用 CMSG_DATA宏来获取控制消息的数据指针。将要传送的文件描述符放入控制消息中。
- 发送消息:调用 sendmsg 函数发送消息,其中将辅助数据结构传递给 msg_control 字段。
在接收端,可以使用以下步骤接收文件描述符:
- 创建辅助数据结构:使用 struct msghdr 结构来表示消息,并为其分配内存。
- 接收消息:使用 recvmsg 函数接收消息,并将接收到的数据存储在辅助数据结构中。
- 解析控制消息:使用 CMSG_FIRSTHDR和 CMSG_NXTHDR宏来遍历控制消息的头部,使用 CMSG_DATA宏来获取控制消息的数据指针。从控制消息中获取传输的文件描述符。
AF_UNIX 实例
SOCK_STREAM 类型实例
SOCK_STREAM 类型 UNIX 域套接字通信过程,如下图所示。下面程序使用 UNIX 域套接字实现服务器与客户端之间的通信,服务器端等待客户端发送字符串“client”,当服务器端成功接收到字符串“client”后发送“ACK”回应客户端,客户端打印服务器端的回应结果。
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
#define AF_UNIX_FILE "afunix.tmp"
int main (int argc, char *argv[])
{
int sockfd;
struct sockaddr_un addr;
socklen_t len;
int i, bytes, result;
char str[16];
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockfd < 0) {
fprintf(stderr, "[client]socket error.\n");
return (-1);
}
bzero(&addr, sizeof(addr));
len = sizeof(addr);
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, AF_UNIX_FILE);
result = connect(sockfd, (struct sockaddr *)&addr, len);
if (result < 0) {
fprintf(stderr, "[client]connect error.\n");
close(sockfd);
return (-1);
}
fprintf(stdout, "[client]connect server success.\n");
for (i = 0; i < 5; i++) {
bytes = write(sockfd, "client", 7);
if (bytes < 0) {
fprintf(stderr, "[client]write error.\n");
break;
}
sleep(2);
bytes = read(sockfd, str, 16);
if (bytes < 0) {
fprintf(stderr, "[client]read error.\n");
break;
}
fprintf(stdout, "[client]receive ACK from server.\n");
}
close(sockfd);
return (0);
}
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
#include <string.h>
#define AF_UNIX_FILE "afunix.tmp"
int main (int argc, char *argv[])
{
int ssockfd, csockfd;
socklen_t slen, clen;
struct sockaddr_un saddr;
int i, bytes;
char str[16];
unlink(AF_UNIX_FILE);
ssockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (ssockfd < 0) {
fprintf(stderr, "[server]socket error.\n");
return (-1);
}
slen = sizeof(saddr);
strcpy(saddr.sun_path, AF_UNIX_FILE);
saddr.sun_family = AF_UNIX;
bind(ssockfd, (struct sockaddr *)&saddr, slen);
listen(ssockfd, 5);
fprintf(stdout, "[server]waiting for client connect...\n");
csockfd = accept(ssockfd, (struct sockaddr *)&saddr, &clen);
if (csockfd < 0) {
fprintf(stderr, "[server]accept error.\n");
close(ssockfd);
return (-1);
}
fprintf(stdout, "[server]connect success.\n");
for (i = 0; i < 5; i++) {
bytes = read(csockfd, str, 16);
if (bytes < 0) {
fprintf(stderr, "[server]read error.\n");
break;
}
if (strncmp("client", str, 6) == 0) {
fprintf(stdout, "[server]receiver from client is: %s\n", str);
} else {
fprintf(stderr, "[server]client send failed.\n");
break;
}
sleep(1);
fprintf(stdout, "[server]server reply ACK.\n");
bytes = write(csockfd, "ACK", 4);
if (bytes < 0) {
fprintf(stderr, "[server]write error.\n");
break;
}
}
unlink(AF_UNIX_FILE);
close(csockfd);
close(ssockfd);
return (0);
}
在 SylixOS Shell 下运行程序,首先启动服务端程序,然后启动客户端程序,结果显示如下:
服务端显示:
# ./Stream_type_Server
[server]waiting for client connect...
[server]connect success.
[server]receiver from client is: client
[server]server reply ACK.
[server]receiver from client is: client
[server]server reply ACK.
[server]receiver from client is: client
[server]server reply ACK.
[server]receiver from client is: client
[server]server reply ACK.
[server]receiver from client is: client
[server]server reply ACK.
客户端显示:
# ./Stream_type_Client
[client]connect server success.
[client]receive ACK from server.
[client]receive ACK from server.
[client]receive ACK from server.
[client]receive ACK from server.
[client]receive ACK from server.
SOCK_DGRAM 类型实例
SOCK_DGRAM 类型 UNIX 域套接字通信过程类似于 UDP 的通信过程如下图所示,下面是使用 SOCK_DGRAM 类型实现的服务器——客户端通信实例,同 SOCK_STREAM 类型的功能类似,服务器端被动地接收由客户端发送来的数据,如果服务器端收到字符“q”,则代表客户端请求通信终止,这是服务器端终止程序。客户端每隔 1 秒发送一次本地当前时间,发送 5 次后终止通信过程。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stddef.h>
#include <time.h>
#define AF_UNIX_FILE "afunix.tmp"
int main (int argc, char **argv)
{
int sockfd;
struct sockaddr_un addr;
int count = 0;
socklen_t len;
sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (sockfd < 0) {
fprintf(stderr, "[client]socker error.\n");
return (-1);
}
bzero(&addr, sizeof(addr));
len = sizeof(addr);
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, AF_UNIX_FILE);
for (;;) {
time_t t;
char *str;
ssize_t ret;
t = time(NULL);
str = ctime(&t);
if (str == NULL) {
break;
}
if (count++ > 5) {
str = "q";
}
ret = sendto(sockfd, str, strlen(str), 0, (struct sockaddr *)&addr, len);
if (ret < 0) {
fprintf(stderr, "[server]sendto error.\n");
break;
}
fprintf(stdout, "[client]send %s", str);
if (count > 6) {
break;
}
sleep(1);
}
unlink(AF_UNIX_FILE);
close(sockfd);
return (0);
}
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stddef.h>
#define AF_UNIX_FILE "afunix.tmp"
int main (int argc, char **argv)
{
int sockfd;
struct sockaddr_un addr;
int ret;
socklen_t len;
size_t size;
char buf[BUFSIZ] = {0};
unlink(AF_UNIX_FILE);
sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (sockfd < 0) {
fprintf(stderr, "[server]socker error.\n");
return (-1);
}
bzero(&addr, sizeof(addr));
len = sizeof(addr);
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, AF_UNIX_FILE);
ret = bind(sockfd, (struct sockaddr *)&addr, len);
if (ret < 0) {
fprintf(stderr, "[server]bind error.\n");
close(sockfd);
return (-1);
}
for (;;) {
size = recvfrom(sockfd, buf, BUFSIZ, 0, NULL, NULL);
if (size > 0) {
fprintf(stdout, "[server]recv: %s", buf);
}
if (strncmp("q", buf, 1) == 0) {
break;
}
}
unlink(AF_UNIX_FILE);
close(sockfd);
return (0);
}
在 SylixOS Shell 下运行程序,首先启动服务端程序,然后启动客户端程序,结果显示如下:
服务端显示:
# ./Dgram_type_Server
[server]recv: Tue Dec 29 11:53:39 2020
[server]recv: Tue Dec 29 11:53:40 2020
[server]recv: Tue Dec 29 11:53:41 2020
[server]recv: Tue Dec 29 11:53:42 2020
[server]recv: Tue Dec 29 11:53:43 2020
[server]recv: Tue Dec 29 11:53:44 2020
[server]recv: que Dec 29 11:53:44 2020
客户端显示:
# ./Dgram_type_Client
[client]send Tue Dec 29 11:53:39 2020
[client]send Tue Dec 29 11:53:40 2020
[client]send Tue Dec 29 11:53:41 2020
[client]send Tue Dec 29 11:53:42 2020
[client]send Tue Dec 29 11:53:43 2020
[client]send Tue Dec 29 11:53:44 2020
SOCK_SEQPACKET 类型实例
SOCK_SEQPACKET 类型的 UNIX 域套接字是面向连接的成块消息传输,因此通信过程类似于 SOCK_STREAM 类型的套接字。
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <string.h>
#define UNIX_PATH "./AF_UNIX_SEQ"
#define SEND_STR "af_unix seqpacket test."
#define SEND_Q "q"
int main (int argc, char *argv[])
{
int sockfd;
int ret;
struct sockaddr_un unixaddr;
socklen_t len = sizeof(unixaddr);
int i;
unlink(UNIX_PATH);
sockfd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
if (sockfd < 0) {
perror("socket");
return (-1);
}
unixaddr.sun_family = AF_UNIX;
strcpy(unixaddr.sun_path, UNIX_PATH);
unixaddr.sun_len = SUN_LEN(&unixaddr);
ret = connect(sockfd, (struct sockaddr *)&unixaddr, len);
if (ret < 0) {
perror("connect");
return (-1);
}
for (i = 0; i < 10; i++) {
sendto(sockfd, SEND_STR, strlen(SEND_STR), 0,
(struct sockaddr *)&unixaddr, len);
fprintf(stdout, "send msg: %s\n", SEND_STR);
sleep(1);
}
sendto(sockfd, SEND_Q, strlen(SEND_Q), 0,
(struct sockaddr *)&unixaddr, len);
return (0);
}
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#define UNIX_PATH "./AF_UNIX_SEQ"
#define QUIT_STR "q"
int main (int argc, char *argv[])
{
int sockfd, csockfd;
int ret;
char buf[1024] = {0};
struct sockaddr_un unixaddr;
socklen_t len = sizeof(unixaddr);
unlink(UNIX_PATH);
sockfd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
if (sockfd < 0) {
perror("socket");
return (-1);
}
unixaddr.sun_family = AF_UNIX;
strcpy(unixaddr.sun_path, UNIX_PATH);
unixaddr.sun_len = SUN_LEN(&unixaddr);
ret = bind(sockfd, (struct sockaddr *)&unixaddr, len);
if (ret < 0) {
perror("bind");
return (-1);
}
listen(sockfd, 5);
csockfd = accept(sockfd, (struct sockaddr *)&unixaddr, &len);
if (csockfd < 0) {
perror("accept");
return (-1);
}
while (1) {
ssize_t stlen;
stlen = recvfrom(csockfd, buf, sizeof(buf), 0, NULL, NULL);
if (stlen < 0) {
continue;
}
if (!strncmp(buf, QUIT_STR, 1)) {
fprintf(stdout, "server exit.\n");
break;
}
fprintf(stdout, "buf: %s\n", buf);
}
return (0);
}
在 SylixOS Shell 下运行程序,首先启动服务端程序,然后启动客户端程序,结果显示如下:
服务端显示:
# ./Seqcacket_type_Server
buf: af_unix seqpacket test.
buf: af_unix seqpacket test.
buf: af_unix seqpacket test.
buf: af_unix seqpacket test.
buf: af_unix seqpacket test.
buf: af_unix seqpacket test.
buf: af_unix seqpacket test.
buf: af_unix seqpacket test.
buf: af_unix seqpacket test.
buf: af_unix seqpacket test.
server exit.
客户端显示:
# ./Seqcacket_type_Client
send msg: af_unix seqpacket test.
send msg: af_unix seqpacket test.
send msg: af_unix seqpacket test.
send msg: af_unix seqpacket test.
send msg: af_unix seqpacket test.
send msg: af_unix seqpacket test.
send msg: af_unix seqpacket test.
send msg: af_unix seqpacket test.
send msg: af_unix seqpacket test.
send msg: af_unix seqpacket test.