多进程编程之进程间通信

  进程间通信(Interprocess Communication, IPC),经典的IPC:管道、FIFO、消息队列、信号量以及共享存储和套接字。

  一、管道

  管道是UNIX系统IPC的最古老的形式,所有的UNIX系统都提供此种通信机制。

  1·、两个局限性:

    (1)半双工,数据只能在一个方向流动,现在有些系统可以支持全双工管道,但是为了最佳的可移植性,应认为系统不支持全双工管道;

    (2)管道只能在具有公共祖先之间的两个进程之间使用;

  2、管道的创建:

   它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。管道是通过调用pipe函数创建的。

1 #include <unistd.h>
2 
3 int pipe(int fd[2]);
4 
5                     //返回值:若成功,返回0,若出错,返回-1.

  经由参数fd返回的两个文件描述符:fd[0]为读而打开,fd[1]为写而打开,fd[1]的输出是fd[0]的输入。通常,进程会先调用pipe,接着调用fork,从而创建了父进程与子进程的IPC通道。fork之后做什么取决于我们想要的数据流的方向,对于从父进程到子进程,父进程关闭管道的读端fd[0],子进程关闭写端fd[1]。

  3、关闭管道的一端

    (1)当读一个写端被关闭的管道时,在所有数据都被读取后,read返回0,表示文件结束;

    (2)当写一个读端被关闭的管道时,则产生信号SIGPIPE,如果忽略该信号或者捕捉该信号并从其处理程序返回,则wirte返回-1.

  关闭写端:

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <stdlib.h>
 4 #include <signal.h>
 5 void sig_pipe(int signal)
 6 {
 7     printf("catch the SIGPIPE signal\n");
 8 }
 9 int main()
10 {
11     int n;
12     int fd[2];
13     int count = 0;
14     char buf[100] = {0};
15 16     if(pipe(fd) < 0)
17     {
18         perror("Fail to create pipe");
19         exit(EXIT_FAILURE);
20     }
21     //close(fd[0]);
22      
23     if ((n = write(fd[1], "hello world", 20)) < 0) {
24         perror("Write error");
25         exit(EXIT_FAILURE);
26     }
27    
28    close(fd[1]);
29 #if 1   
30     if((n = read(fd[0],buf,sizeof(buf))) < 0)
31     {
32         perror("Fail to read pipe");
33         exit(EXIT_FAILURE);
34     }
35 
36     printf("Rread %d bytes : %s.\n",n,buf);
37 #endif
38     return 0;
39 }
执行结果:
Rread 20 bytes : hello world.

  关闭读端: 

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <stdlib.h>
 4 #include <signal.h>
 5 void sig_pipe(int signal)
 6 {
 7     printf("catch the SIGPIPE signal\n");
 8 }
 9 int main()
10 {
11     int n;
12     int fd[2];
13     int count = 0;
14     char buf[100] = {0};
15     signal(SIGPIPE,sig_pipe);
16     if(pipe(fd) < 0)
17     {
18         perror("Fail to create pipe");
19         exit(EXIT_FAILURE);
20     }
21     close(fd[0]);
22      
23     if ((n = write(fd[1], "hello world", 20)) < 0) {
24         perror("Write error");
25         exit(EXIT_FAILURE);
26     }
27    
28    //close(fd[1]);
29 #if 0   
30     if((n = read(fd[0],buf,sizeof(buf))) < 0)
31     {
32         perror("Fail to read pipe");
33         exit(EXIT_FAILURE);
34     }
35 
36     printf("Rread %d bytes : %s.\n",n,buf);
37 #endif
38     return 0;
39 }
执行结果:

catch the SIGPIPE signal
Write error: Broken pipe

  在写管道时,常量PIPE_BUF规定了内核管道的缓冲区大小,如果对管道调用write,而且要求写的字节数小于等于PIPE_BUF,则此操作不会与其他进程对同一管道的write操作交叉进行,如果多个进程对同一管道写的字节数超过PIPE_BUF,所写的数据可能会与其他进程所写的数据相互交叉。用pathconf或fpathconf函数可以获得PIPE_BUF的值。

 二、有名管道FIFO

  未命名的管道只能在两个相关的进程之间通信,通过有名管道FIFO,不相关的进程也能交换数据。FIFO不同于管道之处在于它提供一个路径与之关联,以FIFO的文件形式存在于系统中。

  1、有名管道的创建:

1 #include <sys/stat.h>
2 
3 int mkfifo(const char *path, mode_t mode);
4 
5                                     //返回值:若成功,返回0;若出错,返回-1。参数pathname为路径名,创建管道的名字,mode为创建fifo的权限。

  2、有名管道的读写规则 

  A.从FIFO中读取数据
 
  约定:如果一个进程为了从FIFO中读取数据而以阻塞的方式打开FIFO, 则称内核为该进程的读操作设置了阻塞标志
 
  <1>如果有进程为写而打开FIFO,且当前FIFO内没有数据,则对于设置了阻塞标志的读操作来说,将一直阻塞。对于没有设置阻塞标志读操作来说返回-1,当前errno值为EAGAIN,提醒以后再试。
 
  <2>对于设置阻塞标志的读操作说,造成阻塞的原因有两种:当前FIFO内有数据,但有其他进程正在读这些数据;另外就是FIFO内没有数据。解阻塞的原因则是FIFO中有新的数据写入,不论写入数据量的大小,也不论读操作请求多少数据量。
 
  <3>如果没有进程写打开FIFO,则设置了阻塞标志的读操作会阻塞
 
  <4>如果写端关闭,管道中有数据读取管道中的数据,如果管道中没有数据读端将不会继续阻塞,此时返回0。
 
  注意:如果FIFO中有数据,则设置了阻塞标志的读操作不会因为FIFO中的字节数小于请求读的字节数而阻塞,此时,读操作会返回FIFO中现有的数据量。
 
  B.向FIFO中写入数据
 
  约定:如果一个进程为了向FIFO中写入数据而阻塞打开FIFO,那么称该进程内的写操作设置了阻塞标志。
 
  对于设置了阻塞标志的写操作:
 
  <1>当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,直到当缓冲区中能够容纳写入的字节数时,才开始进行一次性写操作。
 
  <2>当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性。FIFO缓冲区一有空闲区域,写进程就会试图向管道写入数据,写操作在写完所有请求写的数据后返回。
 
  对于没有设置阻塞标志的写操作:
 
  <1>当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。在写满所有FIFO空闲缓冲区后,写操作返回。
 
  <2>当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写。

  读有名管道:

 1 #include <stdio.h>
 2 #include <sys/stat.h>
 3 #include <fcntl.h>
 4 #include <string.h>
 5 #include <sys/types.h>
 6 int main(int argc, char **argv)
 7 {
 8     int fd;
 9     char buf[100] = {0};
10     int n = 0;
11     //if (mkfifo("/tmp/myfifo", 0777) < 0) {
12     //    printf("create fifo failed\n");
13     //    return 0;
14     //}
15     printf("before open\n");
16     fd = open("/tmp/myfifo", O_RDONLY|O_NONBLOCK);//非阻塞方式打开有名管道
17     if (fd < 0) {
18         printf("open error\n");
19         return 0;
20     }
21     printf("after open\n");
22     while (1) {
23         memset(buf, 0, sizeof(buf));
24         if ((n = read(fd, buf, 100)) == 0) {
25             printf("1: n = %d\n", n);
26             printf("no data in fifo\n");
27             sleep(1);
28         } else {
29             printf("2: n = %d\n", n);
30             printf("get data: %s\n",buf);
31             sleep(1);
32         }
33     }
34     close(fd);
35     return 0;
36 }

  写有名管道: 

