AF_UNIX 域协议

更新时间:
2024-05-13
下载文档

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 套接字中,可以使用传送文件描述符的方式在进程之间传递打开的文件描述符。

传送文件描述符的编程步骤包括以下几个步骤:

  1. 创建套接字:调用 socket 函数创建一个 AF_UNIX 套接字,在创建套接字时,需要指定地址族为 AF_UNIX。
  2. 绑定地址:调用 bind 函数将套接字与一个路径名绑定。这个路径名在文件系统中是唯一的,用于标识套接字。
  3. 监听连接:调用 listen 函数开始监听连接请求,等待其他进程连接到该套接字。
  4. 接受连接:调用 accept 函数接受其他进程的连接请求。此时,会创建一个新的套接字来与连接的进程进行通信。
  5. 传送文件描述符:一旦建立连接,可以调用 sendmsg 和 recvmsg 函数 来传送消息。通过传送消息的辅助数据(ancillary data),可以在消息中包含文件描述符。

在发送端,可以使用以下步骤传送文件描述符:

  1. 创建辅助数据结构:使用 struct msghdr 结构来表示消息,并为其分配内存。
  2. 创建控制消息:使用 **CMSG_SPACE **宏计算出控制消息的大小,并为其分配内存。
  3. 填充控制消息:使用 **CMSG_FIRSTHDR **和 **CMSG_NXTHDR **宏来遍历控制消息的头部,使用 **CMSG_DATA **宏来获取控制消息的数据指针。将要传送的文件描述符放入控制消息中。
  4. 发送消息:调用 sendmsg 函数发送消息,其中将辅助数据结构传递给 msg_control 字段。

在接收端,可以使用以下步骤接收文件描述符:

  1. 创建辅助数据结构:使用 struct msghdr 结构来表示消息,并为其分配内存。
  2. 接收消息:使用 recvmsg 函数接收消息,并将接收到的数据存储在辅助数据结构中。
  3. 解析控制消息:使用 **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.
文档内容是否对您有所帮助?
有帮助
没帮助