進程間通訊 (IPC) 方法總結 (一)

進程間通訊 (IPC) 方法總結

進程間通訊(IPC,InterProcess Communication)

Linux環境下,進程地址空間相互獨立,每一個進程各自有不一樣的用戶地址空間。任何一個進程的全局變量在另外一個進程中都看不到,因此進程和進程之間不能相互訪問,要交換數據必須經過內核,在內核中開闢一塊緩衝區,進程1把數據從用戶空間拷到內核緩衝區,進程2再從內核緩衝區把數據讀走,內核提供的這種機制稱爲進程間通訊(IPC,InterProcess Communication)。shell

alt

在進程間完成數據傳遞須要藉助操做系統提供特殊的方法,如:管道、命名管道、信號、消息隊列、共享內存、信號量、套接字等。隨着計算機的蓬勃發展,一些方法因爲自身設計缺陷被淘汰或者棄用。現今經常使用的進程間通訊方式有:服務器

  1. 管道 (使用最簡單)
  2. 信號 (開銷最小)
  3. 共享映射區 (無血緣關係)
  4. 本地套接字 (最穩定)

進程間通訊的7種方式

管道/匿名管道(PIPE)

  • 管道是半雙工,數據只能向一個方向流動;雙方須要互相通訊時,須要創建起兩個管道。
  • 只能用於具備親緣關係的進程(父子進程或者兄弟進程)之間。
  • 管道對於兩端通訊的進程來講就只是一種文件,一種不屬於文件系統僅存在內存中的「僞文件」。
  • 管道的通訊方式爲:寫端每次都將數據寫入管道緩衝區的 末尾 ,而讀端每次都從管道緩衝區的 頭部 讀出數據。數據結構

    管道的實質

    管道的實質是內核利用 環形隊列 的數據結構在 內核緩衝區 中的一個實現,默認設置大小爲4K,能夠經過ulimit -a命令查看。因爲利用 環形隊列 進行實現,讀和寫的位置都是自動增加的,不能隨意改變,一個數據只能被讀取一次,讀取後數據就會從緩衝區中移除。當緩衝區讀空或者寫滿時,有必定的規則控制相應的讀進程或者寫進程進入等待隊列,當空的緩衝區有新數據寫入或者滿的緩衝區有數據讀出來時,就喚醒等待隊列中的進程繼續讀寫。異步

    管道的侷限

  • 因爲管道採用半雙工通訊方式。所以,只支持單向數據流。
  • 只能因爲具備親緣關係的進程。
  • 管道的緩衝區大小有限
  • 沒有名稱函數

    管道的使用

    建立管道操作系統

int pipe(int pipefd[2]); //成功:0;失敗:-1,設置errno設計

函數調用成功返回r/w兩個文件描述符。無需open,但需手動close。規定:fd[0] → r; fd[1] → w,就像0對應標準輸入,1對應標準輸出同樣。向管道文件讀寫數據實際上是在讀寫內核緩衝區指針

管道建立成功之後,建立該管道的進程(父進程)同時掌握着管道的讀端和寫端。如何實現父子進程間通訊呢?一般能夠採用以下步驟:code

alt

  1. 父進程調用pipe函數建立管道,獲得兩個文件描述符fd[0]、fd[1]指向管道的讀端和寫端。
  2. 父進程調用fork建立子進程,那麼子進程也有兩個文件描述符指向同一管道。
  3. 父進程關閉管道讀端,子進程關閉管道寫端。父進程能夠向管道中寫入數據,子進程將管道中的數據讀出。因爲管道是利用環形隊列實現的,數據從寫端流入管道,從讀端流出,這樣就實現了進程間通訊。

eg.server

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(int argc, char *argv[])
{
    int fd[2];
    int res = pipe(fd);
    if(res == -1)
    {
        perror("pipe error");
        return -1;
    }
    pid_t pid = fork();
    if(pid == 0){
        // child
        close(fd[1]);// close write
        char buf[100];
        int count = 0;
        while(count < 5)
        {
            memset(buf,0,sizeof(buf));
            int res = read(fd[0], buf, sizeof(buf));
            if(res > 0)
            {
                buf[res - 1] = '\0';
            }
            printf("I am child! Recv msg:%s from father\n", buf);
            ++count;
        }
        close(fd[0]);
    }else if(pid > 0){
        close(fd[0]);//close read
        int count = 0;
            char buf[100];
        while(count < 5)
        {
            memset(buf,0,sizeof(buf));
            sprintf(buf,"count = %d", ++count);
            int res = write(fd[1],buf,strlen(buf) + 1);// 多一個字節存放\0
            printf("I am father! Send msg:%s to child!\n",buf);
            sleep(2);
        }
        close(fd[1]);
        if(waitpid(pid, NULL, 0) < 0)
        {
            return -1;
        }
    }else{
        perror("fork error!");
        return -1;
    }

    return 0;
}

