【操做系統】Linux下進程通訊(共享內存,管道,消息隊列,Socket)

Linux 下進程通訊

博客新址,這裏更有趣
在網絡課程中,有講到Socket編程,對於tcp講解的環節,爲了加深理解,本身寫了Linux下進程Socket通訊,在學習的過程當中,又接觸到了其它的幾種方式。記錄一下。git

共享內存通訊方式

共享內存的通訊方式,系統根據個人偏好設置在內存中開闢一塊空間,並對其進行相應的指定,而後咱們的另外一個進程能夠按照一樣的指定,也就是其標記,對其進行訪問。建立共享內存,獲得共享內存後,想內存中寫入數據,而後另個進程獲得內存,而後從中讀出數據。
先貼出代碼,寫入方github

#include <stdlib.h>   
#include <sys/shm.h>
#include <sys/ipc.h>  
#include <unistd.h>  
#include <string.h>

#define PATH ""
#define SIZE 512
#define ID 0
int main()
{
    char * shmAddr;
    char * dataAddr = "Hello";
    int key = ftok(PATH,ID);
    int shmID = shmget(key,SIZE,IPC_CREAT);
    shmAddr = shmat(shmID,NULL,0);
    strcpy(shmAddr,dataAddr);
    shmdt(shmAddr);
    exit(0);
}

接收方算法

#include <stdlib.h> 
#include <stdio.h>  
#include <unistd.h>  
#include <string.h>
#include <sys/shm.h>
#include <sys/ipc.h>  

#define PATH ""
#define SIZE 0
#define ID 0
int main()
{
    char * shmAddr;
    char * dataAddr = "Hello";
    int key = ftok(PATH,ID);
    int shmID = shmget(key,SIZE,0);
    shmAddr = shmat(shmID,NULL,0);
    printf("%s\n",shmAddr);
    shmdt(shmAddr);
    shmctl(shmID, IPC_RMID, NULL);
    exit(0);
}

代碼比較簡單,不難看出,經過函數shmget(),咱們獲得了對一塊共享內存的標記,而後經過shmdt(),而後和咱們的進程綁定獲得這塊共享內存的地址,而後便可輸出該塊內存區域中的數據。主要涉及幾個參數,ftok(),shmget(),shmat()。編程

ftok():接受兩個參數一個是文件目錄,一個是咱們的projectid,對於第二個id,咱們這裏能夠隨便制定,此處指定0,該函數能夠爲當前IPC生成一個惟一的鍵值。
shemget():第一個參數是咱們進程的鍵值,第二個參數是咱們指定內存區域的大小,若是制定爲0則表明咱們只是打開,而不是去建立,第三個參數是制定咱們對該內存區域的操做模式權限。
shmat:該參數的做用是將咱們的共享內存映射到咱們的當前進程的內存空間。獲得了其地址。第一個參數是咱們要映射的共享內存的標示符。第二個參數是制定將共享內存的映射到咱們進程空間的起始地址,若是制定爲null,則會由系統自動分配一個。第三個參數是指定咱們的操做模式,是讀仍是寫,此處指定爲0,表示既能夠對其讀又能夠對其寫。
shmdt:這個函數是用來斷開當前進程和該共享內存區域的鏈接,
shmctl:該函數經過對於一些參數的指定來對共享內存區域進行一系列的控制,對於共享內存區域,設有一個計時器,當到達必定時間以後,若是沒有進程和其綁定,該內存將會被回收。也能夠不用手動對其回收。數組

下面運行下這兩個程序
圖片描述
當咱們再次運行client2後
圖片描述
由於咱們的連個進程都已經和該共享內存區域解綁,並且也執行了移除,若是咱們不對其移除。則會
圖片描述服務器

管道通訊(匿名,有名)

管道通訊,在一個進程之中,只能單一的對其寫或者是讀,而不能夠及執行寫操做又執行讀操做。這一點,咱們能夠將其想象成咱們的水管,分別連着不一樣的兩端,在有水流的時候,一端只能進行輸入,另外一端只能進行輸出,而不能夠同時輸入和輸出。網絡

