进程间的通信-IPC

产生背景:

fork 和 exec实现的通信很低效 -- 残疾通信


IPC包括:


管道:(无名和命名管道) 全双工和班双工
消息队列:
信号量;
共享存储:
Scoket,Streams -- 支持不同主机的IPC
 


管道:


特点:


1.默认无名管道-- 半双工(数据只在一个方向上流动)
2.只能在亲戚进程之间通信,父子,兄弟
3.特殊文件,可以用write , read 来写入,读取。不是普通文件,并不属于其他任何文件系统,只存在于内存

#半双工:


1.同一时间只能单向通信,一读一写,不能同时写,同时读 -- 算两个
2.管道不存储数据,读完就没了


pipe 函数建立(无名)管道:


头文件:<unistd.h>

函数原型:
int pipe(int fd[2]);

参数-- 2个文件描述符   fd[0]--read fd[1]--write

返回值: 成功返回0,失败返回-1

怎么关闭管道 -- close关闭两个文件描述符 fd[0] fd[1]

=====================================


case -- 实现无名管道的使用:


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

int main()
{
    int fd[2];
    char readBuf[128]={0};
    if(pipe(fd)==-1){
    puts("open pipe fail!!!");
    }
    int pid=fork();
    if(pid<0){
    puts("open fork fail!!!");
    }
    else if(pid>0){
    sleep(3); // 先让子进程执行,父进程先睡觉,发现子进程read一直阻塞程序,直到父进程睡完写入数据
    puts("this is father to write.");
    close(fd[0]); //关闭读,只写 -->半双工
    write(fd[1],"Hello from father",strlen("Hello from father"));
    wait(NULL);
    }
    else{
    puts("this is son to read.");
    close(fd[1]);//关闭写,只读
    read(fd[0],readBuf,128);
    printf("this is from father:%s\n",readBuf);
    exit(0);
    }

    return 0;
}

=============================


命名管道mkfifo


存在形式: 以 文件 的形式存放在磁盘中
open 一个 mkfifo时,是否设置非阻塞标志的区别(O_NONBLOCK)


默认没有指定 O_NONBLOCK (是否设置非阻塞标志)
只读open要阻塞到某个其他进程为写而打开此FIFO
只写open要阻塞到某个其他进程为读而打开此FIFO


if 指定  O_NONBLOCK : 一般不用
只读open立即返回,只写open出错返回-1,if没有进程已经为读打开FIFO,则errno设置为ENXIO


 


case1:实现命名管道:

打开基本读写,防止阻塞

//读程序
#include<unistd.h>
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<errno.h>

int main()
{
   if(mkfifo("./file",0600)==-1 && errno!=EEXIST){//判断错误原因是否是已经存在
   printf("mkfifo failuer\n");
   perror("why");
   }
   int fd=open("./file",O_RDONLY);
   printf("open read success\n");
    return 0;
}

// 写程序
#include<unistd.h>
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<errno.h>

int main()
{

   int fd=open("./file",O_WRONLY);
   printf("open write success\n");
    return 0;
}


==================================


case2--加入数据一致读写:


 

读文件

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

int main()
{
   char readBuf[1024]={0};
   int nread=0;
   if(mkfifo("./file",0600)==-1 && errno!=EEXIST){
   printf("mkfifo failuer\n");
   perror("why");
   }
   int fd=open("./file",O_RDONLY); //只读打开
   printf("open read success\n");
   while(1){ //时刻等待接收
   nread=read(fd,readBuf,30);
   printf("read num:%d\nmessage:%s\n",nread,readBuf);
   }
   close(fd);//用完关掉
    return 0;
}
写文件:
#include<unistd.h>
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<errno.h>
#include<string.h>

int main()
{
   int cnt=0;
   char * str="The message from  fifo.";
   int fd=open("./file",O_WRONLY); // 只写打开
   printf("open write success\n");
   while(1){
   sleep(1); //每秒写入一次
   int nwrite=write(fd,str,strlen(str));
 
   }
   close(fd);
    return 0;
}

======================================


消息队列:

linux内核中管理消息队列的链表
原理:
算是一个中间桥梁 -- B把数据写到内核中的消息队列,A读取队列中B的数据,当A,B使用同一个队列通讯的时候,实现A,B的通信


特点:


1.消息队列是面向记录的,其中的消息、具有特定的格式和特定的优先级
2.消息队列独立与发送和接收进程,进程终止,消息队列也不会清除
3.消息队列可以实现信息的随机查询,按消息类型读取(链表的数据结构特性)


实现A,B通信:
A:1.获取队列 2读
B:1.获取队列  3.写

消息队列API

头文件

       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/msg.h>
       -- 创建/打开消息队列,成功返回队列ID,失败返回-1:

函数原型
       int msgget(key_t key, int msgflg);
       //创建新的消息队列:
       1.if 没有键值与key相同的消息队列,并且flag 包含 IPC_CREAT标志位
       2.key的参数为 IPC_PRIVATE


       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/msg.h>
       -- 添加消息,成功返回0,失败-1
       int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

  

       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/msg.h>
       -- 读取消息,成功返回队列数据长度,失败返回-
       ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,  int msgflg);
      msgtyp ==0,返回消息队列中的第一个消息
      msgtyp >0 , 返回消息队列中类型为msgtyp 的第一个消息
       msgtyp <0,返回队列中消息类型绝对值小于等于msgtyp 绝对值的消息,if存在多个,则去类型在,制最小的消息

       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/msg.h>
       -- 消息队列的的控制(删除):成功返回0,失败返回-1
       int msgctl(int msqid, int cmd, struct msqid_ds *buf);

//可以看到四个API的头文件都一样

======================================


 case1:实现消息共享:

//获取队列  写入队列
#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include<string.h>
// int msgget(key_t key, int msgflg);
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,  int msgflg);
// int msgctl(int msqid, int cmd, struct msqid_ds *buf);
struct msgbuf
{
    long mtype;
    char mtext[128];
};


int main()
{
    struct msgbuf sendBuf={888,"this is message from que."};
    int msgId=msgget(0x1234,IPC_CREAT|0777);//创建消息队列
    if(msgId == -1){
        puts("get message que failed\n");
    }
    msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);// 向队列中添加消息

    return 0;
}

// 获取队列,读取队列消息


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

// int msgget(key_t key, int msgflg);
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,  int msgflg);
// int msgctl(int msqid, int cmd, struct msqid_ds *buf);
struct msgbuf
{
    long mtype;
    char mtext[128];
};


int main()
{
    struct msgbuf readBuf;
    int msgId=msgget(0x1234,IPC_CREAT|0777);// 获取消息队列
    if(msgId == -1){
        puts("get message que failed\n");
    }
    msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0); //读取队列中的消息
    printf("read from message que :%s\n",readBuf.mtext);

    return 0;
}

=================================


case2:消息队列实现双全工 同时收发


#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include<string.h>
// int msgget(key_t key, int msgflg);
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,  int msgflg);
// int msgctl(int msqid, int cmd, struct msqid_ds *buf);
struct msgbuf
{
    long mtype;
    char mtext[128];
};


int main()
{
    struct msgbuf readBuf;
    int msgId=msgget(0x1234,IPC_CREAT|0777);
    if(msgId == -1){
        puts("get message que failed\n");
    }
    //先接收
    msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);
    printf("read 1 from message que :%s\n",readBuf.mtext);
    //再发送
    struct msgbuf sendBuf={988,"this is return message from que."};//换一个接口988
    msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);
    return 0;
}


#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include<string.h>
// int msgget(key_t key, int msgflg);
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,  int msgflg);
// int msgctl(int msqid, int cmd, struct msqid_ds *buf);
struct msgbuf
{
    long mtype;
    char mtext[128];
};