#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
int main(int argc, char **argv)
{
    int fd;
    char buf[100] = {0};
    int n = 0;
    if(access("/tmp/myfifo", F_OK) != 0){
        if (mkfifo("/tmp/myfifo", 0777) < 0) {
            printf("create fifo failed\n");
            return 0;
        }
    }
    printf("before open\n");
    fd = open("/tmp/myfifo", O_WRONLY);//以阻塞方式打开有名管道
    if (fd < 0) {
        printf("open error\n");
        return 0;
    }
    printf("after open\n");
#if 0
    while (1) {
        memset(buf, 0, sizeof(buf));
        if ((n = read(fd, buf, 100)) == 0) {
            printf("n = %d\n", n);
            printf("no data in fifo\n");
            sleep(1);
        } else {
            printf("         n = %d\n", n);
            printf("get data: %s\n",buf);
            sleep(1);
        }
    }
#endif
    n = write(fd, "abc123", 6);
    printf("n = %d\n", n);
    close(fd);
    return 0;
}

  阻塞与非阻塞的总结:

  (1):写进程阻塞打开有名管道,读进程阻塞打开有名管道

    先运行写进程,阻塞,在运行读进程,写进程不再阻塞,读进程不阻塞;

    先运行读进程,阻塞,在运行写进程,读进程不再阻塞,写进程不阻塞。

  (2):读写进程都非阻塞打开有名管道

    先运行写进程,写进程打开管道失败;

    先运行读进程,读进程读取不到内容,退出,如果循环读取,当写进程运行后,可读到写进程写入的内容;

  (3):写进程阻塞打开有名管道,读进程非阻塞打开有名管道

    先运行写进程,写进程阻塞,在运行读进程,运行正常;

    先运行读进程,如果读进程不是循环读,则退出,再运行写进程,写进程阻塞;

  (4):写进程非阻塞打开有名管道,读进程阻塞打开有名管道

    先运行写进程,写进程打开管道失败;

    先运行读进程,读进程阻塞,再运行写进程,运行正常;

  三、消息队列

    1、消息队列的特点

      1)消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识.
        2)消息队列允许一个或多个进程向它写入与读取消息.
        3)管道和命名管道都是通信数据都是先进先出的原则,消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取.比FIFO更有优势

    2、相关函数 

     1)获得key值,因为消息队列独立于进程而存在,为了区别不同的消息队列,需要以key值标记消息队列,这样两个不相关的进程可以通过事先约定的key值通过消息队列进行消息收发。

 1 #include <sys/types.h>
 2 #include <sys/ips.h>
 3 
 4 key_t futon(char *pathname, int projid)
 5 参数:
 6     pathname:文件名(含路径),通常设置为当前目录“.” 
 7     projid:子序号,虽然为int类型,但只使用了低8位,通常用一个字母表示
 8 返回值:
 9     成功返回key值,失败返回-110 