運行結果:

alt

管道的讀寫行爲

使用管道須要注意如下4種特殊狀況(假設都是阻塞I/O操做,沒有設置O_NONBLOCK標誌):

  1. 若是全部指向管道寫端的文件描述符都關閉了(管道寫端引用計數爲0),而仍然有進程從管道的讀端讀數據,那麼管道中剩餘的數據都被讀取後,再次read會返回0,就像讀到文件末尾同樣。
  2. 若是有指向管道寫端的文件描述符沒關閉(管道寫端引用計數大於0),而持有管道寫端的進程也沒有向管道中寫數據,這時有進程從管道讀端讀數據,那麼管道中剩餘的數據都被讀取後,再次read會阻塞,直到管道中有數據可讀了纔讀取數據並返回。
  3. 若是全部指向管道讀端的文件描述符都關閉了(管道讀端引用計數爲0),這時有進程向管道的寫端write,那麼該進程會收到信號SIGPIPE,一般會致使進程異常終止。固然也能夠對SIGPIPE信號實施捕捉,不終止進程。
  4. 若是有指向管道讀端的文件描述符沒關閉(管道讀端引用計數大於0),而持有管道讀端的進程也沒有從管道中讀數據,這時有進程向管道寫端寫數據,那麼在管道被寫滿時再次write會阻塞,直到管道中有空位置了才寫入數據並返回。

總結:

  • 讀管道:
    1. 管道中有數據,read返回實際讀到的字節數。
    2. 管道中無數據:
      • 管道寫端被所有關閉,read返回0 (好像讀到文件結尾)。
      • 寫端沒有所有被關閉,read阻塞等待(不久的未來可能有數據遞達,此時會讓出cpu)。
  • 寫管道:
    1. 管道讀端所有被關閉, 進程異常終止(也可以使用捕捉SIGPIPE信號,使進程不終止)。
    2. 管道讀端沒有所有關閉:
      • 管道已滿,write阻塞。
      • 管道未滿,write將數據寫入,並返回實際寫入的字節數。

命名管道(FIFO)

FIFO常被稱爲命名管道,以區分管道(pipe)。管道(pipe)只能用於「有血緣關係」的進程間。但經過FIFO,不相關的進程也能交換數據。

命名管道不一樣於匿名管道之處在於它提供了一個路徑名與之關聯,以命名管道的文件形式存在於文件系統中,這樣,即便與命名管道的建立進程不存在親緣關係的進程,只要能夠訪問該路徑,就可以彼此經過命名管道相互通訊,所以,經過命名管道不相關的進程也能交換數據。值的注意的是,命名管道嚴格遵循先進先出(first in first out),對匿名管道及有名管道的讀老是從開始處返回數據,對它們的寫則把數據添加到末尾。它們不支持諸如lseek()等文件定位操做。命名管道的名字存在於文件系統中,內容存放在內存中。

匿名管道和命名管道總結

  1. 管道是特殊類型的文件,在知足先入先出的原則條件下能夠進行讀寫,但不能進行定位讀寫。
  2. 匿名管道是單向的,只能在有親緣關係的進程間通訊;有名管道以磁盤文件的方式存在,能夠實現本機任意兩個進程通訊。
  3. 無名管道阻塞問題:無名管道無需顯示打開,建立時直接返回文件描述符,在讀寫時須要肯定對方的存在,不然將退出。若是當前進程向無名管道的一端寫數據,必須肯定另外一端有某一進程。若是寫入無名管道的數據超過其最大值,寫操做將阻塞,若是管道中沒有數據,讀操做將阻塞,若是管道發現另外一端斷開,將自動退出。
  4. 命名管道阻塞問題:命名管道在打開時須要確實對方的存在,不然將阻塞。即以讀方式打開某管道,在此以前必須一個進程以寫方式打開管道,不然阻塞。此外,能夠以讀寫(O_RDWR)模式打開命名管道,即當前進程讀,當前進程寫,不會阻塞。

    命名管道的使用

    建立方式:
  5. 命令:mkfifo 管道名
  6. 庫函數:
    int mkfifo(const char *pathname, mode_t mode); //成功:0; 失敗:-1

    一旦使用mkfifo建立了一個FIFO,就可使用open打開它,常見的文件I/O函數均可用於fifo。如:close、read、write、unlink等。