int main()
{
    struct msgbuf sendBuf={888,"this is send message from que."};
    struct msgbuf readBuf;
    int msgId=msgget(0x1234,IPC_CREAT|0777);
    if(msgId == -1){
        puts("get message que failed\n");
    }
    //先发送
    msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);

    //再接收
    msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),988,0);//换一个接口988
    printf("read 2 from message que :%s\n",readBuf.mtext);
    return 0;
}
==================================
 

共享内存:

描述:

A,B之间创建一块共享内存,让他们的信息交流无障碍(使用频率高  -- 先进好用)

相关API:

头文件: <sys.shm.h>

// 创建/获取一块共享内存,成功返回内存ID,失败-1
int shmget(key_t key, size_t size, int shmflg);// 注意 共享内存大小必须以M对齐

// 连接共享内存到当前进程地址空间 , 成功返回指向共享内存的指针,失败返回-1

void *shmat(int shmid, const void *shmaddr, int shmflg);
//第二 第三个参数一般都是0,第二个0 -- linux 内核自动安排共享内存,第三个0 -- 共享内存权限可读可写

//断开与共享内存的连接,成功返回0,失败-1
int shmdt(const void *shmaddr);

      #include <sys/ipc.h>
       #include <sys/shm.h>
//控制共享内存的相关信息,成功0,失败-1
       int shmctl(int shmid, int cmd, struct shmid_ds *buf); //第二第三参数: IPC_RMID   0--不关心详细信息

步骤:
1. 先创建/打开共享内存  -- shmget
2.映射 --*shmat
3.数据 -- strcpy 写入数据(操作自己的额内存怎么写就怎么写)
4.释放内存空间 -- shmdt
5.删除共享内存 -- shmctl

sleep在 unistd.h中

linux 指令查看系统中的共享内存:  ipcs -m    

ipcrm -m 共享内存id -- 删除指定共享内存
==============================


case:

-- 写

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

int main()
{

// int shmget(key_t key, size_t size, int shmflg);
// 注意 共享内存大小必须以M对齐
key_t key;
int shmId;
char *shmaddr;
key=ftok("./",1);
shmId=shmget(key,1024*2,IPC_CREAT|0666);//创建共享内存
if(shmId == -1){
puts("shmget fail!");
exit(-1);
}
//void *shmat(int shmid, const void *shmaddr, int shmflg); //连接当前进程地址到共享内存
//第二 第三个参数一般都是0,第二个0 -- linux 内核自动安排共享内存,第三个0 -- 共享内存权限可读可写

shmaddr=shmat(shmId,0,0);
puts("shmat ok.");
strcpy(shmaddr,"mxjun"); // 在这段地址中写入
//int shmdt(const void *shmaddr);
sleep(5);
shmdt(shmaddr);//释放内存空间
// int shmctl(int shmid, int cmd, struct shmid_ds *buf); 
//第二第三参数: IPC_RMID   0--不关心详细信息
shmctl(shmId,IPC_RMID,0); //删除(杀死)共享内存
puts("quit");

    return 0;
}


---读

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

int main()
{

// int shmget(key_t key, size_t size, int shmflg);
// 注意 共享内存大小必须以M对齐
key_t key;
int shmId;
char *shmaddr;
key=ftok("./",1);
shmId=shmget(key,1024*2,0);
if(shmId == -1){
puts("shmget fail!");
exit(-1);
}
//void *shmat(int shmid, const void *shmaddr, int shmflg);
//第二 第三个参数一般都是0,第二个0 -- linux 内核自动安排共享内存,第三个0 -- 共享内存权限可读可写

shmaddr=shmat(shmId,0,0);
puts("shmat ok.");
printf("data= %s\n",shmaddr);
//int shmdt(const void *shmaddr);
sleep(5);
shmdt(shmaddr);
// int shmctl(int shmid, int cmd, struct shmid_ds *buf); 
//第二第三参数: IPC_RMID   0--不关心详细信息
puts("quit");


    return 0;
}
=================================
 

