Unix环境高级编程学习笔记(十二) 高级进程间通信

本文深入探讨了UNIX域Socket的工作原理及应用,包括如何通过Socket进行进程间通信、创建和绑定Socket,以及如何通过Socket传递文件描述符等高级特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


基于流的管道(STREAMS-Based Pipes)

所谓基于流的管道实际上就是一种全双工管道,它必须在基于流的系统上才能实现,linux 默认对它是不支持的,而同样的逻辑,我们通常可以用基于 UNIX domain 的 socket 来实现,所以这里对它只作简单介绍。

关于流机制,我在 Unix环境高级编程学习笔记(九) 高级IO中曾经介绍过,知道可以在流首处加入处理模块,对于基于流的管道而言,管道的两端都是流首,所以可以在两端都加入处理模块,但同时,我们也只能在处理模块加入的一端删除它。

有时候,为了达到在不相关的进程间通信的目的,我们可以将这种管道的一端和某个实际的文件关联起来,也就相当于给了它一个名字,使用 fattach 函数:

[cpp]  view plain copy
  1. int fattach(int filedes, const char *path);  

调用进程必须拥有该文件,且对它具有写权限,或是调用进程具有超级用户的权限。而一旦流管道的一端和某个文件关联上后,该文件就不能再被访问了,任何对该文件执行的打开操作,都是获得管道的访问权,而不再是那个文件本身。不过,对于管道关联之前就已经打开了文件的进程,仍然可以继续访问该文件,而不必受管道的影响。

使用 fdetach 函数可以解除关联:

[cpp]  view plain copy
  1. int fdetach(const char *path);  

在该函数被调用以后,已经获得管道访问权的用户将继续拥有该管道的访问权,而之后打开该文件的进程获得的是对文件本身的访问权。

UNIX 域的 SOCKET

 Unix环境高级编程学习笔记(十一) 网络IPC:套接字中,我介绍了 socket 使用,关于 domain,一共可以有4种情况,今天,我们来看一下用于同一机器下不同进程间通信的 UNIX 域。UNIX 域同时支付流和数据报接口,不同于英特网域,这两种接口都是可靠的。

我们可以像使用普通的 socket 那样来使用它,在 UNIX 域的限定下,其地址格式由 sockaddr_un 数据结构来呈现。在Linux 2.4.22 以及 Solaris 9 上,sockaddr_un 的定义如下:

[cpp]  view plain copy
  1. struct sockaddr_un {  
  2.         sa_family_t     sun_family;/* AF_UNIX */  
  3.         char        sun_path[108];/* pathname */  
  4. };  

sun_path 成员指定了一个文件名,当将一个 UNIX domain socket 和该地址绑定在一起之后,系统将为我们创建一个 S_IFSOCK 类型的该文件。当该文件已经存在时,bind 函数将失败。当我们关闭该 socket 之后,文件不会自动被删除,需要我们手动 unlink 它。

需要注意以几点:

1. 在connect调用中指定的路径名必须是一个当前绑定在某个打开的Unix域套接口上的路径名。如果对于某个Unix域套接口的connect调用发现这个监听套接口的队列已满,调用就立即返回一个ECONNREFUSED错误。这一点不同于TCP,如果TCP监听套接口队列已满,TCP监听端就忽略新到达的SYN,而TCP连接发送端将数次发送SYN进行重试。

2. 在一个未绑定的Unix域套接口上发送数据报不会自动给这个套接口捆绑一个路径名,这一点与UDP套接口不同。所以在客户端,我们也必须显示地bind一个路径名到我们的套接口。

来看一个实际的例子。

服务端:

[cpp]  view plain copy
  1. /* 
  2.  *author: justaipanda 
  3.  *create time:2012/09/05 21:51:27 
  4.  */  
  5.   
  6. #include <stdio.h>  
  7. #include <stdlib.h>  
  8. #include <sys/socket.h>  
  9. #include <sys/un.h>  
  10.   
  11. #define FILE_NAME "1.txt"  
  12. #define BUF_SIZE 1024  
  13.   
  14. int serv_listen(const char* name) {  
  15.   
  16.     int fd;  
  17.     if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)  
  18.         return -1;  
  19.   
  20.     unlink(name);  
  21.     struct sockaddr_un un;  
  22.     memset(&un, 0, sizeof(un));  
  23.     un.sun_family = AF_UNIX;  
  24.     strcpy(un.sun_path, name);  
  25.     int len = (int)((void*)&(un.sun_path) - (void*)&un)   
  26.         + strlen(un.sun_path);  
  27.     if (bind(fd, (struct sockaddr*)&un, len) < 0) {  
  28.         close(fd);  
  29.         return -2;  
  30.     }  
  31.   
  32.     if (listen(fd, 128) < 0) {  
  33.         close(fd);  
  34.         return -3;  
  35.     }  
  36.   
  37.     return fd;  
  38. }  
  39.   
  40. int serv_accept(int listenfd) {  
  41.   
  42.     int fd;  
  43.     struct sockaddr_un un;  
  44.     int len = sizeof(un);  
  45.   
  46.     if ((fd = accept(listenfd, (struct sockaddr*)&un, &len)) < 0)   
  47.         return -1;  
  48.   
  49.     ((char*)(&un))[len] = '\0';  
  50.     unlink(un.sun_path);  
  51.   
  52.     return fd;  
  53. }  
  54.   
  55. int main() {  
  56.       
  57.     int fd;  
  58.     if ((fd = serv_listen(FILE_NAME)) < 0) {  
  59.         printf("listen error!\n");  
  60.         exit(fd);  
  61.     }  
  62.   
  63.     while(1) {  
  64.   
  65.         int sclient = serv_accept(fd);  
  66.         if (sclient < 0) {  
  67.             printf("accept error!\n");  
  68.             exit(sclient);  
  69.         }  
  70.   
  71.         char buffer[BUF_SIZE];  
  72.         ssize_t len = recv(sclient, buffer, BUF_SIZE, 0);  
  73.         if (len < 0) {  
  74.             printf("recieve error!\n");  
  75.             close(sclient);  
  76.             continue;  
  77.         }  
  78.   
  79.         buffer[len] = '\0';  
  80.         printf("receive[%d]:%s\n", (int)len, buffer);  
  81.         if (len > 0) {  
  82.             if('q' == buffer[0]) {  
  83.                 printf("server over!\n");  
  84.                 exit(0);  
  85.             }  
  86.                   
  87.             char* buffer2 = "I'm a server!";  
  88.             len = send(sclient, buffer2, strlen(buffer2), 0);  
  89.             if (len < 0)  
  90.                 printf("send error!\n");  
  91.         }  
  92.         close(sclient);  
  93.     }  
  94.   
  95.     return 0;  
  96. }  


客户端:

[cpp]  view plain copy
  1. /* 
  2.  *author: justaipanda 
  3.  *create time:2012/09/06 10:36:43 
  4.  */  
  5.   
  6. #include <stdio.h>  
  7. #include <stdlib.h>  
  8. #include <sys/socket.h>  
  9. #include <sys/un.h>  
  10. #include <fcntl.h>  
  11.   
  12. #define FILE_NAME "1.txt"  
  13. #define CLI_PATH "/var/tmp/"  
  14. #define BUF_SIZE 1024  
  15.   
  16. int cli_conn(const char* name) {  
  17.   
  18.     int fd;  
  19.     if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)  
  20.         return -1;  
  21.   
  22.     struct sockaddr_un un;  
  23.     memset(&un, 0, sizeof(un));  
  24.     un.sun_family = AF_UNIX;  
  25.     sprintf(un.sun_path, "%s%05d", CLI_PATH, getpid());  
  26.     unlink(un.sun_path);  
  27.     int len = (int)((void*)&(un.sun_path) - (void*)&un)   
  28.         + strlen(un.sun_path);  
  29.     if (bind(fd, (struct sockaddr*)&un, len) < 0) {  
  30.         close(fd);  
  31.         return -2;  
  32.     }  
  33.   
  34.     if(chmod(un.sun_path, S_IRWXU) < 0) {  
  35.         close(fd);  
  36.         return -3;  
  37.     }  
  38.   
  39.     memset(&un, 0, sizeof(un));  
  40.     un.sun_family = AF_UNIX;  
  41.     strcpy(un.sun_path, name);  
  42.     len = (int)((void*)&(un.sun_path) - (void*)&un)   
  43.         + strlen(un.sun_path);  
  44.     if (connect(fd, (struct sockaddr*)&un, len) < 0) {  
  45.         close(fd);  
  46.         return -4;  
  47.     }  
  48.   
  49.     return fd;  
  50. }  
  51.   
  52. int main() {  
  53.   
  54.     int fd;  
  55.     if ((fd = cli_conn(FILE_NAME)) < 0) {  
  56.         printf("connect error!\n");  
  57.         exit(fd);  
  58.     }  
  59.   
  60.     char buffer[BUF_SIZE];  
  61.     printf("input:");  
  62.     scanf("%s", buffer);  
  63.     int len = send(fd, buffer, strlen(buffer), 0);  
  64.     if (len < 0) {  
  65.         printf("send error!\n");  
  66.         exit(len);  
  67.     }  
  68.   
  69.     len = recv(fd, buffer, BUF_SIZE, 0);  
  70.     if (len < 0) {  
  71.         printf("recieve error!\n");  
  72.         exit(len);  
  73.     }  
  74.   
  75.     buffer[len] = '\0';  
  76.     printf("receive[%d]:%s\n", (int)len, buffer);  
  77.   
  78.     return 0;  
  79. }  

