C語言中進程間通訊的方式詳解
一.無名管道
1.1無名管道的原理
無名管道只能用于親緣間進程的通信,無名管道的大小是64K。無名管道是內核空間實現的機制。
1.2功能
1) Pipe()創(chuàng)建一個管道,這是一個單向的數據通道,可用于進程間通信。
2)數組pipefd用于返回兩個指向管道末端的文件描述符。
3)Pipefd[0]指的是管道的讀端。Pipefd[1]指的是管道的寫入端,寫入管道的寫入端數據由內核進行緩沖(64k),直到從管道的讀取端讀取為止。
1.3無名管道通信特點
1.只能用于親緣間進程的通信
2.無名管道數據半雙工的通信的方式
單工 : A -------------->B
半雙工 : 同一時刻 A----->B B------>A
全雙工 : 同一時刻 A<---->B
3.無名管道的大小是64K
4.無名管道不能夠使用lseek函數
5.讀寫的特點
如果讀端存在寫管道:有多少寫多少,直到寫滿為止(64k)寫阻塞
如果讀端不存寫管道,管道破裂(SIGPIPE) (可以通過gdb調試看現象)
如果寫端存在讀管道:有多少讀多少,沒有數據的時候阻塞等待
如果寫端不存在讀管道:有多少讀多少,沒有數據的時候立即返回
1.4無名管道的實例
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#define ERROR(msg) do{\
printf("%s %s %d\n", __FILE__, __func__, __LINE__);\
printf(msg);\
exit(-1); \
}while(0)
int main(int argc, char const *argv[])
{
pid_t pid;
int num[2];
char buff[128] = {0};
if (pipe(num)){
ERROR("pipe error");
}
if ((pid = fork()) == -1){
ERROR("fork error");
}else if(pid == 0){
close(num[0]);
while (1){
memset(buff, 0, sizeof(buff));
printf("請輸入您要輸入的數值>>");
fgets(buff, sizeof(buff), stdin);
buff[strlen(buff) -1] = '\0';
write(num[1], buff, strlen(buff));
if (!strncmp(buff, "quit", 4)){
break;
}
}
close(num[1]);
exit(EXIT_SUCCESS);
}else{
close(num[1]);
while (1){
memset(buff, 0, sizeof(buff));
read(num[0], buff, sizeof(buff));
if (!strncmp(buff, "quit", 4)){
break;
}
printf("%s\n",buff);
}
close(num[0]);
wait(NULL);
}
return 0;
}二.有名管道
2.1有名管道的原理
1)可以用于親緣間進程的通信,也可以用于非親緣間的進程的通信。
2)有名管道會創(chuàng)建一個文件,需要通信的進程打開這個文件,產生文件描述符后就可以通信了,有名管道的文件存在內存上。
3)有名管道的大小也是64K,也不能使用lseek函數
2.2有名管道的特點
1.可以用于任意進程間的通信
2.有名管道數據半雙工的通信的方式
3.有名管道的大小是64K
4.有名管道不能夠使用lseek函數
5.讀寫的特點
如果讀端存在寫管道:有多少寫多少,直到寫滿為止(64k)寫阻塞
如果讀端不存寫管道
1.讀權限沒有打開,寫端在open的位置阻塞
2.讀端打開后關閉,管道破裂(SIGPIPE) (可以通過gdb調試看現象)
如果寫端存在讀管道:有多少讀多少,沒有數據的時候阻塞等待
如果寫端不存在讀管道
1.寫權限沒有打開,讀端在open的位置阻塞
2.寫端打開后關閉,有多少讀多少,沒有數據的時候立即返回
2.3有名管道實例
mkfifo文件:
#include <stdio.h>
#include <stdlib.h>
#define ERROR(msg) do{\
printf("%s %s %d\n", __FILE__, __func__, __LINE__);\
printf(msg);\
exit(-1); \
}while(0)
int main(int argc, char const *argv[])
{
if (mkfifo("./fifo",0666)){
ERROR("mkfifo error");
}
//有名管道沒有阻塞,手動加一個阻塞
getchar();
system("rm ./fifo -rf");
return 0;
}write文件:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#define ERROR(msg) do{\
printf("%s %s %d\n", __FILE__, __func__, __LINE__);\
printf(msg);\
exit(-1); \
}while(0)
int main(int argc, char const *argv[])
{
int fd;
char buff[128] = {0};
if ((fd = open("./fifo",O_WRONLY)) == -1){
ERROR("open fifo error\n");
}
while(1){
printf("input >");
fgets(buff, sizeof(buff), stdin);
buff[strlen(buff) - 1] = '\0';
write(fd, buff, strlen(buff));
if(!strncmp("quit",buff,4))break;
}
close(fd);
return 0;
}read文件:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#define ERROR(msg) \
do \
{ \
printf("%s %s %d\n", __FILE__, __func__, __LINE__); \
printf(msg); \
exit(-1); \
} while (0)
int main(int argc, char const *argv[])
{
int fd;
char buff[128] = {0};
if ((fd = open("./fifo", O_RDONLY)) == -1)
{
ERROR("open error");
}
while (1){
memset(buff, 0, sizeof(buff));
read(fd, buff, sizeof(buff));
if (!strncmp("quit",buff,4)){
break;
}
printf("%s\n",buff);
}
close(fd);
return 0;
}三.信號
3.1信號的概念
信號是中斷的一種軟件模擬,中斷是基于硬件實現的,信號是基于linux內核實現的。
用戶可以給進程發(fā)信號,進程可以給進程發(fā)信號,內核也可以給進程發(fā)信號。進程對
信號的處理方式有三種:捕捉,忽略,默認
3.2發(fā)送信號的函數
int raise(int sig);
功能:給自己(進程或者線程)發(fā)信號
參數:
@sig:信號號
返回值:成功返回0,失敗返回非0
int kill(pid_t pid, int sig);
功能:給進程發(fā)信號
參數:
@pid:進程號
- pid > 0 :給pid對應的進程發(fā)信號
- pid = 0 :給同組的進程發(fā)信號
- pid = -1:給所有的有權限操作的進程發(fā)送信號,init進程除外
- pid < -1:給-pid對應的同組的進程發(fā)信號
@sig:信號號
返回值:成功返回0,失敗返回-1置位錯誤碼
3.3常用的信號


1.在上述的信號中只有SIGKILL和SIGSTOP兩個信號不能被捕捉也不能被忽略
2.SIGCHLD,當子進程結束的時候,父進程收到這個SIGCHLD的信號
3.4實例
捕捉ctrl+c
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#define ERROR(msg) do{\
printf("%s %s %d\n", __FILE__, __func__, __LINE__);\
printf(msg);\
exit(-1); \
}while(0)
void handle(int num)
{
if (num == SIGINT){
printf("我收到一個ctrl+c的信號\n");
}
}
int main(int argc, char const *argv[])
{
//捕捉
if (signal(SIGINT, handle) == SIG_ERR){
ERROR("register signal error");
}
//忽略
if (signal(SIGINT,SIG_IGN) == SIG_ERR){
ERROR("register signal error");
}
//默認
if (signal(SIGINT,SIG_DFL) == SIG_ERR){
ERROR("register signale");
}
while(1);
return 0;
}捕捉管道破裂的消息
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define ERROR(msg) do{\
printf("%s %s %d\n", __FILE__, __func__, __LINE__);\
printf(msg);\
exit(-1); \
}while(0)
void handle(int num)
{
if (num == SIGPIPE){
printf("捕捉到一條管道破裂的消息\n");
}
}
int main(int argc, char const *argv[])
{
int num[2];
char buff[32] = "123";
if(pipe(num)){
ERROR("pipe error");
}
if (signal(SIGPIPE,handle) == SIG_ERR){
ERROR("signal error");
}
close(num[0]);
write(num[1],buff,strlen(buff));
return 0;
}阻塞等待為子進程回收資源
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
void signal_handle(int signo)
{
printf("我是父進程,收到了子進程退出的信號,為它回收資源\n");
waitpid(-1,NULL,WNOHANG); //非阻塞方式回收資源
printf("為子進程回收資源成功\n");
raise(SIGKILL); //給父進程發(fā)送信號,結束父進程
}
int main(int argc,const char * argv[])
{
pid_t pid;
pid = fork();
if(pid == -1){
ERROR("fork error");
}else if(pid == 0){
sleep(5);
printf("子進程執(zhí)行結束了\n");
exit(EXIT_SUCCESS);
}else{
if(signal(SIGCHLD,signal_handle)==SIG_ERR)
ERROR("signal error");
while(1);
}
return 0;
}用arlarm實現一個斗地主機制
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#define ERROR(msg) do{\
printf("%s %s %d\n", __FILE__, __func__, __LINE__);\
printf(msg);\
exit(-1); \
}while(0)
void handle(int num)
{
if (num == SIGALRM){
printf("自動出牌\n");
}
alarm(3);
}
int main(int argc, char const *argv[])
{
char ch;
if (signal(SIGALRM,handle) == SIG_ERR){
ERROR("signale error");
}
alarm(3);
while (1){
printf("請輸入您要出的牌>>");
ch = getchar();
getchar();
printf("%c\n",ch);
alarm(3);
}
return 0;
}四.IPC進程間通信
4.1IPC進程間通信的種類
(1)消息隊列
(2)共享內存
(3)信號燈集
4.2查看IPC進程間通信的命令
4.2.1查看
ipcs -q //查看消息隊列的命令
ipcs -m //查看共享內存的命令
ipcs -s //查看信號燈集的命令
4.2.2刪除ipc的命令
ipcrm -q msqid //刪除消息隊列命令
ipcrm -m shmid //刪除共享內存命令
ipcrm -s semid //刪除信號燈集的命令
4.3消息隊列
4.3.1消息隊列的原理
消息隊列也是借助內核實現的,A進程將消息放到消息隊列中,隊列中的消息
有消息的類型和消息的正文。B進程可以根據想取的消息的類型從消息隊列中
將消息讀走。消息隊列默認的大小是16384個字節(jié)。如果消息隊列中的消息滿了,
A進程還想往隊列中發(fā)消息,此時A進程阻塞。
4.3.2IPC進程間通信鍵值的獲取
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#define ERROR(msg) do{\
printf("%s %s %d\n", __FILE__, __func__, __LINE__);\
printf(msg);\
exit(-1); \
}while(0)
int main(int argc, char const *argv[])
{
key_t key;
struct stat st;
if ((key = ftok("/home/linux",'w')) == -1){
ERROR("ftok error");
}
printf("key=%#x\n",key);
if (stat("/home/linux",&st)){
ERROR("stat error");
}
printf("pro_id=%#x,devno=%#lx,ino=#=%#lx\n",'w',st.st_dev,st.st_ino);
return 0;
}結果圖:

4.3.3消息隊列的實例:(不關注類型的)
頭文件:
#ifndef __MYHEAD_H__
#define __MYHEAD_H__
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/msg.h>
#include <string.h>
#define PRINT_ERR(msg) do{\
printf("%s %s %d\n", __FILE__, __func__, __LINE__);\
printf(msg);\
exit(-1); \
}while(0)
#define MSGSIZE (sizeof(msg_t)-sizeof(long))
typedef struct mubuf{
long mtype;
char text[512];
}msg_t;
#endif發(fā)送方:
#include "myhead.h"
int main(int argc, char const *argv[])
{
key_t key;
int msqid;
msg_t msg ={
.mtype = 100,
};
if((key = ftok("/home/linux/",'r'))==-1)
PRINT_ERR("ftok get key error");
if((msqid = msgget(key,IPC_CREAT|0666))==-1)
PRINT_ERR("create msg queue error");
while (1){
memset(msg.text,0,sizeof(msg.text));
fgets(msg.text,MSGSIZE,stdin);
msg.text[strlen(msg.text) - 1] = '\0';
msgsnd(msqid, &msg, MSGSIZE, 0);
if (!strncmp(msg.text,"quit",4)){
break;
}
}
msgctl(msqid, IPC_RMID, NULL);
return 0;
}接受方:
#include "myhead.h"
int main(int argc, char const *argv[])
{
key_t key;
int msgqid;
msg_t msg;
if ((key = ftok("/home/linux",'r')) == -1){
PRINT_ERR("ftok error");
}
if ((msgqid = msgget(key, IPC_CREAT|0666)) == -1){
PRINT_ERR("msgget error");
}
while (1){
memset(msg.text, 0, sizeof(msg.text));
msgrcv(msgqid, &msg, MSGSIZE,0,0);
if (!strncmp("quit",msg.text,4)){
break;
}
printf("%s\n",msg.text);
}
msgctl(msgqid,IPC_RMID,NULL);
return 0;
}
4.3.4消息隊列的實例:(關注類型的)
頭文件:
#ifndef __MSGQUE_H__
#define __MSGQUE_H__
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/msg.h>
#include <string.h>
typedef struct msgbuf {
long id;
char name[30];
char sex;
int age;
}msg_t;
#define MSGSIZE (sizeof(msg_t)-sizeof(long))
#endif發(fā)送方:
#include "msgqueue.h"
#include <head.h>
int main(int argc, const char* argv[])
{
key_t key;
int msqid;
if ((key = ftok("/home/linux/", 'r')) == -1)
PRINT_ERR("ftok get key error");
if ((msqid = msgget(key, IPC_CREAT | 0666)) == -1)
PRINT_ERR("create msg queue error");
msg_t m1 = {
.id = 1,
.name = "zhangsan",
.sex = 'm',
.age = 30,
};
msgsnd(msqid, &m1, MSGSIZE, 0);
msg_t m2 = {
.id = 2,
.name = "lisi",
.sex = 'w',
.age = 18,
};
msgsnd(msqid, &m2, MSGSIZE, 0);
msg_t m3 = {
.id = 3,
.name = "wangwu",
.sex = 'm',
.age = 22,
};
msgsnd(msqid, &m3, MSGSIZE, 0);
// msgctl(msqid, IPC_RMID, NULL);
return 0;
}接受方:
#include "msgqueue.h"
int main(int argc, const char* argv[])
{
key_t key;
int msqid;
msg_t msg;
if ((key = ftok("/home/linux/", 'r')) == -1)
PRINT_ERR("ftok get key error");
if ((msqid = msgget(key, IPC_CREAT | 0666)) == -1)
PRINT_ERR("create msg queue error");
memset(&msg, 0, sizeof msg);
msgrcv(msqid, &msg, MSGSIZE, atoi(argv[1]), 0);
printf("id=%ld,name=%s,sec=%c,age=%d\n",msg.id,msg.name,msg.sex,msg.age);
// msgctl(msqid, IPC_RMID, NULL);
return 0;
}結果圖:

4.4共享內存
4.4.1原理:
共享內存:在內核空間創(chuàng)建共享內存,讓用戶的A和B進程都能夠訪問到。通過這塊內存
進行數據的傳遞。共享內存所有的進程間通信中效率最高的方式(不需要來回拷貝數據),共享內存的大小為 4k整數倍。
4.4.2實例
接受方:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <stdlib.h>
#define PRINT_ERR(msg) do{\
printf("%s %s %d\n", __FILE__, __func__, __LINE__);\
printf(msg);\
exit(-1); \
}while(0)
int main(int argc, char const *argv[])
{
key_t key;
int shmid;
char* over;
if ((key = ftok("/home/linux", 'r')) == -1){
PRINT_ERR("ftok error");
}
if ((shmid = shmget(key, 4096, IPC_CREAT|0666)) == -1){
PRINT_ERR("shemget error");
}
if ((over = shmat(shmid, NULL, 0)) == (void*)-1){
PRINT_ERR("shmat error");
}
while (1){
if (!strncmp("quit", over, 4))break;
getchar();
printf("%s\n",over);
}
if (shmdt(over)){
PRINT_ERR("shmdt error");
}
shmctl(shmid,IPC_RMID,NULL);
return 0;
}發(fā)送方:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <stdlib.h>
#define PRINT_ERR(msg) do{\
printf("%s %s %d\n", __FILE__, __func__, __LINE__);\
printf(msg);\
exit(-1); \
}while(0)
int main(int argc, char const *argv[])
{
key_t key;
int shmid;
char* over;
if ((key = ftok("/home/linux", 'r')) == -1){
PRINT_ERR("ftok error");
}
if ((shmid = shmget(key, 4096, IPC_CREAT|0666)) == -1){
PRINT_ERR("shmget error\n");
}
if ((over = shmat(shmid, NULL, 0)) == (void*)-1){
PRINT_ERR("shmat error\n");
}
while (1){
printf("請輸入>>");
fgets(over,4096,stdin);
over[strlen(over) - 1] = '\0';
if (!strncmp("quit",over,4)){
break;
}
}
if (shmdt(over)){
PRINT_ERR("shmdt error\n");
}
shmctl(shmid,IPC_RMID,NULL);
return 0;
}4.5信號燈集合
信號量的原理
信號量:又叫信號燈集,它是實現進程間同步的機制。在一個信號燈集中可以有很多的信號燈,這些信號燈它們的工作相關不干擾。一般使用的時候使用的是二值信號燈。
信號燈集函數的封裝
sem.h
#ifndef __SEM_H__
#define __SEM_H__
int mysem_init(int nsems);
int P(int semid, int semnum);
int V(int semid, int semnum);
int sem_del(int semid);
#endif
sem.c
#include <head.h>
union semun {
int val; /* Value for SETVAL */
struct semid_ds* buf; /* Buffer for IPC_STAT, IPC_SET */
};
int semnum_init_value(int semid, int which, int value)
{
union semun sem = {
.val = value,
};
if (semctl(semid, which, SETVAL, sem) == -1)
PRINT_ERR("semctl int value error");
return 0;
}
//初始化信號燈集
int mysem_init(int nsems)
{
key_t key;
int semid;
// 1.通過ftok獲取鍵值
if ((key = ftok("/home/linux/", 'g')) == -1)
PRINT_ERR("get key error");
// 2.如果不選擇就創(chuàng)建信號燈集,如果存在返回已存在的錯誤
if ((semid = semget(key, nsems, IPC_CREAT | IPC_EXCL | 0666)) == -1) {
if (errno == EEXIST) {
//如果已存在,這里調用semget,直接返回semid
semid = semget(key, nsems, IPC_CREAT | 0666);
} else {
PRINT_ERR("create sem error");
}
} else {
// 3.初始化信號燈集中的信號燈
for (int i = 0; i < nsems; i++) {
semnum_init_value(semid, i, !i);
}
}
return semid;
}
//申請資源
int P(int semid, int semnum)
{
struct sembuf buf = {
.sem_num = semnum,
.sem_op = -1,
.sem_flg = 0,
};
if (semop(semid, &buf, 1))
PRINT_ERR("request resource error");
return 0;
}
//釋放資源
int V(int semid, int semnum)
{
struct sembuf buf = {
.sem_num = semnum,
.sem_op = 1,
.sem_flg = 0,
};
if (semop(semid, &buf, 1))
PRINT_ERR("free resource error");
return 0;
}
//刪除信號燈集
int sem_del(int semid)
{
semctl(semid,0,IPC_RMID);
}用信號燈集實現進程同步
寫端:
#include <head.h>
#include "sem.h"
int main(int argc, const char* argv[])
{
key_t key;
int shmid,semid;
char* waddr;
//0.信號量的初始化
semid = mysem_init(2);
if(semid == -1){
printf("sem init error");
return -1;
}
// 1.獲取key
if ((key = ftok("/home/linux", 'p')) == -1)
PRINT_ERR("get key error");
// 2.創(chuàng)建共享內存
if ((shmid = shmget(key, 4096, IPC_CREAT | 0666)) == -1)
PRINT_ERR("create share memory error");
// 3.將共享內存映射到用戶空間
if ((waddr = shmat(shmid, NULL, 0)) == (void*)-1)
PRINT_ERR("shmat error");
printf("waddr = %p\n", waddr);
// 4.寫操作
while (1) {
P(semid,0);
printf("input > ");
fgets(waddr, 4096, stdin);
waddr[strlen(waddr) - 1] = '\0';
if (strncmp(waddr, "quit", 4) == 0)
break;
V(semid,1);
}
// 5.取消地址映射
if (shmdt(waddr))
PRINT_ERR("shmdt error");
// 6.刪除共享內存
if (shmctl(shmid, IPC_RMID, NULL))
PRINT_ERR("shmrm error");
//7.刪除信號量
sem_del(semid);
return 0;
}讀端口:
#include "sem.h"
#include <head.h>
int main(int argc, const char* argv[])
{
key_t key;
int shmid, semid;
char* raddr;
// 0.信號量的初始化
semid = mysem_init(2);
if (semid == -1) {
printf("sem init error");
return -1;
}
// 1.獲取key
if ((key = ftok("/home/linux", 'p')) == -1)
PRINT_ERR("get key error");
// 2.創(chuàng)建共享內存
if ((shmid = shmget(key, 4096, IPC_CREAT | 0666)) == -1)
PRINT_ERR("create share memory error");
// 3.將共享內存映射到用戶空間
if ((raddr = shmat(shmid, NULL, 0)) == (void*)-1)
PRINT_ERR("shmat error");
printf("waddr = %p\n", raddr);
// 4.讀操作
while (1) {
P(semid,1);
printf("raddr = %s\n", raddr);
if (strncmp(raddr, "quit", 4) == 0)
break;
V(semid,0);
}
// 5.取消地址映射
if (shmdt(raddr))
PRINT_ERR("shmdt error");
// 6.刪除共享內存
shmctl(shmid, IPC_RMID, NULL);
//7.刪除信號量
sem_del(semid);
return 0;
}以上就是C語言中進程間通訊的方式詳解的詳細內容,更多關于C語言進程通訊的資料請關注腳本之家其它相關文章!