11 man手册:
12      The ftok() function attempts to create a unique key suitable for use with the semget(2), and shmget(2) functions, given the path
13      of an existing file and a user-selectable id.
14 
15      The specified path must specify an existing file that is accessible to the calling process or the call will fail.  Also, note
16      that links to files will return the same key, given the same id.

    2)创建或打开消息队列

 1 #include <sys/types.h>
 2 #include <sys/ipc.h>
 3 #include <sys/msg.h>
 4 
 5 int msgget(key_t key, int msgflag)
 6 /*
 7 功能:
 8     用于创建一个新的或打开一个已经存在的消息队列,此消息队列与key相对应。
 9 参数:
10     key:函数ftok的返回值或IPC_PRIVATE,为IPC_PRIVATE时表示创建自己的消息队列,这样就只能用于和自己有亲缘关系的进程间通信。
11     msgflag:
12         IPC_CREAT:创建新的消息队列。
13         IPC_EXCL:与IPC_CREAT一同使用,表示如果要创建的消息队列已经存在,则返回错误。
14         IPC_NOWAIT:读写消息队列要求无法满足时,不阻塞。
15 返回值:
16     调用成功返回队列标识符,否则返回-1.
17 */

    3)将消息添加到消息队列

 1 #include <sys/types.h>
 2 #include <sys/ipc.h>
 3 #include <sys/msg.h>
 4 
 5 int msgsnd(int msqid,  struct msgbuf *msgp,  size_t msgsz,  int msgflag)
 6 /*
 7 功能:
 8     将新消息添加到队列尾端,即向消息队列中发送一条消息。
 9 参数:
10     msqid:消息队列的标识符。
11     msgp:指向消息缓冲区的指针,此位置用来暂时存储发送和接收的消息,是一个用户可定义的通用结构,形态如下
12  
13 struct msgbuf {
14     long mtype;     /* 消息类型,必须 > 0 */
15     char mtext[1];  /* 消息文本 */
16 };
17     msgsz:消息的大小,消息的总大小不能超过8192字节,在<linux/msg.h>有个宏控制,#define MSGMAX 8192。
18     msgflag:
19          IPC_NOWAIT: 指明在消息队列没有足够空间容纳要发送的消息时,msgsnd立即返回。
20          0:msgsnd调用阻塞直到条件满足为止.(一般选这个)
21 */

    4)从消息队列读取消息

 1 #include <sys/types.h>
 2 #include <sys/ipc.h>
 3 #include <sys/msg.h>
 4 
 5 ssize_t msgrcv(int msqid,  struct msgbuf *msgp,  size_t msgsz,  long msgtype,  int msgflag)
 6 
 7 /*
 8 功能:
 9     从队列中接收消息
10 参数:
11     msqid:已打开的消息队列id
12     msgp:存放消息的结构体指针。msgp->mtype与第四个参数是相同的。
13     msgsz:消息的字节数,指定mtext的大小。
14     msgtype:消息类型,消息类型 mtype的值。如果为0,则接受该队列中的第一条信息,如果小于0,则接受小于该值的绝对值的消息类型,如果大于0,接受指定类型的消息,即该值消息。
15     msgflag:函数的控制属性。
16     msgflag:
17         MSG_NOERROR:若返回的消息比msgsz字节多,则消息就会截短到msgsz字节,且不通知消息发送进程.
18         IPC_NOWAIT:调用进程会立即返回.若没有收到消息则返回-1.
19         0:msgrcv调用阻塞直到条件满足为止.
20 在成功地读取了一条消息以后,队列中的这条消息将被删除。
21 返回值:成功执行时,msgrcv()返回0, 失败返回-1
22 */

    5)消息队列属性控制

 1 #include <sys/types.h>
 2 #include <sys/ipc.h>
 3 #include <sys/msg.h>
 4 
 5 int msgctl(int msqid,  int cmd,  struct msqid_ds *buf)
 6 
 7 /*
 8    功能:
 9  对消息队列进行各种控制操作,操作的动作由cmd控制。
10 参数:
11     msqid:消息队列ID,消息队列标识符,该值为msgget创建消息队列的返回值。
12     cmd:
13         IPC_STAT:取出系统保存的消息队列的msqid_ds 数据,并将其存入参数buf 指向的msqid_ds 结构
14         IPC_SET:设定消息队列的msqid_ds 数据中的msg_perm 成员。设定的值由buf 指向的msqid_ds结构给出
15         IPC_RMID:删除由msqid指示的消息队列
16 */

  3、实例

 1 /* msgsnd.c */
 2 
 3 
 4 #include <sys/types.h>
 5 #include <sys/ipc.h>
 6 #include <sys/msg.h>
 7 #include <stdio.h>
 8 #include <stdlib.h>
 9 #include <unistd.h>
10 #include <string.h>
11 #define  BUFFER_SIZE 512
12 
13 
14 struct message
15 {
16     long msg_type;
17     char msg_text[BUFFER_SIZE];
18 };
19 
20 
21 int main()
22 {
23     int   qid;
24     key_t key;
25     struct message msg;
26 
27     /*根据不同的路径和关键表示产生标准的key*/
28     if ((key = ftok(".", 'a')) == -1)
29     {
30         perror("ftok");
31         exit(1);
32     }
33 
34     /*创建消息队列*/
35     if ((qid = msgget(key, IPC_CREAT|0666)) == -1)
36     {
37         perror("msgget");
38         exit(1);
39     }
40     printf("Open queue %d\n",qid);
41 
42     while(1)
43     {
44        printf("Enter some message to the queue(enter 'quit' to exit):");
45         if ((fgets(msg.msg_text, BUFFER_SIZE, stdin)) == NULL)
46         {
47             puts("no message");
48             exit(1);
49         }
50 
51         msg.msg_type = getpid();
52 
53         /*添加消息到消息队列*/
54         if ((msgsnd(qid, &msg, strlen(msg.msg_text), 0)) < 0)
55         {
56             perror("message posted");
57             exit(1);
58         }
59 
60         if (strncmp(msg.msg_text, "quit", 4) == 0)
61         {
62             break;
63         }
64     }
65 
66     exit(0);
67 }          

 

 1 /* msgrcv.c */
 2 
 3 #include <sys/types.h>
 4 #include <sys/ipc.h>
 5 #include <sys/msg.h>
 6 #include <stdio.h>
 7 #include <stdlib.h>
 8 #include <unistd.h>
 9 #include <string.h>