eg.

fifo_w.c // 向FIFO中寫入數據

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>

#define MAX_BUF_SIZE 655360

int main(int argc, char* argv[])
{
    if(argc < 2)
    {
        fprintf(stderr,"usage: %s argv[1].\n",argv[0]);
        return -1;
    }
    if(mkfifo(argv[1], 0666) < 0 && errno != EEXIST)
    {
        fprintf(stderr,"Fail to mkfifo %s : %s.",argv[1],strerror(errno));
        return -1;
    }
    int fd;
    if((fd = open(argv[1],O_WRONLY)) < 0)
    {
        fprintf(stderr,"Fail to open mkfifo %s : %s.",argv[1],strerror(errno));
        return -1;
    }
    printf("open for write success\n");
    
    int n;
    char buf[MAX_BUF_SIZE];
    while(1)
    {
        memset(buf, 0, sizeof(buf));       
        printf(">");
        scanf("%s",buf);
        
        n = write(fd, buf, strlen(buf) + 1);// 將\0也寫入
        printf("Write %d bytes.\nSend MSG:%s\n",n, buf);
    }

    return 0;
}

fifo_r.c // 從FIFO中讀取數據

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

#define MAX_BUF_SIZE 655360

int main(int argc, char *argv[])
{
    if(argc < 2)
    {
        fprintf(stderr,"Usage: %s argv[1]\n",argv[0]);
        return -1;
    }

    if(mkfifo(argv[1],0666) < 0 && errno != EEXIST)
    {
        fprintf(stderr,"Fail to mkfifo %s : %s",argv[1],strerror(errno));
        return -1;
    }
    int fd;
    if((fd = open(argv[1],O_RDONLY)) < 0)
    {

        fprintf(stderr,"Fail to open mkfifo %s : %s",argv[1],strerror(errno));
        return -1;
    }
    
    int n;
    char buf[MAX_BUF_SIZE];
    while(1)
    {
        memset(buf, 0, sizeof(buf));
        n = read(fd, buf, sizeof(buf));
        printf("Read %d bytes\nRECV MSG:%s\n",n, buf);
    }
    return 0;
}

運行結果:

寫入:

alt

讀取:

alt

信號(SIGNAL)

  • 信號是Linux系統中用於進程間互相通訊或者操做的一種機制,信號能夠在任什麼時候候發給某一進程,而無需知道該進程的狀態。
  • 若是該進程當前並未處於執行狀態,則該信號就有內核保存起來,直到該進程回覆執行並傳遞給它爲止。
  • 若是一個信號被進程設置爲阻塞,則該信號的傳遞被延遲,直到其阻塞被取消是才被傳遞給進程。

Linux系統中經常使用信號:

  1. SIGHUP:用戶從終端註銷,全部已啓動進程都將收到該進程。系統缺省狀態下對該信號的處理是終止進程。
  2. SIGINT:程序終止信號。程序運行過程當中,按Ctrl+C鍵將產生該信號。
  3. SIGQUIT:程序退出信號。程序運行過程當中,按Ctrl+\鍵將產生該信號。
  4. SIGBUS和SIGSEGV:進程訪問非法地址。
  5. SIGFPE:運算中出現致命錯誤,如除零操做、數據溢出等。
  6. SIGKILL:用戶終止進程執行信號。shell下執行kill -9發送該信號。
  7. SIGTERM:結束進程信號。shell下執行kill 進程pid發送該信號。
  8. SIGALRM:定時器信號。
  9. SIGCLD:子進程退出信號。若是其父進程沒有忽略該信號也沒有處理該信號,則子進程退出後將造成殭屍進程。
    可使用 kill -l 查看當前系統可用信號有哪些

信號來源

信號是軟件層次上對中斷機制的一種模擬,是一種異步通訊方式,,信號能夠在用戶空間進程和內核之間直接交互,內核能夠利用信號來通知用戶空間的進程發生了哪些系統事件,信號事件主要有兩個來源:

  • 硬件來源:用戶按鍵輸入 Ctrl+C 退出、硬件異常如無效的存儲訪問等。
  • 軟件終止:終止進程信號、其餘進程調用 kill 函數、軟件異常產生信號。

