Unix 域协议

发布时间 2023-08-05 22:02:39作者: 某某人8265

Unix 域协议

unix域协议是在单个主机上执行客户端/服务器通信的一种方法,使用套接字API,可使用IPC的方法一种。

分为两类:字节流套接字(类似TCP)、数据报套接字(类似UDP)

  1. Unix域套接字比位于同一个主机的TCP套接字快,X Window System 就使用了Unix域协议
  2. 在不同进程间传递描述符
  3. 能把客户的凭证(用户ID和组ID)提供给服务器,从而提供额外的安全检查措施
  4. 使用普通文件系统的路径名作为标识,而非ip和端口。这些特殊文件除非和Unix域套接字关联,否则无法读写
// unix域套接字地址,可使用 bind getsockname 等函数操作
struct sockaddr_un {
  sa_family_t sun_family; /* AF_UNIX */
  char sun_path[108];     /* Path name. 为空时等价于 INADDR_ANY */
};

// Returns 0 on success, -1 for errors.
int socketpair(
    int __domain,    // AF_LOCAL
    int __type,      // SOCK_STREAM or SOCK_DGRAM
    int __protocol,  // 0
    int __fds[2]);   // 返回两个新创建的文件描述符,类似 pipe
                    // 但是此处的两个文件描述符都是全双工的,可以双向通信
  1. 应当捆绑绝对路径
  2. connect 指定的绝对路径应绑定某个打开的Unix域套接字,且套接字类型一致
  3. 调用 connect 连接的权限类似以只读权限 open 路径
  4. Unix域套接字的connect调用发现监听套接字的队列满后,调用立即返回一个 ECONNREFUSED 错误;TCP此时会忽略新到达的SYN,发起端将重试SYN
  5. 与TCP UDP会分配临时端口不同,给未绑定的套接字发信息不会自动绑定路径名
流式服务端
 #include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>

const char *UNIXSTR_PATH = "/tmp/unix_domain_socket";

int main(int argc, char const *argv[]) {
  int server_sockfd, client_sockfd;
  socklen_t client_len;
  struct sockaddr_un server_address;
  struct sockaddr_un client_address;

  // 删除并创建新套接字
  if (unlink(UNIXSTR_PATH) < 0) {
    perror("unlink");
  }
  server_sockfd = socket(AF_UNIX,  // 等于AF_LOCAL
                         SOCK_STREAM, 0);

  // 命名套接字
  server_address.sun_family = AF_UNIX;
  strcpy(server_address.sun_path, UNIXSTR_PATH);
  // 创建文件权限为 0777 按umask修正后的值
  if (bind(server_sockfd, (struct sockaddr *)&server_address,
           sizeof(server_address)) < 0) {
    perror("bind");
    return -1;
  }

  // 建立连接队列
  listen(server_sockfd, 5);
  while (1) {
    char ch;
    printf("server waiting\n");

    // 接受一个连接
    client_len = sizeof(client_address);
    client_sockfd =
        accept(server_sockfd, (struct sockaddr *)&client_address, &client_len);

    // 操作 socket
    read(client_sockfd, &ch, 1);
    printf("client_sockfd = %d send: %c \n", client_sockfd, ch);
    ch++;
    write(client_sockfd, &ch, 1);
    close(client_sockfd);
  }
  return 0;
}
流式客户端
 #include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>

const char* UNIXSTR_PATH = "/tmp/unix_domain_socket";

int main(int argc, char const *argv[]) {
  int ret{0};
  int client_sockfd;
  struct sockaddr_un server_address;

  client_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);

  bzero(&server_address, sizeof(server_address));
  server_address.sun_family = AF_UNIX;
  strncpy(server_address.sun_path, UNIXSTR_PATH,
          sizeof(server_address.sun_path) - 1);

  ret = connect(client_sockfd, (struct sockaddr *)&server_address,
                sizeof(server_address));
  if (ret < 0) {
    perror("connect");
    return -1;
  }

  char ch{'a'};
  write(client_sockfd, &ch, 1);
  read(client_sockfd, &ch, 1);
  printf("char from server = %c\n", ch);

  close(client_sockfd);
  return 0;
}

数据报类型协议与UDP类似,但是必须显示bind一个路径名到套接字

描述符传递

父子进程可通过fork或exec命令传递描述符,而通过Unix域套接字使用 sendmsg 发送特殊消息,可以在任意进程间传递描述符

建立流式域套接字(数据报域套接字可能丢失数据,就像UDP),可以传输任意类型的描述符。发送进程构建一个 msfhdr 结构,其中 msfhdr 的 msg_control 成员用于发送描述符
调用sendmsg发送后,即使在对方接收前关闭了该描述符,此进程仍保持打开状态,发送描述符会使其引用计数加1
接收的描述符可能具有不同的描述符号,这是正常的,传输过程涉及在接收进程中创建一个新的描述符,新旧描述符指向内核中同一个文件表项