10 #define  BUFFER_SIZE        512
11 
12 struct message
13 {
14     long msg_type;
15     char msg_text[BUFFER_SIZE];
16 };
17 
18 int main()
19 {
20     int qid;
21     key_t key;
22     struct message msg;
23     
24     /*根据不同的路径和关键表示产生标准的key*/
25     if ((key = ftok(".", 'a')) == -1)
26     {
27         perror("ftok");
28         exit(1);
29     }
30     
31     /*创建消息队列*/
32     if ((qid = msgget(key, IPC_CREAT|0666)) == -1)
33     {
34         perror("msgget");
35         exit(1);
36     }
37     printf("Open queue %d\n", qid);
38     
39     do
40     {
41         /*读取消息队列*/
42         memset(msg.msg_text, 0, BUFFER_SIZE);
43         if (msgrcv(qid, (void*)&msg, BUFFER_SIZE, 0, 0) < 0)   //读取消息不管是谁发的
44         {
45             perror("msgrcv");
46             exit(1);
47         }
48         printf("The message from process %d : %s", msg.msg_type, msg.msg_text);        
49         
50     } while(strncmp(msg.msg_text, "quit", 4));
51     
52     
53     /*从系统内核中移走消息队列 */
54     if ((msgctl(qid, IPC_RMID, NULL)) < 0)
55     {
56         perror("msgctl");
57         exit(1);
58     }
59     
60     exit(0);
61 }

   四、共享存储

    共享存储允许一个或多个进程共享一个给定的存储区,因为数据不需要在客户端进程和服务器进程之间进行复制,所以这是最快的一种IPC,对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。

    不同进程之间共享的内存通常安排为同一段物理内存。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以。

    1、共享存储的特点:

      1)速度快,效率高;

      2)需要有同步机制;

    2、共享存储操作流程

      1)创建/打开共享内存

      2)映射共享内存,即把即把指定的共享内存映射到进程的地址空间用于访问

      3)撤销共享内存映射

      4)删除共享内存

    3、相关函数

      1)创建/打开共享内存

 1 #include <sys/ipc.h>
 2 #include <sys/shm.h>
 3 
 4 int shmget(key_t key, size_t size, int shmflg);
 5 
 6 /*
 7    功能:分配一块共享内存
 8 
 9    参数:
10         key: 函数ftok的返回值或IPC_PRIVATE,为IPC_PRIVATE时创建的共享内存只能用于有亲缘关系的进程通信;
11         size:建立共享内存的大小(单位字节),通常取系统页长的整数倍,若size值不是系统页的整数倍,则最后一页剩余部分是不可用的;
12         shmflg:
13             0:取共享内存标识符,若不存在则函数会报错
14             IPC_CREAT:如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符
15             IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的共享内存,则新建一个消息队列;如果存在这样的共享内存则报错  
16 返回值:成功,返回共享内存标识符,失败返回-1;
17 */

      2)映射共享内存

 1 #include <sys/types.h>
 2 #include <sys/shm.h>
 3 
 4 void *shmat(int shmid, const void *shmaddr, int shmflg)
 5 
 6 /*
 7     功能:连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问
 8     
 9     参数:
10           msqid:共享内存标识符  
11           shmaddr:指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置  
12           shmflg:SHM_RDONLY:为只读模式,否则为读写模式
13 
14     返回值:
15             成功,返回共享内存地址,出错返回-1
16 */    

      3)撤销共享内存映射

 1 #include <sys/types.h>
 2 #include <sys/shm.h>
 3 
 4 int shmdt(const void *shmaddr)
 5 
 6 /*
 7     功能:取消共享内存与用户进程之间的映射
 8 
 9     参数:shmaddr,连接的共享内存的起始地址
10 
11     返回值:成功返回0,出错返回-112 */

      4)控制共享内存

 1 #include <sys/types.h>
 2 #include <sys/shm.h>
 3 
 4 int shmctl(int shmid, int cmd, struct shmid_ds *buf)
 5 
 6 /*
 7     功能:完成对共享内存的控制
 8 
 9     参数:
10         shmid:共享内存标识id;
11         cmd:
12             IPC_STAT得到共享内存的状态
13             IPC_SET改变共享内存的状态
14             IPC_RMID删除共享内存
15         but:是一个结构体指针,IPC_STAT时,取得共享内存状态放在这个结构体中,IPC_SET改变共享内存状态时,用这个结构指定
16 
17     返回值:成功返回0,失败返回-118 */    

 

    4、实例:

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <string.h>
 4 #include <sys/ipc.h>
 5 #include <sys/shm.h>
 6 //#include <error.h>
 7 #define SIZE 1024
 8 int main()
 9 {
10     int shmid ;
11     char *shmaddr ;
12     struct shmid_ds buf ;
13     int flag = 0 ;
14     int pid ;
15  
16     shmid = shmget(IPC_PRIVATE, SIZE, IPC_CREAT|0600 ) ;
17     if ( shmid < 0 )
18     {
19             perror("get shm  ipc_id error") ;
20             return -1 ;
21     }
22     pid = fork() ;
23     if ( pid == 0 )
24     {
25         shmaddr = (char *)shmat( shmid, NULL, 0 ) ;
26         if ( (int)shmaddr == -1 )
27         {
28             perror("shmat addr error") ;
29             return -1 ;
30  
31         }
32         strcpy( shmaddr, "Hi, I am child process!\n") ;
33         shmdt( shmaddr ) ;
34         return  0;
35     } else if ( pid > 0) {
36         sleep(3 ) ;
37         flag = shmctl( shmid, IPC_STAT, &buf) ;
38         if ( flag == -1 )
39         {
40             perror("shmctl shm error") ;
41             return -1 ;
42         }
43  
44         printf("shm_segsz =%d bytes\n", buf.shm_segsz ) ;
45         printf("parent pid=%d, shm_cpid = %d \n", getpid(), buf.shm_cpid ) ;
46         printf("chlid pid=%d, shm_lpid = %d \n",pid , buf.shm_lpid ) ;
47         shmaddr = (char *) shmat(shmid, NULL, 0 ) ;
48         if ( (int)shmaddr == -1 )
49         {
50             perror("shmat addr error") ;
51             return -1 ;
52  
53         }
54         printf("%s", shmaddr) ;
55         shmdt( shmaddr ) ;
56         shmctl(shmid, IPC_RMID, NULL) ;
57     }else{
58         perror("fork error") ;
59         shmctl(shmid, IPC_RMID, NULL) ;
60     }
61  
62     return 0 ;
63 }

   五、UNIX域套接字

    UNIX域套接字用于在同一台机器上运行的进程之间的通信,虽然因特网域套接字可用于同一目的,但UNIX域套接字的效率更高。UNIX域套接字仅仅复制数据;它们并不执行协议处理,不需要添加或删除网络报头,无需计算检验和,不要产生顺序号,无需发送确认报文。UNIX域套接字有两种类型的套接字:字节流套接字和数据包套接字,字节流套接字类似于TCP,数据包套接字类似于UDP。

  1、非命名的UNIX域套接字

  1)创建

    UNIX域套接字是套接字和管道之间的混合物。为了创建一对非命名的,相互连接的UNXI域套接字,可以使用socketpair函数。

 1 #include <sys/types.h>
 2 #include <sys/socket.h>
 3 
 4 int socketpair(int domain, int type, int protocol, int sv[2]);
 5 
 6 功能:创建一对匿名的已连接的全双工的socket。创建出来的两个描述符是等价的。
 7 
 8 参数:
 9     domain:表示协议族,只能为AF_LOCAL或者AF_UNIX;