信號生命週期和處理流程

  1. 信號被某個進程產生,並設置此信號傳遞的對象(通常爲對應進程的pid),而後傳遞給操做系統;
  2. 操做系統根據接收進程的設置(是否阻塞)而選擇性的發送給接收者,若是接收者阻塞該信號(且該信號是能夠阻塞的),操做系統將暫時保留該信號,而不傳遞,直到該進程解除了對此信號的阻塞(若是對應進程已經退出,則丟棄此信號),若是對應進程沒有阻塞,操做系統將傳遞此信號。
  3. 目的進程接收到此信號後,將根據當前進程對此信號設置的預處理方式,暫時終止當前代碼的執行,保護上下文(主要包括臨時寄存器數據,當前程序位置以及當前CPU的狀態)、轉而執行中斷服務程序,執行完成後在回覆到中斷的位置。固然,對於搶佔式內核,在中斷返回時還將引起新的調度。

alt

eg.

藉助SIGCHLD信號回收子進程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>

void sys_err(char *str)
{
    perror(str);
    exit(1);
}

void do_sig_child(int signo)
{
    int status;    pid_t pid;
    while ((pid = waitpid(0, &status, WNOHANG)) > 0) {
        if (WIFEXITED(status))
            printf("child %d exit %d\n", pid, WEXITSTATUS(status));
        else if (WIFSIGNALED(status))
            printf("child %d cancel signal %d\n", pid, WTERMSIG(status));
    }
}

int main(void)
{
    pid_t pid;    int i;
    for (i = 0; i < 10; i++) {
        if ((pid = fork()) == 0)
            break;
        else if (pid < 0)
            sys_err("fork");
    }
    if (pid == 0) {    
        int n = 1;
        while (n--) {
            printf("child ID %d\n", getpid());
            sleep(1);
        }
        return i+1;
    } else if (pid > 0) {
        struct sigaction act;
        act.sa_handler = do_sig_child;
        sigemptyset(&act.sa_mask);
        act.sa_flags = 0;
        sigaction(SIGCHLD, &act, NULL);
        
        while (1) {
            printf("Parent ID %d\n", getpid());
            sleep(1);
        }
    }
    return 0;
}

運行結果:

alt

消息隊列(MESSAGE)

  • 消息隊列是存放在內核中的消息鏈表,每一個消息隊列由消息隊列標識符表示。
  • 與管道(無名管道:只存在於內存中的文件;命名管道:存在於實際的磁盤介質或者文件系統)不一樣的是消息隊列存放在內核中,只有在內核重啓(即,操做系統重啓)或者顯示地刪除一個消息隊列時,該消息隊列纔會被真正的刪除。
  • 另外與管道不一樣的是,消息隊列在某個進程往一個隊列寫入消息以前,並不須要另外某個進程在該隊列上等待消息的到達

    消息隊列特色總結:
    1. 消息隊列是消息的鏈表,具備特定的格式,存放在內存中並由消息隊列標識符標識.
    2. 消息隊列容許一個或多個進程向它寫入與讀取消息
    3. 管道和消息隊列的通訊數據都是先進先出的原則。
    4. 消息隊列能夠實現消息的隨機查詢,消息不必定要以先進先出的次序讀取,也能夠按消息的類型讀取.比FIFO更有優點。
    5. 消息隊列克服了信號承載信息量少,管道只能承載無格式字 節流以及緩衝區大小受限等缺。
    6. 目前主要有兩種類型的消息隊列:POSIX消息隊列以及System V消息隊列,System V消息隊列目前被大量使用。System V消息隊列是隨內核持續的,只有在內核重起或者人工刪除時,該消息隊列纔會被刪除。

消息隊列的使用

對於系統中的每一個消息隊列,內核維護一個定義在<sys/msg.h>頭文件中的信息結構。

struct msqid_ds {
    struct ipc_perm msg_perm ; 
    struct msg*    msg_first ; //指向隊列中的第一個消息
    struct msg*    msg_last ; //指向隊列中的最後一個消息
    ……
} ;
msgget函數

調用的第一個函數一般是msgget,其功能是打開一個現存隊列或建立一個新隊列。

#include <sys/msg.h>
int  msgget (key_t key,  int oflag) ;