Linux 信号(Signal):

对于linux来说,信号是软中断,许多主要重新都需要中断。ctrl + c退出  也是中断程序。
但A,B同时写共享内存的一块地方的时候就会出现信息杂糅 -- 信号来处理

信号定义在"signal.h"头文件里

指令kill -l:    查看系统中的所有信号;

信号处理方式:


忽略 (SIGNKILL  SIGNSTOP不能忽略) 
捕捉: 类似槽函数,告诉内核希望如何处理某信号  -- 信号的最大意义 -- 实现异步通信
默认: 系统对每个信号都有其默认处理动作

kill -9 pid -- 杀死进程 9--对应信号SIGNKILL


=====================================


signal:


信号处理函数
signal
高级:
sigaction

      #include <signal.h>

       typedef void (*sighandler_t)(int);

       sighandler_t signal(int signum, sighandler_t handler);

函数原型:
  signal(SIGINT,handler); -- 收到SINGINT 信号的时候调用handler函数

case1:不给ctrl + c退出的程序(ctrl +z -- signstop) -- 检测你发送的信号

#include<stdio.h>
#include<signal.h>
void handler(int signum){
printf("get signum=%d\n",signum);
switch (signum)
{
case 2:
printf("SIGINT\n");
    break;
    case 9:
printf("SIGKILL\n");
    break;
    case 10:
printf("SIGUSR1\n");
    break;

default:
    break;
}

printf("never be killer\n");
}

int main()
{
  signal(SIGINT,handler);
  signal(SIGKILL,handler);
  signal(SIGUSR1,handler);
while(1);

    return 0;
}

kill函数

头文件:
       #include <sys/types.h>
       #include <signal.h>

函数原型:    

   int kill(pid_t pid, int sig); // 使用kill发信号
//ASCLL码值转整形数
       #include <stdlib.h>

       int atoi(const char *nptr);
       long atol(const char *nptr);
       long long atoll(const char *nptr);

==================


case: kill函数发信号



#include<stdio.h>
#include<signal.h>
#include<sys/types.h>
#include<stdlib.h>


int main(int argc,char**argv)
{
    int  signum;
    int  pid;
    char cmd[128]={0};
    signum=atoi(argv[1]);  // 注意转换为字符串
    pid=atoi(argv[2]);
    printf("num=%d\t pid=%d\n",signum,pid);
// int kill(pid_t pid, int sig); 
   // kill(pid,signum);

   sprintf(cmd,"kill -%d %d",signum,pid);
    system(cmd);
    puts("send signal ok");
    return 0;
}
=============================

sigaction

 头文件

       #include <signal.h>

函数原型

       int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);

发信号:sigqueue


 头文件

       #include <signal.h>

函数原型

       int sigqueue(pid_t pid, int sig, const union sigval value);

case:sigaction实现 数据接收

#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

// int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

void handler(int signum, siginfo_t *info, void *context)
{
    printf("signum=%d\n", signum);
    if (context != NULL)
    {
        printf("get data = %d\n", info->si_int);
        printf("get data = %d\n", info->si_value.sival_int);
        printf("from:%d\n",info->si_pid);
    }
}

int main()
{
    struct sigaction act;
    printf("pid = %d\n",getpid());
    act.sa_sigaction = handler;
    act.sa_flags = SA_SIGINFO;
    sigaction(SIGUSR1, &act, NULL);
    while (1);
    return 0;
}

---------------
发数据

#include<signal.h>
#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include<stdlib.h>
//  int sigqueue(pid_t pid, int sig, const union sigval value);


int main(int argc,char**argv)
{
    int  signum;
    int  pid;

    signum=atoi(argv[1]);
    pid=atoi(argv[2]);
    //printf("num=%d\t pid=%d\n",signum,pid);
    union sigval value;
    value.sival_int=100;//待发送 的数据 
    sigqueue(pid,signum,value);//发数据
    printf("pid = %d\n",getpid());
    


    puts("done");
    return 0;
}
=====================================
 


