• 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. }
  • 相关阅读:
    redis 订阅者与发布者(命令行)
    CentOS 6 使用 tptables 打开关闭防火墙与端口
    CentOS 7 使用 firewalld 打开关闭防火墙与端口
    Python面向对象编程-OOP
    python命名规则 PEP8编码规则(约定俗成)
    python 装饰器 概念
    python常用模块 os,datetime,time,MySQLdb,hashlib
    python xml.etree.ElementTree 处理xml 文件 变量 流 xml概念
    Pycharm小技巧
    python概要笔记2
  • 原文地址:https://www.cnblogs.com/alantu2018/p/8466190.html
Copyright © 2020-2023  润新知