管道又分爲有名管道和匿名管道,二者的區別在於對於匿名管道,其只能在具備親緣關係的父子進程之間進行消息通訊。管道的通訊是藉助於在咱們的磁盤上生成一個文件,而後對文件的讀寫來實現咱們的通訊,並且數據被讀以後,在文件中將會被刪除掉。socket

匿名

匿名管道的實現較爲簡單,實現代碼:tcp

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define BUFFER_SIZE 9

int main()
{
        int n;
        int fd[2];
        pid_t pid;
        char buf[BUFFER_SIZE];
        if(pipe(fd)==-1)
        {
                printf("error in create pipe\n");
                exit(1);
        }
        if((pid=fork())<0)
        {
                printf("error in fork a sub process\n");
                exit(1);
        }
        if(pid>0)
        {
                close(fd[0]);
                write(fd[1],"Welcome!",10);
        }
        else
        {
                close(fd[1]);
                read(fd[0],buf,9);
                printf("%s,son\n",buf);
        }
        return 0;
}

1.建立管道2.經過fork函數分叉出子進程3.寫數據4.讀數據5.關閉管道讀端和寫端。這裏也除了管道函數,還要特別說一下的是fork函數。函數

fork函數返回的數據在父進程中是子進程pid,而對子進程返回的是0,這並非說,子進程的pid就是零,還有一個函數是getpid(),執行這個函數後,在子進程和父進程中獲得的都是該進程pid,而不是fork函數的返回值。fork函數執行的時候,是將父進程中所有的數據置入本身進程之中,和父進程中的數據不是同步的了。

經過pipe函數咱們建立了一個管道,管道接收的參數是一個長度爲2的一維數組,此時,此時在0位返回的是讀端口,1位返回的是寫端口,當咱們要對數據進行寫入的時候,咱們須要關閉其讀端口,若是對其進行讀操做,咱們要關閉寫端口。相似於讀文件的操做,制定數據和大小,而後經過read和write函數對其進行讀寫。

後來又發現的一些問題是對於讀寫端口的問題,當咱們在寫的時候,讀端口開着對其並無影響,其目的是以防萬一,寫入數據被自身讀取走了。還有此處實現方式是經過創建一個管道文件,而後採用了常規的文件讀寫方式進行的讀寫,還會出現的一些是,當咱們read完一次以後,若是下面接着再read,將會出現一個問題,咱們還可以從中讀出數據來,緣由是管道文件自己具備一個自身管理的,來負責對於管道文件的擦除,而其擦除速度較慢於咱們從中讀出的速度,因此致使了這個問題。

圖片描述

有名

經過對於匿名管道的分析,再到有名管道,爲何有名管道能夠被非親緣進程找到利用?由於它有名呀,對的,若是在家中,父親要和兒子談話,只需說出來就行了,由於信道上的接聽者只有父親和兒子,因此即便不指兒子的名字和兒子說,兒子也是知道是在和他講,可是若是要和別人講話,並且由不少人同時在偵聽信道的時候,若是咱們不指定名字,他們就不會知道咱們是在跟誰講話。因此對於有名管道,咱們首先要對其指定一個名字,而後指定做爲寫端或者是讀端,而後對其進行操做。

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

#define FIFO_NAME "/oswork/pipe/myfifo"

