加入收藏 | 设为首页 | 会员中心 | 我要投稿 开发网_新乡站长网 (https://www.0373zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 服务器 > 搭建环境 > Unix > 正文

Unix/Linux编程:Unix domain socket

发布时间:2022-12-21 11:05:10 所属栏目:Unix 来源:
导读:  UNIX domain socket用于同一主机系统上的相互通信

  UNIX domain socket 地址:struct sockaddr_un

  在Unix domain中,socket地址以路径名来表示,domain特定的socket地址结构的定义如下所示:
  UNIX domain socket用于同一主机系统上的相互通信
 
  UNIX domain socket 地址:struct sockaddr_un
 
  在Unix domain中,socket地址以路径名来表示,domain特定的socket地址结构的定义如下所示:
 
  struct sockaddr_un{
   sa_family_t sun_family;   // always AF_UNIX
   char sun_path[128];  
  };
  sockaddr_un 结构中字段的 sun_前缀与 Sun Microsystems 没有任何关系,它是根据 socket unix 而来的。
 
  为将一个Unix domain socket绑定到一个地址上,需要初始化一个sockaddr_un结构,然后将指定这个结构的一个指针作为addr参数bind()并将addrlen指定为这个结构的大小。如下所示:
 
   const char *SV_SOCK_PATH = "/tmp/mysock";
   int sfd;
   struct sockaddr_un addr;
    // 绑定一个 UNIX domain socket
    sfd = socket(AF_UNIX, SOCK_STREAM, 0);
      if (sfd == -1){
          perror("socket");
          exit(EXIT_FAILURE);
      }
      
       memset(&addr, 0, sizeof(struct sockaddr_un));
      addr.sun_family = AF_UNIX;
      strncpy(addr.sun_path, SV_SOCK_PATH, sizeof(addr.sun_path) - 1);
      if (bind(sfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) == -1)
      {
          printf("bind");
          exit(EXIT_FAILURE);
      }
  当用来绑定 UNIX domain socket 时,bind()会在文件系统中创建一个条目(因此作为socket路径名的一部分目录需要可访问和可读写)。文件的所有权将根据常规的文件创建规则来确定。这个文件会被标记为一个socket。当在这个路径名上stat()时,它会在stat结构的st_mode字段中的文件类型部分返回值S_IFSOCK。当使用ls –l列出时,UNIX domain socket 在第一列将会显示类型 s,而 ls –F 则会在 socket 路径名后面附加上一个等号(=)。
 
  有关绑定一个 UNIX domain socket 方面还需要注意以下几点
 
  UNIX domain 中的流 socket
 
  下面是一个简单的 UNIX domain 流 socket 服务器 (这个服务器是一个简单的迭代式服务器——服务器在处理下一个客户端之前一次只处理一个客户端)
 
  // us_xfr.h
  #include
  #define BACKLOG 5
  #define SV_SOCK_PATH "/tmp/us_xfr"
  #define BUF_SIZE 100
  int main(int argc, char *argv[])
  {
      struct sockaddr_un addr;
      int sfd, cfd;
      ssize_t numRead;
      char buf[BUF_SIZE];
      // 创建一个 socket。
      sfd = socket(AF_UNIX, SOCK_STREAM, 0);
      if (sfd == -1){
          perror("socket");
          exit(EXIT_FAILURE);
      }
      if (strlen(SV_SOCK_PATH) > sizeof(addr.sun_path) - 1){
          printf("Server socket path too long: %s", SV_SOCK_PATH);
          exit(EXIT_FAILURE);
      }
      // 删除所有与路径名一致的既有文件,这样才能将 socket 绑定到这个路径名上
      if (remove(SV_SOCK_PATH) == -1 && errno != ENOENT){
          printf("remove-%s", SV_SOCK_PATH);
          exit(EXIT_FAILURE);
      }
      //为服务器 socket 构建一个地址结构
      memset(&addr, 0, sizeof(struct sockaddr_un));
      addr.sun_family = AF_UNIX;
      strncpy(addr.sun_path, SV_SOCK_PATH, sizeof(addr.sun_path) - 1);
      //将 socket 绑定到该地址上
      if (bind(sfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) == -1)
      {
          printf("bind");
          exit(EXIT_FAILURE);
      }
      // 将这个 socket 标记为监听 socket。
      if (listen(sfd, BACKLOG) == -1){
          printf("listen");
          exit(EXIT_FAILURE);
      }
      // 执行一个无限循环来处理进入的客户端请求。每次循环迭代执行下列任务。
      for (;;) {          /* Handle client connections iteratively */
          /* Accept a connection. The connection is returned on a new
             socket, 'cfd'; the listening socket ('sfd') remains open
             and can be used to accept further connections. */
          // 接受一个连接,为该连接获取一个新 socket cfd。
          cfd = accept(sfd, NULL, NULL);
          if (cfd == -1)
          {
              printf("accept");
              exit(EXIT_FAILURE);
          }
          /* Transfer data from connected socket to stdout until EOF */
          //从已连接的 socket 中读取所有数据并将这些数据写入到标准输出中。
          while ((numRead = read(cfd, buf, BUF_SIZE)) > 0)
              if (write(STDOUT_FILENO, buf, numRead) != numRead)
              {
                  printf("partial/failed write");
                  exit(EXIT_FAILURE);
              }
          if (numRead == -1)
          {
              printf("read");
              exit(EXIT_FAILURE);
          }
          // 关闭已连接的 socket cfd
          if (close(cfd) == -1)
          {
              printf("close");
              exit(EXIT_FAILURE);
          }
      }
  }
  下面是一个简单的 UNIX domain 流 socket 客户端
 
  int
  main(int argc, char *argv[])
  {
      struct sockaddr_un addr;
      int sfd;
      ssize_t numRead;
      char buf[BUF_SIZE];
 
     // 创建一个 socket。
      sfd = socket(AF_UNIX, SOCK_STREAM, 0);      /* Create client socket */
      if (sfd == -1){
          perror("socket");
          exit(EXIT_FAILURE);
      }
      /* Construct server address, and make the connection */
   // 为服务器 socket 构建一个地址结构并连接到位于该地址处的 socket。
      memset(&addr, 0, sizeof(struct sockaddr_un));
      addr.sun_family = AF_UNIX;
      strncpy(addr.sun_path, SV_SOCK_PATH, sizeof(addr.sun_path) - 1);
      if (connect(sfd, (struct sockaddr *) &addr,
                  sizeof(struct sockaddr_un)) == -1)
      {
          perror("connect");
          exit(EXIT_FAILURE);
      }
      /* Copy stdin to socket */
   //执行一个循环将其标准输入复制到 socket 连接上。当遇到标准输入中的文件结尾时客
      // 户端就终止,其结果是客户端 socket 将会被关闭并且服务器在从连接的另一端的
      // socket 中读取数据时会看到文件结束。
      while ((numRead = read(STDIN_FILENO, buf, BUF_SIZE)) > 0)
          if (write(sfd, buf, numRead) != numRead)
          {
              printf("partial/failed write");
              exit(EXIT_FAILURE);
          }
      if (numRead == -1)
      {
              printf("read");
              exit(EXIT_FAILURE);
          }
      exit(EXIT_SUCCESS);         /* Closes our socket; server sees EOF */
  }
  使用效果:首先在后台运行服务器。
 
  然后创建一个客户端用作输入的测试文件并运行客户端。
 
  此刻子进程已经结束了。现在终止服务器并检查服务器的输出是否与客户端的输入匹配。
 
  diff 命令没有产生任何输出unix进程通信,表示输入和输出文件是一致的。
 
  注意在服务器终止之后,socket 路径名会继续存在。这就是为何服务器在调用 bind()之前使用 remove()删除 socket 路径名的所有既有实例。
 
  UNIX domain 中的数据报 socket
 
  一般来说,使用数据报socket的通信是不可靠的,但这个论断适用于通过网络传输的数据报。对于Unix domin socket来讲,数据报的传输是在内核中发送的,并且也是可靠的,所有的消息都会按照顺序被传递而且也不会发生重复情况
 
  下面是一个简单的 UNIX domain 数据报服务器 程序
 
  // ud_ucase_sv
  #include
  #include
  #include
  #define BUF_SIZE 10             /* Maximum size of messages exchanged
                                     between client and server */
  #define SV_SOCK_PATH "/tmp/ud_ucase"
  int main(int argc, char *argv[])
  {
      struct sockaddr_un svaddr, claddr;
      int sfd, j;
      ssize_t numBytes;
      socklen_t len;
      char buf[BUF_SIZE];
    // 先创建一个 socket
      sfd = socket(AF_UNIX, SOCK_DGRAM, 0);       /* Create server socket */
      if (sfd == -1){
          perror("socket");
          exit(EXIT_FAILURE);
      }
      if (strlen(SV_SOCK_PATH) > sizeof(svaddr.sun_path) - 1){
           printf("Server socket path too long: %s", SV_SOCK_PATH);
           exit(EXIT_FAILURE);
       }
     //服务器先删除了与该地址匹配的路径名,以防出现这个路径名已经存在的情况
      if (remove(SV_SOCK_PATH) == -1 && errno != ENOENT){
           printf("remove-%s", SV_SOCK_PATH);
           exit(EXIT_FAILURE);
   }
   // 将其绑定到一个众所周知的地址上
      memset(&svaddr, 0, sizeof(struct sockaddr_un));
      svaddr.sun_family = AF_UNIX;
      strncpy(svaddr.sun_path, SV_SOCK_PATH, sizeof(svaddr.sun_path) - 1);
      if (bind(sfd, (struct sockaddr *) &svaddr, sizeof(struct sockaddr_un)) == -1)
      {
          printf("bind");
          exit(EXIT_FAILURE);
      }
      /* Receive messages, convert to uppercase, and return to client */
      for (;;) {
          len = sizeof(struct sockaddr_un);
          numBytes = recvfrom(sfd, buf, BUF_SIZE, 0,
                              (struct sockaddr *) &claddr, &len);
          if (numBytes == -1) {
           printf("recvfrom");
           exit(EXIT_FAILURE);
       }
          printf("Server received %ld bytes from %s\n", (long) numBytes,
                  claddr.sun_path);
          /*FIXME: above: should use %zd here, and remove (long) cast */
          for (j = 0; j < numBytes; j++)
              buf[j] = toupper((unsigned char) buf[j]);
          if (sendto(sfd, buf, numBytes, 0, (struct sockaddr *) &claddr, len) !=
                  numBytes)
             {
    printf("sendto");
    exit(EXIT_FAILURE);
   }
      }
  }
  下面是一个简单的 UNIX domain 数据报客户端
 
  // ud_ucase_cl
  #include
  #include
  #include
 
  #define BUF_SIZE 10             /* Maximum size of messages exchanged between client and server */
  #define SV_SOCK_PATH "/tmp/ud_ucase"
  int
  main(int argc, char *argv[])
  {
      struct sockaddr_un svaddr, claddr;
      int sfd, j;
      size_t msgLen;
      ssize_t numBytes;
      char resp[BUF_SIZE];
      if (argc < 2 || strcmp(argv[1], "--help") == 0){
          printf("%s msg...\n", argv[0]);
          exit(1);
          }
      /* Create client socket; bind to unique pathname (based on PID) */
      sfd = socket(AF_UNIX, SOCK_DGRAM, 0);
      if (sfd == -1)
      {
          perror("socket");
          exit(EXIT_FAILURE);
      }
      memset(&claddr, 0, sizeof(struct sockaddr_un));
      claddr.sun_family = AF_UNIX;
      snprintf(claddr.sun_path, sizeof(claddr.sun_path),
              "/tmp/ud_ucase_cl.%ld", (long) getpid());
      if (bind(sfd, (struct sockaddr *) &claddr, sizeof(struct sockaddr_un)) == -1)
      {
          perror("bind");
          exit(EXIT_FAILURE);
      }
      /* Construct address of server */
      memset(&svaddr, 0, sizeof(struct sockaddr_un));
      svaddr.sun_family = AF_UNIX;
      strncpy(svaddr.sun_path, SV_SOCK_PATH, sizeof(svaddr.sun_path) - 1);
      /* Send messages to server; echo responses on stdout */
      for (j = 1; j < argc; j++) {
          msgLen = strlen(argv[j]);       /* May be longer than BUF_SIZE */
          if (sendto(sfd, argv[j], msgLen, 0, (struct sockaddr *) &svaddr,
                  sizeof(struct sockaddr_un)) != msgLen)
           {
           perror("sendto");
           exit(EXIT_FAILURE);
       }
          numBytes = recvfrom(sfd, resp, BUF_SIZE, 0, NULL, NULL);
          /* Or equivalently: numBytes = recv(sfd, resp, BUF_SIZE, 0);
                          or: numBytes = read(sfd, resp, BUF_SIZE); */
          if (numBytes == -1) {
           perror("recvfrom");
           exit(EXIT_FAILURE);
       }
          printf("Response %d: %.*s\n", j, (int) numBytes, resp);
      }
      remove(claddr.sun_path);            /* Remove client socket pathname */
      exit(EXIT_SUCCESS);
  }
  下面演示了如何使用服务器和客户端程序。
 
  从客户端程序的第二个调用(有意在 recvfrom()调用中指定了一个比消息更小的 length 值),可以看出消息会被静默地截断
 
  UNIX domain socket 权限
 
  socket文件的所有权和权限绝对了哪些进程能够与这个socket进行通信:
 
  此外,需要在存放 socket 路径名的所有目录上都拥有执行(搜索)权限。
 
  在默认情况下,创建 socket(通过 bind())时会给所有者(用户)、组以及 other 用户赋予所有的权限。要改变这种行为可以在调用 bind()之前先调用 umask()来禁用不希望赋予的权限。
 
  创建互联 socket 对:socketpair()
 
  有时候让单个进程创建一对socket并将它们连接起来是比较有用的。这可以通过使用两个socket()调用和一个bind()调用以及对listen()、connect()、accept()(用于流 socket)的调用或对 connect()(用于数据报 socket)的调用来完成。socketpair()系统调用则为这个操作提供了一个快捷方式。具体请参见socketpair()
 
  Linux抽象socket名空间
 
  所谓的抽象路径名空间是Linux特有的一项特性,它允许将一个Unix domain socket绑定到一个名字上但不会在文件系统中创建该名字。这种做法具备几点优势:
 
  要创建一个抽象绑定就需要将sun_path字段的第一个字节指定为NULL字节(\0)。这样就能够将抽象socket名字与传统的Unix domain socket路径名区分开来,因为传统的名字是由一个或者多个非空字节以及终止NULL字节构成的字符串。sun_path字段的余下字节为socket定义了抽象名字。在解释这个名字时需要用到全部字节,而不是将其看成是一个以 null结尾的字符串。
 
  下面演示了如何创建一个抽象 socket 绑定
 
  // us_abstract_bind
  #include
  #include
  int main(int argc, char *argv[])
  {
      int sockfd;
      struct sockaddr_un addr;
      char *str;
      memset(&addr, 0, sizeof(struct sockaddr_un));  /* Clear address structure */
      addr.sun_family = AF_UNIX;                     /* UNIX domain address */
      /* addr.sun_path[0] has already been set to 0 by memset() */
      str = "xyz";        /* Abstract name is "\0xyz" */
      strncpy(&addr.sun_path[1], str, strlen(str));
      // In early printings of the book, the above two lines were instead:
      //
      // strncpy(&addr.sun_path[1], "xyz", sizeof(addr.sun_path) - 2);
      //            /* Abstract name is "xyz" followed by null bytes */
      sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
      if (sockfd == -1)
      {
   perror("socket");
   exit(EXIT_FAILURE);
   }
      if (bind(sockfd, (struct sockaddr *) &addr,
              sizeof(sa_family_t) + strlen(str) + 1) == -1)
      {
   perror("bind");
   exit(EXIT_FAILURE);
   }
      // In early printings of the book, the final part of the bind() call
      // above was instead:
      //        sizeof(struct sockaddr_un)) == -1)
      sleep(60);
      exit(EXIT_SUCCESS);
  }
  使用一个初始null字节来区分抽象socket名和传统的socket名会带来不同寻常的结果。假设变量name正好指向了一个长度为零的字符串并将一个 UNIX domain socket绑定到一个照下列方式初始化 sun_path 的名字上。
 
   strncpy(&addr.sun_path, str, strlen(addr.sun_path) - 1);
  在 Linux 上,就会在无意中创建了一个抽象 socket 绑定。但这种代码可能并不是期望中的代码(即一个 bug)。在其他 UNIX 实现中,后续的 bind()调用会失败
 
  总结
 
  Unix domain socket允许位于同一主机上的应用程序之间进行通信。Unix domain支持流和数据报socket
 
  Unix domain socket是通过文件系统中的一个路径来标识的。文件权限可以用来控制对Unix domain socket的访问
 
  socketpair()系统调用创建一对相互连接的Unix domain socket。这样就无需调用多个系统调用来创建、绑定以及连接socket。一个socket对的使用方式通常与管道类似:一个进程创建socket对,然后创建一个其引用socket对的描述符的紫禁城,然后这两个进程就能够通过这个socket对进行通信了。
 
  Linux 特有的抽象 socket 名空间允许将一个 UNIX domain socket 绑定到一个不存在于文件系统中的名字上。
 

(编辑:开发网_新乡站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章