信号量的三个API:     

头文件  

   #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>
      // 创建/获取一个信号量组:if成功放回信号量集ID,失败放回-1
       int semget(key_t key, int nsems, int semflg);
         //对信号量组进行操作,改变信号量额值,成功0,失败-1
       int semop(int semid, struct sembuf *sops, size_t nsops);

       //int semtimedop(int semid, struct sembuf *sops, size_t nsops,
                      const struct timespec *timeout);
       // 控制信号量相关信息
          int semctl(int semid, int semnum, int cmd, ...);


case1:初始化信号量:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

union semun
{
    int val;               /* Value for SETVAL */
    struct semid_ds *buf;  /* Buffer for IPC_STAT, IPC_SET */
    unsigned short *array; /* Array for GETALL, SETALL */
    struct seminfo *__buf; /* Buffer for IPC_INFO
                              (Linux-specific) */
};

int main(int argc, char **argv)
{
    int semid;
    key_t key;
    key = ftok(".", 2);
    //   int semget(key_t key, int nsems, int semflg);
    // 获取创建信号量
    semid = semget(key, 1, IPC_CREAT | 0666); // 1 --希望只有一个信号量
    union semun initsem;
    initsem.val = 1;
    // 初始化信号量
    semctl(semid, 0, SETVAL, initsem); // 0--操作第0个信号量
    // SETVAL -- 设置信号量的值
    int pid = fork();
    if(pid>0){
        puts("this is father");
        //去拿🔓

        //🔓放回去
    }
    else if(!pid){
        puts("this is son");
    }
    else{
        puts("fork error!!!");
    }
    return 0;
}


============================


case2:控制 信号量控制子进程先进行:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

union semun
{
    int val;               /* Value for SETVAL */
    struct semid_ds *buf;  /* Buffer for IPC_STAT, IPC_SET */
    unsigned short *array; /* Array for GETALL, SETALL */
    struct seminfo *__buf; /* Buffer for IPC_INFO
                              (Linux-specific) */
};
void pGetKey(int id)
{
//int semop(int semid, struct sembuf *sops, size_t nsops);
struct sembuf set;
set.sem_num=0;
set.sem_op=-1;
set.sem_flg=SEM_UNDO;
semop(id,&set,1);
printf("get key\n");

}

void vPutBackKey(int id)
{
//int semop(int semid, struct sembuf *sops, size_t nsops);
struct sembuf set;
set.sem_num=0;
set.sem_op=1;
set.sem_flg=SEM_UNDO;
semop(id,&set,1);
printf("put back key\n");

}


int main(int argc, char **argv)
{
    int semid;
    key_t key;
    key = ftok(".", 2);
    //   int semget(key_t key, int nsems, int semflg);
    // 获取创建信号量
    semid = semget(key, 1, IPC_CREAT | 0666); // 1 --希望只有一个信号量
    union semun initsem;
    initsem.val = 0; // 房间钥匙被拿
    // 初始化信号量
    semctl(semid, 0, SETVAL, initsem); // 0--操作第0个信号量
    // SETVAL -- 设置信号量的值
    int pid = fork();
    if(pid>0){
        //去拿🔓
        pGetKey(semid);
        puts("this is father");
       
        //🔓放回去
        vPutBackKey(semid);
    }
    else if(!pid){
        puts("this is son");
         //🔓放回去
        vPutBackKey(semid);
    }
    else{
        puts("fork error!!!");
    }
    return 0;
}


// 初始化,把锁拿走,父亲开不了门,不能执行,儿子先放钥匙进去才能让父亲开门,so每次都是子进程先运行

====================================


进程间的通信-IPC:


1.无名管道
2.命名管道
3.消息队列
4. 共享内存
5信号
6.信号量


 

  • 23
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值