10     type:表示协议类型,可以是SOCK_STREAM或者SOCK_DGRAM;
11     protocol:表示协议,只能为0;
12     sv[2]:接收代表两个套接口的整数数组
13 
14 返回值:成功返回0,失败返回-1

  2)、实例:父子进程个关闭一个文件描述符,则在父进程写可从子进程读取,反之若子进程写,父进程同样可以读取。

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <sys/socket.h>
 4 #include <string.h>
 5 #include <unistd.h>
 6 int main(void){
 7         int fd[2];
 8         int pid;
 9        char wbuf1[16] = "1234567890";
10     char wbuf2[16] = "abcdefg";
11        char rbuf[16];
12         if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
13                 perror("socketpair");
14                 return -1;
15         }
16 
17         if((pid = fork())<0){
18                 perror("fork");
19                 return -1;
20         }else if(pid == 0){
21                 //child
22                 close(fd[0]);
23                 if(write(fd[1],wbuf1,strlen(wbuf1)) < 0){
24                         perror("write");
25                         exit(-1);
26                 }
27         if(read(fd[1],rbuf,16) < 0) {
28             perror("child read");
29             exit(-1);
30         }
31         printf("child:%s\n",rbuf);
32         }else{
33                 //parent
34                 close(fd[1]);
35         if(write(fd[0], wbuf2, 16) < 0) {
36             perror("parent write");
37             exit(-1);
38         }
39                 if(read(fd[0],rbuf,16) < 0){
40                         perror("read");
41                         exit(-1);
42                 }
43                 printf("parent:%s\n",rbuf);
44         }
45         return 0;
46 }

  2、命名UNIX域套接字

    socketpair函数创建相互连接的一对套接字,但是每一个套接字都没有名字。这意味着无关进程不能使用它们。命名UNIX域套接字和因特网域套接字类似,不过UNIX域套接字只能用于同一台设备之间的进程间通信。

    1)UNIX域套接字的特点

      1)UNIX域套接字与TCP套接字比较,在同一台主机的传输速度前者是后者的两倍;

      2)UNIX域套接字可以在同一台主机上各进程之间传递描述符;

      3)UNIX域套接字与传统套接字的区别是用路径名来表示协议族的描述。

    2)UNIX域地址结构

 1 <linux/un.h>
 2 
 3 #define UNIX_PATH_MAX    108
 4 
 5 struct sockaddr_un {
 6     sa_family sun_family;
 7     char sun_path[UNIX_PATH_MAX];
 8 }
 9 
