多進程編程之進程間通信


  進程間通信(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                             

 

    

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM