進程間通信(1)
進程間通信是指進程之間的消息交換。
進程間通信的實質是多個進程看到同一塊資源.
本文將介紹Linux系統下關於進程間通信的
- 管道
- 消息隊列
- 共享內存
- 信號量
管道
所謂管道,是指用於連接一個讀進程一個寫進程以實現它們之間通信的一個共享文件
匿名管道
創建匿名管道函數
參數:pipifd文件描述符數組 pipefd[0] 讀端 pipefd[1]寫端
成功返回0 失敗返回錯誤
利用管道實現父子進程間通信
父進程向子進程發送hello
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
void Test()
{
int fd[2] = {0};
if(pipe(fd) < 0)
{
perror("pipe");
exit(1);
}
pid_t pid = fork();
if(pid < 0)
{
perror("fork:");
exit(2);
}
else if(pid == 0)
{
close(fd[1]); //關閉寫端
char buf[100] = {0};
read(fd[0], buf, 100);
printf("%s\n", buf);
printf("pid = %d\n", pid);
exit(0);
}
else
{
close(fd[0]); //關閉讀端
write(fd[1], "hello\n", 5);
printf("father pid = %d\n", pid);
}
}
int main()
{
Test();
return 0;
}
由於子進程是由父進程創建出來,即子進程也就能看到pipe創建出來的文件,利用read和write函數就能夠實現通信。
匿名管道通信的特點:
- 只能具有親緣關係的進程才能夠通信
- 單向通信
- 基於字節流
- 管道的生命週期隨進程
- 管道自帶同步機制
命名管道
創建命名管道函數
參數1:文件名 參數2: 文件的權限
利用命名管道實現兩進程間通信
//makefile
.PHONY:all
all:server client
server:serverPipe.c
gcc -o $@ $^
client:clientPipe.c
gcc -o $@ $^
.PHONY: clean
clean:
rm f server client
//server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int pf = mkfifo("p", 0644);
if(pf < 0)
{
perror("mkfifo");
return 1;
}
int fp = open("p",O_RDONLY);
if(fp < 0)
{
perror("open");
return 2;
}
char buf[1024] = {0};
while(1)
{
memset(buf, 0x0, sizeof(buf));
ssize_t r = read(fp, buf, sizeof(buf)-1);
printf("wait...:\n");
if(r > 0)
{
printf("client: %s\n", buf);
}
else if(r == 0)
{
printf("client exit\n");
return 3;
}
else
{
perror("read:");
return 2;
}
}
close(fp);
return 0;
}
//client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fp = open("p", O_WRONLY);
if(fp < 0)
{
perror("open");
return 1;
}
char buf[1024] = {0};
while(1)
{
memset(buf, 0x0, sizeof(buf));
printf("please Enter:\n");
int rd = read(0, buf, sizeof(buf)-1);
if(rd > 0)
{ ssize_t r = write(fp, buf, sizeof(buf)-1);
if(r < 0)
{
perror("write");
return 3;
}
}
else
{
perror("read:");
return 4;
}
}
close(fp);
return 0;
}
命名管道就能實現任意兩個間進程通信
消息隊列
消息隊列是指操作系統將進程用於通信的信息儲存的一個容器。
這個容器由操作系統維護,需要通信的進程通過操作系統看到這個隊列。
進程可以在這個隊列中讀信息和發信息。
消息隊列提供了一個進程向別一個進程發送具有類型的數據塊的方法。(具有類型是由於需要知道這個數據塊那個進程發送的)
每個消息的最大長度是有上限的(MSGMAX),每個消息隊列的總的字節數是有上限的(MSGMNB),系統上消息隊列的總數也有一個上(MSGMNI)
消息隊列函數
ftok函數
功能:得到一個id
key_t ftok( char * fname, int id )
參數: 可以任意取(來得到一個key值)
msgget函數
功能:用來創建和訪問一個消息隊列
原型
int msgget(key_t key, int msgflg);
參數
key: 某個消息隊列的名字
msgflg:由九個權限標誌構成,它們的用法和創建文件時使用的mode模式標誌是一樣的
返回值:成功返回一個非負整數,即該消息隊列的標識碼;失敗返回-1
msgctl函數
功能:消息隊列的控制函數
原型
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
參數
msqid: 由msgget函數返回的消息隊列標識碼
cmd:是將要採取的動作
返回值:成功返回0,失敗返回-1
msgsnd函數
功能:把一條消息添加到消息隊列中
原型
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
參數
msgid: 由msgget函數返回的消息隊列標識碼
msgp:是一個指針,指針指向準備發送的消息,
msgsz:是msgp指向的消息長度,這個長度不含保存消息類型的那個long int長整型
msgflg:控制着當前消息隊列滿或到達系統上限時將要發生的事情
msgflg=IPC_NOWAIT表示隊列滿不等待,返回EAGAIN錯誤。
返回值:成功返回0;失敗返回-1
說明
1.消息結構在兩方面受到制約:
首先,它必須小於系統規定的上限值;
其次,它必須以一個long int長整數開始,接收者函數將利用這個長整數確定消息的類型
2.消息結構參考形式如下:
struct msgbuf {
long mtype;
char mtext[1];
}
msgrcv函數
功能:是從一個消息隊列接收消息
原型
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
參數
msgid: 由msgget函數返回的消息隊列標識碼
msgp:是一個指針,指針指向準備接收的消息,
msgsz:是msgp指向的消息長度,這個長度不含保存消息類型的那個long int長整型
msgtype:它可以實現接收優先級的簡單形式
msgflg:控制着隊列中沒有相應類型的消息可供接收時將要發生的事
返回值:成功返回實際放到接收緩衝區裏去的字符個數,失敗返回-1
利用消息隊列實現進程間通信
//Makefile
.PHONY:all clean
all: server client
server: server.c comm.c
gcc -o $@ $^
client: client.c comm.c
gcc -o $@ $^
clean:
rm -f server client
//comm.h
#ifndef _COMM_H_
#define _COMM_H_
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define PHAH "."
#define FTOK_ID 0x777
#define CLIENT_TYPE 1
#define SERVER_TYPE 2
struct Buf_msg
{
long type;
char text[100];
};
int createmsgQueue();
int getmsgQueue();
int destroymsgQueue(int msgid);
int sentMsg(int msgid, char* buf, int _type);
int recvMsg(int msgid, char* buf,int _type);
#endif // _COMM_H_
//comm.c
#include "comm.h"
static int comm(int flag)
{
key_t key = ftok(PHAH,FTOK_ID);
if(key < 0)
{
perror("ftok");
return -1;
}
int msgid = msgget(key, flag);
if( msgid < 0)
{
perror("msgget");
return -2;
}
return msgid;
}
int createmsgQueue()
{
return comm(IPC_CREAT|IPC_EXCL|0666);
}
int getmsgQueue()
{
return comm(IPC_CREAT);
}
int destroymsgQueue(int msgid)
{
if( msgctl(msgid, IPC_RMID, NULL) < 0)
{
perror("msgctl");
return -1;
}
return 0;
}
int sentMsg(int msgid, char* buf, int _type)
{
struct Buf_msg buff;
buff.type = _type;
strcpy(buff.text, buf);
if(msgsnd(msgid,(void*)&buff, sizeof(buff.text), 0) < 0)
{
perror("msgsnd");
return -1;
}
return 0;
}
int recvMsg(int msgid, char* buf, int _type)
{
struct Buf_msg buff;
ssize_t size = 0;
size = msgrcv(msgid,(void*)&buff, sizeof(buff.text), _type, 0);
if(size < 0)
{
perror("msgrcv");
return -1;
}
strncpy(buf, buff.text, size);
buf[size] = '\0';
return 0;
}
//server.c
#include "comm.h"
int main()
{
int msgid = createmsgQueue();
char buf[100] = {0};
while(1)
{
buf[0] = 0;
printf("wait recv:\n");
recvMsg(msgid, buf, CLIENT_TYPE);
printf("client : %s\n", buf);
fflush(stdout);
if(strcasecmp("quit", buf) == 0)
{
printf("cilent exit now!\n");
break;
}
printf("please Entry:");
fflush(stdout);
int size = read(0, buf, sizeof(buf)-1);
if(size < 0)
{
perror("read");
return 1;
}
else
{
buf[size-1] = '\0';
sentMsg(msgid, buf, SERVER_TYPE);
}
}
destroymsgQueue(msgid);
return 0;
}
//client.c
#include "comm.h"
int main()
{
int msgid = getmsgQueue();
char buf[100] = {0};
while(1)
{
buf[0] = 0;
printf("please say:");
fflush(stdout);
int size = read(0, buf, sizeof(buf)-1);
if(size < 0)
{
perror("read");
return 1;
}
else
{
buf[size-1] = '\0';
sentMsg(msgid, buf, CLIENT_TYPE);
if(strcasecmp("quit", buf) == 0)
{
printf("client quit now!\n");
break;
}
printf("wait recv:\n");
}
recvMsg(msgid, buf, SERVER_TYPE);
printf("server : %s\n", buf);
}
return 0;
}
使用ipcs -q 可以看到消息隊列
使用ipcsrm -q id 可以刪除信息隊列
消息隊列的生命週期是隨內核,與管道不同。
進程間通信(2)