Unix环境高级编程(十八)高级进程间通信

  本章主要介绍了基于STREAM的管道和UNIX域套接字,这些IPC可以在进程间传送打开文件描述符。服务进程可以使用它们的打开文件描述符与指定的名字相关联,客户进程可以使用这些名字与服务器进程通信。

1、基于STREAMS的管道

  STREAMS pipe是一个双向(全双工)管道,单个STREAMS管道就能向父、子进程提供双向的数据流。如下图所示:

下面采用STREAMS管道实现加法协同进程实例,程序如下:

 1  1 #include <stdio.h>
 2  2 #include <stdlib.h>
 3  3 #include <unistd.h>
 4  4 #include <errno.h>
 5  5 #include <string.h>
 6  6 #include <signal.h>
 7  7 
 8  8 #define MAXLINE 1024
 9  9 
10 10 static void sig_pipe(int signo)
11 11 {
12 12     printf("SIGPIPE caught
");
13 13     exit(1);
14 14 }
15 15 int s_pipe(int fd[2])
16 16 {
17 17     return pipe(fd);
18 18 }
19 19 int main()
20 20 {
21 21     int     n;
22 22     int     fd[2];
23 23     pid_t   pid;
24 24     char    line[MAXLINE];
25 25 
26 26     signal(SIGPIPE,sig_pipe);
27 27     s_pipe(fd);
28 28     if((pid = fork()) == -1)
29 29     {
30 30         perror("fork() error");
31 31         exit(-1);
32 32     }
33 33     if(pid == 0)  //子进程用fd[1]
34 34     {
35 35         close(fd[0]);
36 36         dup2(fd[1],STDIN_FILENO);
37 37         dup2(fd[1],STDOUT_FILENO);
38 38         execl(".//add","add",(char *)0);
39 39         exit(0);
40 40     }
41 41     else   //父进程用fd[0]
42 42     {
43 43         close(fd[1]);
44 44         while(fgets(line,MAXLINE,stdin) != NULL)
45 45         {
46 46             n = strlen(line);
47 47             if(write(fd[0],line,n) != n)
48 48             {
49 49                 perror("write() error to pipe");
50 50                 exit(-1);
51 51             }
52 52             if((n =read(fd[0],line,MAXLINE)) < 0)
53 53             {
54 54                 perror("read() error to pipe");
55 55                 exit(-1);
56 56             }
57 57             if(n==0)
58 58             {
59 59                 printf("child close pipe
");
60 60                 break;
61 61             }
62 62             line[n] = '';
63 63             fputs(line,stdout);
64 64         }
65 65         exit(0);
66 66     }
67 67 }

 在APUE2 P469中这样讲:“solaris支持STREAMS管道,Linux的可选附加包也提供了STREAMS管道。”这个包没有安装上,程序不能正常运行。

1.2命名的STREAMS管道

  管道仅在相关进程之间使用,例如子进程集成父进程的管道。无关进程可以使用FIFO进行通信,但是这仅仅提供单向通信。STREAMS机制提供了一种有效的途径,使得进程可以给予管道一个文件系统的名字,避免了单向FIFO的问题。操作函数原型如下:

#include <stropts.h>
int fattach(int fildes, const char *path);  //给STREAMS管道一个系统文件中的名字
int fdetach(const char *path); //撤销STREAMS管道文件与文件系统中名字的关联

2、UNIX域套接字

  UNIX域套接字用于在同一台机器上运行的进程之间的通信,UNIX域套接字仅仅复制数据,不执行协议处理,不需要添加或删除网络报头,无需计算校验和,不要产生顺序号,无需发送确认报文。UNIX域套接字是套接字和管道之间的结合物,提供流和数据报两种接口。

UINX域套接字的好处:

(1)在同一台主机上进行通信时,是不同主机间通信的两倍

(2)UINX域套接口可以在同一台主机上,不同进程之间传递套接字描述符

(3)UINX域套接字可以向服务器提供客户的凭证(用户id或者用户组id)

UINX域套接字使用的地址通常是文件系统中一个文件路径,这些文件不是不同的文件,只能作为域套接字通信,不能读写。创建函数如下:

#include <sys/socket.h>
int socketpair(int domain, int type, int protocolint " sv [2]);

利用UNIX域实现全双工管道,程序如下:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <errno.h>
 5 #include <string.h>
 6 #include <signal.h>
 7 #include <sys/socket.h>
 8 #define MAXLINE 1024
 9 
10 static void sig_pipe(int signo)
11 {
12     printf("SIGPIPE caught
");
13     exit(1);
14 }
15 int s_pipe(int fd[2])
16 {
17     return socketpair(AF_UNIX,SOCK_STREAM,0,fd);  //创建UNIX域套接字
18 }
19 int main()
20 {
21     int     n;
22     int     fd[2];
23     pid_t   pid;
24     char    line[MAXLINE];
25 
26     signal(SIGPIPE,sig_pipe);
27     s_pipe(fd);
28     if((pid = fork()) == -1)
29     {
30         perror("fork() error");
31         exit(-1);
32     }
33     if(pid == 0)
34     {
35         close(fd[0]);
36         dup2(fd[1],STDIN_FILENO);
37         dup2(fd[1],STDOUT_FILENO);
38         execl(".//add","add",(char *)0);
39         exit(0);
40     }
41     else
42     {
43         close(fd[1]);
44         while(fgets(line,MAXLINE,stdin) != NULL)
45         {
46             n = strlen(line);
47             if(write(fd[0],line,n) != n)
48             {
49                 perror("write() error to pipe");
50                 exit(-1);
51             }
52             if((n =read(fd[0],line,MAXLINE)) < 0)
53             {
54                 perror("read() error to pipe");
55                 exit(-1);
56             }
57             if(n==0)
58             {
59                 printf("child close pipe
");
60                 break;
61             }
62             line[n] = '';
63             fputs(line,stdout);
64         }
65         exit(0);
66     }
67 }

add是单独的程序,共程序调用,执行结果如下:

2.1命令UNIX域套接字

  UNIX域套接字的地址有sockaddr_run结构表示。

  #define UNIX_PATH_MAX 108
  struct sockaddr_un {
    sa_family_t sun_family; /* AF_UNIX */
    char sun_path[UNIX_PATH_MAX]; /* pathname };

写个程序,将一个地址绑定一UNIX域套接字,程序如下:

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stddef.h>
int main()
{
    int fd,size;
    struct sockaddr_un  un;
    un.sun_family = AF_UNIX;
    strcpy(un.sun_path,"foo.socket");
    fd = socket(AF_UNIX,SOCK_STREAM,0);
    size = offsetof(struct sockaddr_un,sun_path) + strlen(un.sun_path);
    if(bind(fd,(struct sockaddr*)&un,size)<0)
    {
        perror("bind() error");
        exit(-1);
    }
    printf("UNIX domain socket bound
");
    exit(0);
}
复制代码

程序执行结果如下:

从结果可以看出,如果绑定地址时候,文件已经存在,那么bind请求将会失败,关闭套接字时,并不删除该文件。因此必须保证在应用程序终止前,对该文件进行解除链接操作。

2.2唯一连接

  服务器进程可以使用标准bind、listen和accept函数为客户进程安排一个唯一的UNIX域连接,客户进程使用connect与服务器进程联系,服务器进程接受了connect请求后,在服务器进程和客户进程之间就存在了唯一的连接。

参考

http://blog.csdn.net/youkuxiaobin/article/details/6965527 

http://bbs.chinaunix.net/thread-2183106-1-1.html

unix域套接字

unix域套接字实际上不是一个实际的协议,他只是在同一台主机上客户和服务器之间通信时,使用与在不同主机上客户和服务器间通信时相同的API   

unix域套接字分为两种:字节流套接字和数据报套接字

unix域套接字的好处:

1 在同一台主机上进行通信时,是不同主机间通信的两倍

2 unix域套接口可以在同一台主机上,不同进程之间传递套接字描述符

3 unix域套接字可以向服务器提供客户的凭证(用户id或者用户组id)

unix域套接字使用的地址通常是文件系统中一个文件路径(套接口文件:APUE中的4.3节文件类型,是以s开头的),这些文件不是不同的文件,只能作为域套接字通信,不能读写


并且是以s开头的文件

unix域套接字的地址结构是:

  1. struct sockaddr_un  
  2. {  
  3.     uint8_t sun_len;  
  4.     sa_family_sun_family;//AF_LOCAL  
  5.     char sun_path[104];//必须是以空结尾的字符串(路径+文件名)  
  6. }  

int socketpair(int family, int type, intprotocol, int sockfd[2]);

这个函数创建两个互相连接的套接字(socketfd[2])family 是AF_LOCAL, type可以是SOCK_STREAM (字节流)或者SOCK_DGRAM(数据报),协议是0,之后就能够或者两个互相连接的套接字

以SOCK_STREAM调用的socketpair函数得到的套接字叫做流管道(stream pipe),是全双工的,就是这两个套接字是可读可写的

1在unix域套接字进行bind的时候建立套接口文件,其默认的权限值是0777,并被当前的umask修改,看上图就知道,umask是0022 的到的文件权限是0755

2关于bind创建文件中地址参数 sockaddr_un 中的sun_path需要是绝对路径,这样才能不用考虑相对的概念。防止客户端程序也用相对路径,但是和服务器不在同一个目录的考虑

3 connect中的地址中的路径名必须是套接口文件,而且已经被服务端绑定的,以下情况会出错:1 文件路径存在但是不是套接口文件2 路径名存在且是套接口文件,但是没有和该文件绑定的套接口3 就是type必须和服务器相同

4 connect连接unix套接口的时候的权限检查和open函数一样的

5 unix域字节流套接口和TCP一样都给进程提供一个无记录边界的字节流接口

6 unix域套接口connect发现等待队列满了,就直接返回ECONNREFUSRD错误,说连接拒绝错误

7 unix域数据报套接口和UDP一样提供一个保留记录边界的不可靠数据报服务

unix域套接字的客户端:

  1. #include <sys/un.h>  
  2. #include <stdio.h>  
  3. #include <errno.h>  
  4. #include <stdlib.h>  
  5. #include <sys/types.h>  
  6. #include <sys/socket.h>  
  7.   
  8. #define MAX_SEND 1025  
  9. #define UNIX_PATH "/tmp/sinfor"  
  10.   
  11. void dump_unix(int sock_fd)  
  12. {  
  13.     char tmp[MAX_SEND] = {0};  
  14.     char recv[MAX_SEND] = {0};  
  15.     while(fgets(tmp, MAX_SEND, stdin) != NULL)  
  16.     {  
  17.         write(sock_fd, tmp, strlen(tmp));  
  18.         read(sock_fd, recv, MAX_SEND);  
  19.         printf("data : %s ", recv);  
  20.         bzero(tmp,MAX_SEND);  
  21.         bzero(recv, MAX_SEND);  
  22.     }  
  23. }  
  24. int main(int argc, char** argv)  
  25. {  
  26.     int conn_sock = socket(AF_LOCAL, SOCK_STREAM, 0);  
  27.     if(conn_sock == -1)  
  28.     {  
  29.         perror("socket fail ");  
  30.         return -1;  
  31.     }  
  32.     struct sockaddr_un addr;  
  33.     bzero(&addr, sizeof(addr));  
  34.     addr.sun_family = AF_LOCAL;  
  35.     strcpy((void*)&addr.sun_path, UNIX_PATH);  
  36.     if(connect(conn_sock, (struct sockaddr*)&addr, sizeof(addr)) < 0)  
  37.     {  
  38.         perror("connect fail ");  
  39.         return -1;  
  40.     }  
  41.     dump_unix(conn_sock);  
  42.     close(conn_sock);  
  43.     return 0;  
  44. }  

unix域套接字的服务器;

  1. #include <sys/un.h>  
  2. #include <stdio.h>  
  3. #include <signal.h>  
  4. #include <errno.h>  
  5. #include <stdlib.h>  
  6. #include <sys/types.h>  
  7. #include <sys/wait.h>  
  8. #include <sys/socket.h>  
  9.   
  10. #define MAX_RECV 1025  
  11. #define UNIX_SERV_PATH "/tmp/sinfor"  
  12. void client_dump(int sock_fd)  
  13. {  
  14.     char rec[MAX_RECV] = {0};  
  15.     int size;  
  16.     while((size = read(sock_fd, rec, MAX_RECV)) != 0)  
  17.     {  
  18.         printf("**********1111************ ");  
  19.         printf("recv data is %s ", rec);  
  20.         write(sock_fd, rec, size);  
  21.     }  
  22. }  
  23. void sig_son(int num)  
  24. {  
  25.     printf("son is %d ", getpid());  
  26.     wait(NULL);  
  27.     //return NULL;  
  28. }  
  29. int main(int argc, char** argv)  
  30. {  
  31.     int acc_sock, dump_sock;  
  32.     acc_sock = socket(AF_LOCAL, SOCK_STREAM, 0);  
  33.     if(acc_sock == -1)  
  34.     {  
  35.         perror("socket func fail ");  
  36.         return -1;  
  37.     }  
  38.     struct sockaddr_un ser_addr, cli_addr;  
  39.     ser_addr.sun_family = AF_LOCAL;  
  40.     strcpy(ser_addr.sun_path, UNIX_SERV_PATH);  
  41.     unlink(UNIX_SERV_PATH);  
  42.     bind(acc_sock, (struct sockaddr*)&ser_addr, sizeof(ser_addr));  
  43.     listen(acc_sock, 5);  
  44.     signal(SIGCHLD, sig_son);  
  45.     while(1)  
  46.     {  
  47.         int len = sizeof(cli_addr);  
  48.         dump_sock = accept(acc_sock, (struct sockaddr*)&cli_addr, &len);  
  49.         if(dump_sock == -1)  
  50.         {  
  51.             if(errno == EINTR)  
  52.             {  
  53.                 continue;  
  54.             }  
  55.             else  
  56.             {  
  57.                 perror("accept fail");  
  58.                 return -1;  
  59.             }  
  60.         }  
  61.         if(fork() == 0)  
  62.         {  
  63.             close(acc_sock);  
  64.             client_dump(dump_sock);       
  65.             close(dump_sock);  
  66.             exit(0);  
  67.         }  
  68.         close(dump_sock);  
  69.     }  
  70.     close(acc_sock);  
  71.     return 0;  
  72. }  

使用Unix域套接字实现进程间通讯

  1. /*
  2. domain_socket.h
  3. @Author: duanjigang @2006-4-11
  4. @Desp: declaratin of methods used for unix-domain-socket communication
  5. */
  6. #ifndef _H_
  7. #define _H_
  8. #include <stdio.h>
  9. #include <unistd.h>
  10. #include <sys/un.h>
  11. #include <sys/socket.h>
  12. #define MSG_SIZE 1024
  13. int init_send_socket(struct sockaddr_un * addr,char * path)
  14. {
  15.         int sockfd,len;
  16.         sockfd=socket(AF_UNIX,SOCK_DGRAM,0);
  17.         if(sockfd<0)
  18.         {
  19.                 exit(1);
  20.         }
  21.         bzero(addr,sizeof(struct sockaddr_un));
  22.         addr->sun_family=AF_UNIX;
  23.         strcpy(addr->sun_path,path);
  24.         return sockfd;
  25. }
  26. int init_recv_socket(char * path)
  27. {
  28.         int sockfd,len;
  29.         struct sockaddr_un addr;
  30.         sockfd=socket(AF_UNIX,SOCK_DGRAM,0);
  31.         if(sockfd<0)
  32.         {
  33.            return -1;
  34.         }
  35.         bzero(&addr,sizeof(struct sockaddr_un));
  36.         addr.sun_family = AF_UNIX;
  37.         strcpy(addr.sun_path, path);
  38.         unlink(path);
  39.         len = strlen(addr.sun_path) + sizeof(addr.sun_family);
  40.         if(bind(sockfd,(struct sockaddr *)&addr,len)<0)
  41.          {
  42.           return -1;
  43.          }
  44.         return sockfd;

  45. }
  46. int receive_from_socket(int sockfd, char msg[])
  47. {
  48.       int n;
  49.       memset(msg, 0, MSG_SIZE);
  50.       n=recvfrom(sockfd, msg, MSG_SIZE, 0, NULL, NULL);
  51.       if(n<=0)
  52.       {
  53.        return -1;
  54.       }
  55.       msg[n]=0;
  56.       return n;
  57. }
  58. int send_to_socket(int sockfd, char msg[], const struct sockaddr_un * addr)
  59. {
  60.         int len;
  61.         len = strlen(addr->sun_path)+sizeof(addr->sun_family);
  62.         sendto(sockfd, msg, strlen(msg), 0, (struct sockaddr*)addr,len);
  63.         return 1;
  64. }
  65. #endif
 
一个调用的例子
  1. /*
  2. main.c
  3. @Author: duanjigang @ 2006-4-11
  4. @Desp: Two processes communicate with unix domain socket
  5. */
  6. #include "domain_socket.h"
  7. #define PATH "/home/useless"
  8. /*
  9. 进程间通过域进行通讯-举例:父子进程,一个发送,一个接收
  10. */
  11. int main(void)
  12. {
  13.   int pid;
  14.   /*
  15.   子进程用于发送消息
  16.   */
  17.   if((pid = fork()) == 0)
  18.   {
  19.     int fd, counter = 0;
  20.     char send_buffer[MSG_SIZE];
  21.     struct sockaddr_un addr;
  22.    if( (fd = init_send_socket(&addr, PATH)) > 0)
  23.     while(1)
  24.     {
  25.        memset(send_buffer, 0 , MSG_SIZE);
  26.        /*
  27.            防止计数器越界,所以做一个复位判断
  28.            */
  29.            sprintf(send_buffer,"message for %d times",
  30.        counter++ >= 10000 ? 1 : counter);
  31.        send_to_socket(fd, send_buffer, &addr);
  32.        printf("Sender: %s ", send_buffer);
  33.        sleep(1);
  34.     }
  35.   }/*
  36.    父进程用于接收消息
  37.   */
  38.   else
  39.   {
  40.       int fd;
  41.       char recv_buffer[MSG_SIZE];
  42.       if( (fd = init_recv_socket(PATH))> 0)
  43.       while(1)
  44.       {
  45.        memset(recv_buffer, 0, MSG_SIZE);
  46.        if(receive_from_socket(fd, recv_buffer))
  47.        {
  48.         printf("Receiver: %s ", recv_buffer);
  49.        }
  50.       }
  51.   }
  52. }
原文地址:https://www.cnblogs.com/alantu2018/p/8466190.html