int main()
{
        int res;
        int pipe_id;
        char buffer[] = "Hello world";
        if(access(FIFO_NAME,F_OK)==-1)
        {
                res = mkfifo(FIFO_NAME,O_WRONLY);
        if(res!=0)
                {
                        printf("Error in creating fifo.\n");
                        exit(1);
                }
        }
        pipe_id = open(FIFO_NAME,O_WRONLY);
        if(pipe_id!= -1)
        {
                if(write(pipe_id,buffer,PIPE_BUF)>0){
                        close(pipe_id);
                }else{
                        printf("Error in writing.\n");
                        exit(1);
                }
        }else
        {
                printf("Error in opening.\n");
                exit(1);
        }

由於管道是經過本地磁盤上的文件進行信息的交換,所以咱們須要給予其本地磁盤上的一個文件目錄,而後根據該目錄經過access()函數來獲取該管道,若是獲取失敗,那麼咱們就按照給定的目錄建立一個,建立管道的時候,咱們須要制定是對其進行讀仍是寫,建立好以後,經過指定模式打開咱們的管道,此時會獲得一個管道號,而後根據得到管道標誌號,做爲參數,進行read和write操做。下面是讀端的執行代碼。

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

#define FIFONAME "/oswork/pipe/myfifo"
int main()
{
        char buffer[PIPE_BUF+1];
        int pipe_id;
        pipe_id = open(FIFONAME,O_RDONLY);
        if(pipe_id != -1)
        {
                read(pipe_id,buffer,PIPE_BUF);
                printf("%s",buffer);
        }else{
                printf("error in reading\n");
        }
}

執行結果,在這裏的讀和寫都是設置爲堵塞執行方式,就是若是沒讀端,這個時候,寫端就是處於堵塞狀態
圖片描述
這個時候再執行讀端
圖片描述
寫端堵塞解除
圖片描述

Socket通訊

Socket通訊,不只僅是一臺主機上的兩個進程能夠進行通訊,還可讓處在因特網中的兩個進程進行通訊。在兩個進程進行通訊的時候,首先本地的進程在運行的時候會綁定一個端口,而後咱們本地爲該進程生成一個緩衝區,返回一個值,即爲socket做爲對其進行標記,每當本地進程和遠程一個進程創建鏈接的時候,就會根據遠程進程的信息和本地進程的信息生成一個socket,而後雙方藉助於socket就能夠進行通訊,運輸層獲得的數據寫入socket標誌的緩衝區,而後在裏面進行相應的操做以後將其提交給網絡層。相比其它的幾種處理方式,該中方式比較麻煩。多於服務端,經過listen阻塞監聽,監聽到有鏈接請求,經過accept函數獲得一個本地與之對應的緩衝區,而後建立一個進程用來和該鏈接進行交互,而後經過receive來接收信息,因爲c語言大一學過去以後,基本沒怎麼再看過,因此寫來的時候仍是遇到了幾個小坑。這裏實現的是當鏈接創建後,服務端給本地端發送一個鏈接創建提示,而後客戶端能夠向服務端發送消息,服務端給予一個I don't know的回覆。

服務端代碼

#include <sys/type.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>

#define PORT 9000
#define KEY 123
#define SIZE 1024


int main()
{
    char buf[100];
    memset(buf,0,100);
    int server_sockfd,client_sockfd;
    socklen_t server_len,client_len;
    struct  sockaddr_in server_sockaddr,client_sockaddr;
    /*create a socket.type is AF_INET,sock_stream*/
    server_sockfd = socket(AF_INET,SOCK_STREAM,0);
    /*the address of the sockt which is a struct include sinfamily,sin_port and sin_addr(struct)
    htons is a convert function,*/
    server_sockaddr.sin_family = AF_INET;
    server_sockaddr.sin_port = htons(PORT);
    server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    /*the length of the sever_sockt address*/
    server_len = sizeof(server_sockaddr);
    /*
    first option is socket we create,the second option is level the left is 
    the paraments'length and value
    */
    int on;
    setsockopt(server_sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
    /*bind a socket or rename a sockt*/
    if(bind(server_sockfd,(struct sockaddr*)&server_sockaddr,server_len)==-1){
        printf("bind error");
        exit(1);
    }
    /*listen the socket,the second option is 5 which is the limit number of kernal handle*/
    if(listen(server_sockfd,5)==-1){
        printf("listen error");
        exit(1);
    }

    client_len = sizeof(client_sockaddr);
    /*define a pid number*/
    pid_t ppid,pid;
    while(1){
        if(client_sockfd=accept(server_sockfd,(struct sockaddr*)&client_sockaddr,&client_len)==-1){
            printf("connect error");
            exit(1);
        }
        else{
            //send and receiv all include a socket , content and the len. 
            send(client_sockfd,"You have conected the server",strlen("You have connected the server"),0);
        }
        ppid = fork();
        if(ppid == -1) { 
             printf("fork 1 failed:"); 
         } 
         /*the subthread is used to receiver the message from client Create a process again when 
         we create father process success*/
         else if(ppid == 0){ 
             int recvbytes; 
              pid = fork(); 
          if(pid == -1){
              printf("fork 2 failed:"); 
              exit(1);
          } 
          else if(pid == 0) {
              while(1){ 
                  //set the 100 byte of the buf to 0
                  bzero(buf,100); 
                  if((recvbytes = recv(client_sockfd,buf,100,0))==-1) { 
                      perror("read client_sockfd failed:");
                  } 
                  //if the subProcess receive the message successfully
                  else if(recvbytes != 0){
                      buf[recvbytes] = '\0'; 
                      usleep(1000); 
                      printf("%s:%d said:%s\n",inet_ntoa(client_sockaddr.sin_addr), ntohs(client_sockaddr.sin_port), buf); 
                      //send same message to the client. 
                      if(send(client_sockfd,buf,recvbytes,0)==-1){ 
                          perror("send error"); 
                          break;
                      } 
                  }
                  //when send the msg successfuly end up the method 
                  } 
              } 
              else if(pid>0) {

              }
          } else if(ppid>0){ //總父進程中關閉client_sockfd(由於有另外一個副本在子進程中運行了)返回等待接收消息 
                  close(client_sockfd); 
              } 
          } 
        return 0;
    }


}

其中涉及到建立進程與結束進程等仍是比較複雜的。下面經過一個流程圖說明其工做原理
圖片描述
對於其中的一些函數,有本身的英文註釋,想鍛鍊下英文表達能力,可是表達在語法和意思上仍是有些錯誤的。
客戶端工做代碼

#include <stdio.h> 
#include <stdlib.h> 
#include <errno.h> 
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h> 
#include <sys/wait.h> 
#include <unistd.h> 
#include <arpa/inet.h> 

#define SERVER_PORT 9000
/* the port of the sever which will connected*/ 
#define MAXDATASIZE 100 /* the maxmum bytes every send*/ 
#define SERVER_IP "114.215.100.147" 
/* the ip address of the server*/ 

/*First we need to create a socket and then initialize the socket,then we invoke the connect,it will build a connection between client 
and server we designate*/
int main() { 
    int sockfd, numbytes; 
    char buf[MAXDATASIZE]; 
    struct sockaddr_in server_addr; 
    printf("\n======================client initialization======================\n"); 
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { 
        perror("socket"); 
        exit(1); 
    }
    server_addr.sin_family = AF_INET; 
    server_addr.sin_port = htons(SERVER_PORT); 
    server_addr.sin_addr.s_addr = inet_addr(SERVER_IP); 
    bzero(&(server_addr.sin_zero),sizeof(server_addr.sin_zero)); 
    if (connect(sockfd, (struct sockaddr *)&server_addr,sizeof(struct sockaddr_in)) == -1){
         perror("connect error"); 
         exit(1);
     } 
     //waitting for receive msg from the server,the receiver method is a block method,when the server break up
     //the connection ,is will close the socket.
     while(1) { 
         bzero(buf,MAXDATASIZE); 
         printf("\nBegin receive...\n"); 
         if ((numbytes=recv(sockfd, buf, MAXDATASIZE, 0)) == -1){  
             perror("recv"); 
             exit(1);
         } 
         else if (numbytes > 0){ 
             int len, bytes_sent;
             buf[numbytes] = '\0'; 
            printf("Received: %s\n",buf);
            printf("Send:"); 
            char msgb[100];
            scanf("%s",msg);
            len = strlen(msg); 
            //sent to the server
            if(send(sockfd,msg,len,0) == -1){ 
                perror("send error"); 
            }
        } 
        else { 
            printf("soket end!\n"); 
        } 
        }  
        close(sockfd); 
        return 0;
    }

相比於服務器端,客戶端的代碼比較簡單了,發起鏈接請求-->鏈接成功進入輪詢-->接受消息————>發送消息。
執行結果
圖片描述

圖片描述

消息隊列通訊

消息隊列和有名管道有些相似的地方,最大相同點在於它能夠用於在不一樣的進程之間進行通訊,可是管道有一個劣勢是,對於接收端其對與管道內的數據只能是接受,而不能夠對其進行過濾選擇,同時在寫和讀的時候還會出現堵塞.對於消息隊列其在接收的時候也是會發生堵塞的,解除阻塞,只有當其接收到合適的消息或者該隊列被刪除了,這個阻塞纔會解除。對於消息隊列的通訊流程是建立消息-》得到隊列--》向隊列中發送消息--》從隊列中取出消息。

實現代碼

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/stat.h>
#include <sys/msg.h>
#define MSG_FILE "/oswork/message/sender.c"
#define BUFFER 255
#define PERM S_IRUSR|S_IWUSR
//message struct
struct msgbuf
{
    long mtype;
    char mtext[BUFFER+1];
};
int main()
{
    struct msgbuf msg;
    key_t key;
    int msgid;
    int i;
    char *myask="I'm receiver, 3 messages received from you.";
    //create a key for
    if((key=ftok(MSG_FILE,66))==-1)
    {
        fprintf(stderr,"Creat Key Error:%s \n",strerror(errno));
        exit(EXIT_FAILURE);
    }
    //get a message queue
    if((msgid=msgget(key,PERM|IPC_CREAT))==-1)
    {
        fprintf(stderr,"Creat MessageQueue Error:%s \n",strerror(errno));
        exit(EXIT_FAILURE);
    }
    //get a message from the queue everytime
    for(i=0; i<3; i++)
    {
        msgrcv(msgid,&msg,sizeof(struct msgbuf),1,0);
        printf("Receiver receive: %s\n",msg.mtext);
    }
    msg.mtype=2;
    //send the message that I have received the message.
    strncpy(msg.mtext,myask,BUFFER);
    msgsnd(msgid,&msg,sizeof(struct msgbuf),0);
    return 1;
}

the code of sender

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys.stat.h>

#define MSG_FILE "/oswork/message/sender.c"
#define BUFFER_SIZE 255
#define PERM S_IRUSR|S_IWUSR

struct msgbuf
{
    long mtype;
    char mtext[BUFFER_SIZE+1];
};
//create three messages
char *message[3]={"I'm sender,there are some message for you.","Message1",
"Message2"};

int main()
{
    struct  msgbuf msg;
    key_t key;
    int msgid;
    if((key=ftok(MSG_FILE,66))==-1)
    {
        printf("error in creating key\n");
        exit(0);
    }
    if((msgid=msgget(key,PERM|IPC_CREAT))==-1)
    {
        printf("error in creating get message\n");
        exit(0);
    }
//set the type of the message
    msg.mtype=1;
    int i;
//send the message
    for(i=0; i<3; i++)
    {
        strncpy(msg.mtext,message[i],BUFFER_SIZE);
        msgsnd(msgid,&msg,sizeof(struct msgbuf),2,0);
    }
    memset(&msg,'\0',sizeof(struct msgbuf));
/*receive the message,the third arguments show the type of message will be received
*/
    msgrcv(msgid,&msg,sizeof(struct msgbuf),2,0);
    printf("%s\n",msg.mtext);
//delete the message queue.
    if(msgctl(msgid,IPC_RMID,0)==-1)
    {
        printf("error in deleting msg queue\n");
        exit(0);
    }
    return 1;
}

對於消息通訊,其中的幾個函數,這裏在說明一下,msgrcv和msgsnd兩個函數,對於其中的參數,第一個指定的是咱們的消息隊列的標示符,而後第二個是消息區域,而後是咱們的消息長度,而後是消息類型,最後一個是用來指定是否堵塞。消息隊列中的消息只要是被讀出的,就會自動的被從隊列中剔除掉。

上述爲Linux下進程間通訊的四種方式,實際開發中,咱們傳輸數據類型和進程間的關係選擇合適的方式。

前段時間在牛客網上將《劍指offer》書中算法題用Java實現了一遍,放在了Github上,歡迎一塊兒交流,提高。https://github.com/Jensenczx/...

相關文章
相關標籤/搜索