10 
11 //UNIX域地址结构成员变量sun_family的值是AF_UNIX或AF_LOCAL;
12 //sun_path是一个路径名,此路径名的属性为0777,可以进行读写等操作;

注意:

sun_path成员包含一路经名,当我们将一个地址绑定至UNIX域套接字时,系统用该路经名创建一类型为S_IFSOCK文件。该文件仅用于向客户进程告知套接字名字。该文件不能打开,也不能由应用程序用于通信。如果当我们试图绑定地址时,该文件已经存在,那么bind请求失败。当关闭套接字时,并不自动删除该文件,所以我们必须确保在应用程序终止前,对该文件执行解除链接操作。


    3)套接字函数

    UNIX域套接字函数和以太网套接字函数类似,但是当用于UNIX域套接字时,套接字函数有一些差别和限制,主要有如下几条:

    (1)使用函数bind()进行套接字和地址绑定的时候,地址结构中的路径名和路径名所表示的文件的默认权限为0777;

    (2)结构sun_path中的路径名必须是一个绝对路径,不能是相对路径;

    (3)函数connect()的路径名必须是一个绑定在某个已打开的unix域套接字上的路径名,并且套接字类型也必须一致;

    (4)如果UNIX域字节流套接字的connect()函数发现监听队列已满,则立刻返回ECONNREFUSED错误,这和tcp不同,tcp套接字如果发现监听队列已满,服务器方会忽略到来的syn,tcp连接发起方会接着发送syn进行重传;

    4)实例

  1 /*客户端程序*  2 
  3 #define     IPPROBE_STR                              "/tmp/ipprobe.str"
  4 
  5  
  6 
  7 int local_write_read(char *path, char *write_buf, int write_len, char *read_buf, int read_len)
  8 
  9 {
 10 
 11          int sockfd;
 12 
 13          struct sockaddr_un servaddr;
 14 
 15          int ret = 0;
 16 
 17  
 18 
 19          assert(path);
 20 
 21          assert(write_buf);
 22 
 23          assert(read_buf);
 24 
 25          if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0)
 26 
 27          {
 28 
 29                    return -1;
 30 
 31          }
 32 
 33  
 34 
 35          memset(&servaddr, 0 ,sizeof(servaddr));
 36 
 37          servaddr.sun_family = AF_LOCAL;
 38 
 39          strcpy(servaddr.sun_path, path);
 40 
 41  
 42 
 43          if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
 44 
 45          {
 46 
 47                    return -1;
 48 
 49          }
 50 
 51  
 52 
 53          ret = write(sockfd, write_buf, write_len);
 54 
 55          if (ret < 0)
 56 
 57          {
 58 
 59                    return -1;
 60 
 61          }
 62 
 63         
 64 
 65          ret = read(sockfd, read_buf, read_len);
 66 
 67          printf("local_write_read ret = %d\n", ret);
 68 
 69          if (ret < 0)
 70 
 71          {
 72 
 73                    read_buf = NULL;
 74 
 75                    return -1;
 76 
 77          }
 78 
 79  
 80 
 81          close(sockfd);
 82 
 83          return 0;
 84 
 85 }
 86 
 87 
 88 /*服务器程序* 89 
 90 int response_server()
 91 
 92 {
 93 
 94          struct sockaddr_un serv_addr, cli_addr;
 95 
 96          int listen_fd, conn_fd;
 97 
 98          socklen_t cli_len;
 99 
100          int ret = 0;
101 
102          char buf[256];
103 
104          unsigned short flag;
105 
106          struct ipprobe_t *ipprobe;
107 
108          struct ipprobe_reg_t *reg;
109 
110          struct ipprobe_entry_t *entry;
111 
112          struct ipprobe_query_t query;
113 
114          unsigned short id;
115 
116          int i = 0;
117 
118  
119 
120  
121 
122         
123 
124          debug_msg("response_server start\n");       
125 
126  
127 
128          if ((listen_fd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0)
129 
130          {
131 
132                    debug_msg("socket");
133 
134                    return -1;
135 
136          }
137 
138         
139 
140          memset(&serv_addr, 0, sizeof(serv_addr));
141 
142          serv_addr.sun_family = AF_LOCAL;
143 
144          strcpy(serv_addr.sun_path, IPPROBE_STR);
145 
146          unlink(serv_addr.sun_path);
147 
148         
149 
150          if (bind(listen_fd, (struct sockaddr *)&serv_addr, SUN_LEN(&serv_addr)) < 0)
151 
152          {
153 
154                    debug_msg("bind");
155 
156                    return -1;
157 
158          }
159 
160         
161 
162          if (listen(listen_fd, 10) < 0)
163 
164          {
165 
166                    debug_msg("listen");
167 
168                    return -1;
169 
170          }
171 
172  
173 
174          cli_len = SUN_LEN(&cli_addr);
175 
176          debug_msg("listening\n");
177 
178         
179 
180          for (;;)
181 
182          {
183 
184                    if ((conn_fd = accept(listen_fd, (struct sockaddr *)&cli_addr, &cli_len)) < 0 )
185 
186                    {
187 
188                             return -1;
189 
190                    }
191 
192                   
193 
194                    memset(buf, 0, sizeof(buf));
195 
196                    if (read(conn_fd, buf, sizeof(buf)) < 0)
197 
198                    {
199 
200                             debug_msg("read");
201 
202                             return -1;
203 
204                    }
205 
206  
207 
208                    flag =  *(unsigned short *)buf;
209 
210                    debug_msg("flag = %d\n", flag);
211 
212                    switch (flag)
213                         ……
214         }
215 }
216                             

 

    

 

posted @ 2017-10-16 09:21  请叫我阿强  阅读(11142)  评论(0编辑  收藏  举报