对于关联的进程,我们也可以使用 socketpair 函数简化工作:

[cpp]  view plain copy
  1. int socketpair(int domain, int type, int protocol, int sockfd[2]);  

这个函数的功能实际上就相当于创建了一个全双工的管道,其中,domain 只能被指定为 AF_UNIX。

传送文件描述符

许多时候,如果可以在进程间传递文件描述符,这将极大的简化我们程序的设计。

所谓传送文件描述符实际上是指——让接收进程打开一个文件描述符,该描述符的值不一定和发送进程发送的文件描述符相同,但它们都指向同一文件表。

如果发送进程关闭了文件描述符,这并不会真的关闭文件或设备,因为系统认为,接收进程仍然打开着文件,即使此时,接收进程可能还未收到该文件描述符。

传送文件描述符一般可以有两种方式:基于流的管道和 UNIX domain socket,对于前者,这里不多下文笔,主要讲后者。利用 UNIX domain socket 传递文件描述符需要使用前面讲过的 sendmsg 和 recvmsg 函数(参见Unix环境高级编程学习笔记(十一) 网络IPC:套接字)。利用 msghdr 结构体中的 msg_constrol 成员传递描述符。该成员指向 cmsghdr 结构:

[cpp]  view plain copy
  1. struct cmsghdr {  
  2.     socklen_t cmsg_len;/* data byte count, including header */  
  3.     int cmsg_level; /* originating protocol */  
  4.     int cmsg_type;/* protocol-specific type */  
  5.     /* followed by the actual control message data */  
  6. };  

为了发送文件描述符,将 cmsg_len 设置为 cmsghdr 结构的长度再加上一个整型(描述符)的长度,cmsg_level 字段设置为 SOL_SOCKET,cmsg_type 字段设置为 SCM_RIGHTS,用以指明我们在传送访问权限。

对于这个结构体的操作宏:

[cpp]  view plain copy
  1. unsigned char *CMSG_DATA(struct cmsghdr *cp);  
  2. //Returns: pointer to data associated with cmsghdr structure  
  3.   
  4. struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *mp);  
  5. //Returns: pointer to first cmsghdr structure associated with the msghdr structure, or NULL if none exists  
  6.   
  7. struct cmsghdr *CMSG_NXTHDR(struct msghdr *mp, struct cmsghdr *cp);  
  8. //Returns: pointer to next cmsghdr structure associated with the msghdr structure given the current cmsghdr structure, or NULL if we're at the last one  
  9.   
  10. unsigned int CMSG_LEN(unsigned int nbytes);  
  11. //Returns: size to allocate for data object nbytes large  

下面看一个使用 UNIX domain socket 传递文件描述符的例子,该例子是由前面的例子修改而来,其中 serv_listen,serv_accept,以及 cli_conn 函数和原来一样,就不再重复了。

服务端:

[cpp]  view plain copy
  1. int send_fd(int fd, int fd_to_send) {  
  2.   
  3.     struct msghdr msg;  
  4.     msg.msg_name = NULL;  
  5.     msg.msg_namelen = 0;  
  6.   
  7.     struct cmsghdr *cmptr = NULL;  
  8.     char buffer[BUF_SIZE];  
  9.     struct iovec iov;  
  10.   
  11.     if (fd_to_send >= 0) {  
  12.         int cmsg_len = CMSG_LEN(sizeof(int));  
  13.         cmptr = malloc(cmsg_len);  
  14.   
  15.         cmptr->cmsg_level = SOL_SOCKET;  
  16.         cmptr->cmsg_type = SCM_RIGHTS;  
  17.         cmptr->cmsg_len = cmsg_len;  
  18.         *(int*)CMSG_DATA(cmptr) = fd_to_send;  
  19.   
  20.         msg.msg_control = cmptr;  
  21.         msg.msg_controllen = cmsg_len;  
  22.   
  23.         sprintf(buffer, "OK!");  
  24.     }  
  25.     else {  
  26.   
  27.         if (-1 == fd_to_send)  
  28.             sprintf(buffer, "cannot open file!");  
  29.         else  
  30.             sprintf(buffer, "wrong command!");  
  31.           
  32.         msg.msg_control = NULL;  
  33.         msg.msg_controllen = 0;  
  34.     }  
  35.   
  36.     msg.msg_iov = &iov;  
  37.     msg.msg_iovlen = 1;  
  38.     iov.iov_base = buffer;  
  39.     iov.iov_len = strlen(buffer);  
  40.   
  41.     sendmsg(fd, &msg, 0);  
  42.     if (cmptr)  
  43.         free(cmptr);  
  44.     return 0;  
  45. }  
  46.   
  47. int main() {  
  48.       
  49.     int fd;  
  50.     if ((fd = serv_listen(FILE_NAME)) < 0) {  
  51.         printf("listen error!\n");  
  52.         exit(fd);  
  53.     }  
  54.   
  55.     while(1) {  
  56.   
  57.         int sclient = serv_accept(fd);  
  58.         if (sclient < 0) {  
  59.             printf("accept error!\n");  
  60.             exit(sclient);  
  61.         }  
  62.   
  63.         char buffer[BUF_SIZE];  
  64.         ssize_t len = recv(sclient, buffer, BUF_SIZE, 0);  
  65.         if (len < 0) {  
  66.             printf("recieve error!\n");  
  67.             close(sclient);  
  68.             continue;  
  69.         }  
  70.   
  71.         buffer[len] = '\0';  
  72.         printf("receive[%d]:%s\n", (int)len, buffer);  
  73.         if (len > 0) {  
  74.             if('q' == buffer[0]) {  
  75.                 printf("server over!\n");  
  76.                 exit(0);  
  77.             }  
  78.             else if ('f' == buffer[0]) {  
  79.                 int new_fd = open(buffer + 2, O_RDWR);  
  80.                 send_fd(sclient, new_fd);  
  81.             }  
  82.             else  
  83.                 send_fd(sclient, -2);  
  84.         }  
  85.         close(sclient);  
  86.     }  
  87.   
  88.     return 0;  
  89. }  

客户端:

[cpp]  view plain copy
  1. int recv_fd(int fd, char *buffer, size_t size) {  
  2.   
  3.     struct cmsghdr *cmptr;  
  4.     int cmsg_len = CMSG_LEN(sizeof(int));  
  5.     cmptr = malloc(cmsg_len);  
  6.   
  7.     struct iovec iov;  
  8.     iov.iov_base = buffer;  
  9.     iov.iov_len = size;  
  10.   
  11.     struct msghdr msg;  
  12.     msg.msg_name = NULL;  
  13.     msg.msg_namelen = 0;  
  14.     msg.msg_iov = &iov;  
  15.     msg.msg_iovlen = 1;  
  16.     msg.msg_control = cmptr;  
  17.     msg.msg_controllen = cmsg_len;  
  18.   
  19.     int len = recvmsg(fd, &msg, 0);  
  20.     if (len < 0) {  
  21.         printf("receve message error!\n");  
  22.         exit(0);  
  23.     }  
  24.     else if (len == 0) {  
  25.         printf("connection closed by server!\n");  
  26.         exit(0);  
  27.     }  
  28.   
  29.     buffer[len] = '\0';  
  30.     int cfd = -1;  
  31.     if (cmptr->cmsg_type != 0)  
  32.         cfd = *(int*)CMSG_DATA(cmptr);  
  33.     free(cmptr);  
  34.     return cfd;  
  35. }  
  36.   
  37. int main() {  
  38.   
  39.     int fd;  
  40.     if ((fd = cli_conn(FILE_NAME)) < 0) {  
  41.         printf("connect error!\n");  
  42.         exit(fd);  
  43.     }  
  44.   
  45.     char buffer[BUF_SIZE];  
  46.     printf("input:");  
  47.     fgets(buffer, BUF_SIZE, stdin);  
  48.     buffer[strlen(buffer) - 1] = '\0';  
  49.     int len = send(fd, buffer, strlen(buffer), 0);  
  50.     if (len < 0) {  
  51.         printf("send error!\n");  
  52.         exit(len);  
  53.     }  
  54.   
  55.     int cfd = recv_fd(fd, buffer, BUF_SIZE);  
  56.     printf("data:%s\n", buffer);  
  57.     if (cfd >= 0) {  
  58.         printf("received open file:%d\n", cfd);  
  59.     }  
  60.   
  61.     return 0;  
  62. }  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值