返回值是一個整數標識符,其餘三個msg函數就用它來指代該隊列。它是基於指定的key產生的,而key既能夠是ftok的返回值,也能夠是常值IPC_PRIVATE。
oflag是讀寫權限的組合(用於打開時)。它還能夠是IPC_CREATE或IPC_CREATE | IPC_EXCL(用於建立時)。

msgsnd函數

使用msgsnd打開一個消息隊列後,咱們使用msgsnd往其上放置一個消息。

#include <sys/msg.h>
int  msgsnd (int msqid,  const void *ptr,  size_t length,  int flag) ;

其中msqid是由msgget返回的標識符。ptr是一個結構指針,該結構具備以下模板(咱們須要按這個模板本身定義結構體)

struct mymesg {
    long  mtype ;     //消息類型(大於0)
    char  mtext[512] ;  //消息數據
} ;
//結構體的名字和其中變量名都由咱們本身肯定,咱們只要按照這個模板定義便可。

消息數據mtext中,任何形式的數據都是容許的,不管是二進制數據仍是文本,內核根本不解釋消息數據的內容。(咱們能夠在消息的數據部分 再分割一部分 根據須要定義本身的通訊協議)

參數length指定了待發送消息數據部分的長度。

參數flag的值能夠指定爲IPC_NOWAIT。這相似於文件IO的非阻塞IO標誌。若消息隊列已滿,則指定IPC_NOWAIT使得msgsnd當即出錯返回EAGAIN。

若是沒有指定IPC_NOWAIT,則進程阻塞直到下述狀況出現爲止:①有空間能夠容納要發送的消息 ②從系統中刪除了此隊列(返回EIDRM「標識符被刪除」)③捕捉到一個信號,並從信號處理程序返回(返回EINTR)

msgrcv函數

使用msgrcv函數從某個消息隊列中讀出一個消息。

#include <sys/msg.h>
ssize_t  msgrcv (int msqid,  void* ptr,  size_t length,  long type,  int flag) ;

參數ptr指定所接收消息的存放位置。參數length指定了數據部分大小(只想要多長的數據)

參數type指定但願從隊列中讀出什麼樣的消息。

type == 0 返回隊列中的第一個消息

type > 0  返回隊列中消息類型爲type的第一個消息

type < 0  返回隊列中消息類型值小於或等於type絕對值的消息,若是這種消息有若干個。則取類型值最小的消息。

(若是一個消息隊列由多個客戶進程和一個服務器進程使用,那麼type字段能夠用來包含客戶進程的進程ID)

 參數flag能夠指定爲IPC_NOWAIT,使操做不阻塞。

msgctl函數

msgctl函數提供在一個消息隊列上的各類控制操做。

#include <sys/msg.h>
int  msgctl (int msqid,  in cmd,  struct msqid_ds * buff) ;

參數cmd說明對由msqid指定的隊列要執行的命令:

IPC_STAT :取此隊列的msqid_ds結構,並將它存放在buf指向的結構中。

IPC_SET  :按由buf指向結構中的值,設置與此隊列相關結構中的字段。

IPC_RMID:從系統中刪除該消息隊列以及仍在該隊列中的全部數據。

(這三條命令也可用於信號量和共享存儲)

eg.

一個寫進程,多個讀進程

//-------------------頭文件msgqueue.h ------------------
#ifndef _MAGQUEUE_H_
#define _MAGQUEUE_H_
#include <sys/ipc.h> //包含ftok
#include <sys/msg.h>
#include <sys/types.h>
 
//消息隊列的讀 寫模式掩碼
#define MSG_W 0200
#define MSG_R 0400
 
//定義衆所周知的消息隊列鍵
#define MQ_KEY1 128L
 
#define DATA_SIZE 512
 
typedef struct msgbuf
{
    long mtype ;
    char mdata[DATA_SIZE] ;
} mymsg_t ;
 
#endif
 
//-----------------客戶端進程-----------------
 
#include "msgqueue.h"
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
void client(int, int) ;
 
int main(int argc, char** argv)
{
    int  msgqid ;
 
    //打開消息隊列
    msgqid = msgget(MQ_KEY1, 0) ;
    if (msgqid < 0)
    {
        puts("Open msg queue error!\n") ;
        exit(0) ;
    }
 
    client(msgqid, msgqid) ;
    exit(0) ;
}
 
void client(int readfd, int writefd)
{
    mymsg_t msgToServer ;
    mymsg_t msgFromServer ;
    char*   writePtr ;
    ssize_t pidLen ;
    ssize_t dataLen ;
    ssize_t recvBytes ;
    int     pid ;
 
    //-------構造一條消息-----
    //消息類型爲1
    msgToServer.mtype = 1 ;
 
    //在消息頭部放本進程ID和空格
    pid = getpid() ;
    snprintf(msgToServer.mdata, DATA_SIZE, "%ld ", pid) ;
    pidLen = strlen(msgToServer.mdata) ;
    writePtr = msgToServer.mdata + pidLen ;
 
    //從標準輸入讀入文件路徑
    fgets(writePtr, DATA_SIZE - pidLen, stdin) ;
    dataLen = strlen(msgToServer.mdata) ;  
    if (msgToServer.mdata[dataLen-1] == '\n') //刪除換行符
    {
        msgToServer.mdata[dataLen-1] = '\0' ;
    }
 
    //發送消息
    if (msgsnd(writefd, &msgToServer, strlen(msgToServer.mdata), 0) == -1)
    {
        puts("Send Error!");
        exit(0) ;
    }
 
    //-----接收來自服務器的消息
    while ((recvBytes = msgrcv(readfd, &msgFromServer, DATA_SIZE, pid, 0)) > 0)
    {
        write(STDOUT_FILENO, msgFromServer.mdata, recvBytes) ;
    }
 
}
 
//---------------服務器端進程---------------
//消息隊列是雙向通訊的,故用單個隊列就夠用。
//咱們用每一個消息的類型來標識該消息是從客戶到服務器,仍是從服務器到客戶。
//客戶向隊列發類型爲一、PID和路徑名。
//服務器向隊列發類型爲客戶進程ID的文件內容。
//
//當心死鎖隱患:
//客戶們能夠填滿消息隊列,妨礙服務器發送應答,因而客戶被阻塞在發送中,服務器也被阻塞。
//避免的方法是:約定服務器對消息隊列老是使用非阻塞寫。
 
#include "msgqueue.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
void server(int, int) ;
 
int main(int argc, char** argv)
{
    int  msgqid;
 
    //建立消息隊列
    msgqid = msgget(MQ_KEY1, IPC_CREAT) ;
    if (msgqid < 0)
    {
        puts("Create msg queue error!\n") ;
        exit(0) ;
    }
 
    server(msgqid, msgqid) ;
    exit(0) ;
}
 
void server(int readfd, int writefd)
{
    FILE*    fp ;
    pid_t    clientPid ;
    mymsg_t* msgPtr ;
    ssize_t  recvBytes ;
    char*    pathStr ;    
 
    while(1)
    {
        //從消息隊列中讀取來自客戶的請求文件路徑
        msgPtr = malloc(DATA_SIZE + sizeof(long)) ;
        recvBytes = msgrcv(readfd, msgPtr, DATA_SIZE, 1, 0) ; //阻塞讀
        if (recvBytes <= 0)
        {
            puts("pathname missing") ;
            continue ;
        }
        msgPtr->mdata[recvBytes] = '\0' ;
 
        //分析消息,提取客戶PID,文件路徑
        if ((pathStr = strchr(msgPtr->mdata, ' ')) == NULL)
        {
            puts("bogus request!") ;
            continue ;
        }
        *pathStr++ = 0 ;
        clientPid = atol(msgPtr->mdata) ;
 
        //讀取文件內容 返回給客戶
        msgPtr->mtype = clientPid ; //msgPtr既做爲接收消息 又用做發送消息
        if ((fp = fopen(pathStr, "r")) == NULL)
        {
            //讀取文件失敗,返回給客戶失敗信息(在原消息內容後 添加錯誤信息)
            snprintf(msgPtr->mdata + recvBytes, sizeof(msgPtr->mdata) -recvBytes, 
                    ": can't open!") ;
 
            if (msgsnd(writefd, msgPtr, strlen(msgPtr->mdata), IPC_NOWAIT) == -1)
            {
                puts("Send Error!");
                exit(0);
            }
        }
        else
        {   //copy文件內容 發給客戶
            while (fgets(msgPtr->mdata, DATA_SIZE, fp) != NULL)
            {
                msgsnd(writefd, msgPtr, strlen(msgPtr->mdata), IPC_NOWAIT) ; //非阻塞寫
            }
        }
    }//while()
}
相關文章
相關標籤/搜索