Unix C (九)

来源:互联网 发布:台视直播软件 编辑:程序博客网 时间:2024/05/29 07:46
进程间通信————IPC(Inter Process Communication)
1、Unix/Linux系统基于多进程,进程和进程之间经常做数据的交互,这种技术称为进程间通信。
2、进程间通信的方式:
1)文件
2)信号
3)管道(最古老的IPC之一,目前较少使用)
4)共享内存
5)消息队列
6)信号量集
7)网络编程(socket)
...
其中共享内存、消息队列和信号量集遵循相同的规范,因此编码上有很多的共同点,因此我们把这三个统称为XSI IPC。网络编程以前用于IPC,现在更多的用于网络。
3、管道就是用管道文件做交互媒介的IPC。管道文件是一种特殊的文件(touch、open和fopen函数无法创建管道文件),使用ls命令时,文件类型是p。
4、管道的文件要用mkfifo命令/函数创建管道文件。
5、管道是文件交互的媒介,不存储任何数据;只有在读进程写进程才能畅通,否则阻塞。
6、管道分为两种:有名管道和无名管道。
有名管道:由程序员创建管道文件进行IPC,可以用于所有进程之间的交互
无名管道:由系统创建和维护管道文件进行IPC,只能用于fork()创建的父子进程之间的交互
7、有名管道的用法:
1)用mkfifo命令/函数创建管道文件。
2)像读写文件一样操作文件。

3)如果不再使用管道文件,可以删除。

实例:

(1)pipea.c代码:

/*
   pipea.c与pipeb.c的通信
 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>


int main(){
mkfifo("ab.pipe",O_WRONLY | O_CREAT);
int fd = open("ab.pipe",O_WRONLY | O_CREAT | O_TRUNC,0666);
if(fd == -1){
perror("open"),exit(-1);
}


char buf[4] = {};
int i = 0;
for(i = 0; i < 100; i++){
sprintf(buf,"%d",i);
write(fd,buf,sizeof(buf));
}


close(fd);

return 0;
}

(2)pipeb.c代码:

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


int main(){
int fd = open("ab.pipe",O_RDONLY);
if(fd == -1){
perror("open"),exit(-1);
}
char buf[4];
while(1){
int res = read(fd, buf, sizeof(buf));
if(res <= 0){
break;
}
printf("%s ",buf);
}
printf("\n");


close(fd);


return 0;
}


知识补充:
1、规范(标准)是行业准则,任何相关软件都必须遵守。标准是行业共同协商的成果。
2、产品就是遵循标准的软件,产品更注重质量,不为个别客户服务的。比较轻松,不用特别赶时间。
3、项目是只针对特定客户的定制,客户的影响力非常大,时间一般特别紧。比较近,而且需要年轻化。


共享内存、消息队列和信号量集的通用规范:
1、所有的IPC结构都有一个内部的ID做唯一的标识。
2、内部ID的获取需要借助外部的key,类型key_t。
    3、key的获取有三种方式:
a 使用宏IPC_PRIVATE做key,但这种方式外部无法获取,因此基本不用。
b 使用ftok()提供一个key。
c 在头文件中统一定义所有的key,防止重复key。
     4、用key获取内部id的函数都是 xxxget(),比如: shmget() 、msgget()
5、每种IPC结构都提供了一个 xxxctl()函数,这个函数的功能至少包括:
查询、修改和删除。
其中有一个cmd参数,值:
IPC_STAT 查询
IPC_SET         修改
IPC_RMID 按ID删除IPC结构
6、所有IPC结构都是内核管理,不使用时需要手工删除。
7、IPC结构的相关命令:
ipcs 查询当前的IPC结构
ipcrm       删除当前的IPC结构(用id删除)
显示
ipcs -a 显示所有共享内核对象
ipcs -m 显示共享内存   m=memory
ipcs -q 显示共享队列q=queue
ipcs -s 显示信号量 s=semaphore
删除
ipcrm -m ID  删除共享内存
ipcrm -q ID  删除共享队列
ipcrm -s ID  删除信号量


共享内存:
以一块共享的物理内存做媒介。通常情况下,两个进程无法直接映射相同的内存。共享的实现:
1 内核先拿出一块物理内存,内核负责管理。
2 允许所有进程对这块内存进行映射。
3 这样两个不同的进程就可以映射到相同的物理内存上,从而实现信息的交互。
共享内存是效率最高的IPC。
编程步骤:
1、 获取 key,方式ftok()或头文件定义。
2、 使用shmget()函数创建/获取内部ID。
3、 使用shmat()挂接共享内存(映射)。
4、 可以像正常操作一样使用共享内存。
5、 使用shmdt()脱接共享内存(解除映射)。
6、 如果确定已经不再使用,可以使用shmctl()删除共享内存。
7、 key_t ftok(char* pathname,int projectid)
参数
pathname是一个真实存在的可访问的路径
projectid是项目编号,低8位有效(1~255)
返回key。
ftok()如果给定路径有效,不会出错。会按照路径和项目ID生成一个key。相同的路径+相同的项目ID生成相同的key。
8、 int shmget(key_t key,size_t size,int flag)
参数:
key就是第一步返回值,外部的key
    size就是共享内存的大小
    flag在获取时用0;在新建时用IPC_CREAT|0666(权限)
成功返回共享内存的ID,失败返回-1.
9、 void* shmat(int shmid,0,0) 可以挂接
10、int  shmdt(void* addr)adrr是shmat()的返回值,首地址(虚拟)。
11、shmctl() 可以查询、修改和删除共享内存。
函数原型: int shmctl(int shmid, int cmd, struct shmid_ds *buf);
struct shmid_ds {
        struct ipc_perm shm_perm;    /* 权限 */
        size_t          shm_segsz;   /* 共享内存的大小 */
        time_t          shm_atime;   /* 最近挂接时间 */
        time_t          shm_dtime;   /* 最近脱接时间 */
        time_t          shm_ctime;   /* 最近修改时间 */
        pid_t           shm_cpid;    /* 创建者的进程号 */
        pid_t           shm_lpid;    /* 挂接脱接进程号 */
        shmatt_t        shm_nattch;  /* 挂接数目 */
        ...
    };
    
    struct ipc_perm {
        key_t          __key;    /* 外部key */
        uid_t          uid;      /* 用户主id */
        gid_t          gid;      /* 用户群组id */
        uid_t          cuid;     /* 创建用户的id */
        gid_t          cgid;     /* 创建用户群组的id */
        unsigned short mode;     /* 权限 */
        unsigned short __seq;    /* 共享内存数目 */
    };


查询时,会把共享内存的信息放入第三个参数
修改时,只有用户id、组id和权限可以修改。
删除时,第三个参数给0 即可。
第三个参数是结构体指针:struct shmid_ds 
12、删除共享内存时,挂接数必须为0才能真正删除,否则删除只是做一个删除标记,等挂接数为0时才真正删除。
13、共享内存虽然速度最快,但当多个进程同时写数据时,会发生互相覆盖,导致数据混乱。

实例:

(1)

/*
   共享内存使用演示
 */
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>


int main(){
//创建外部key
key_t key = ftok(".",100);
if(key == -1){
perror("ftok"),exit(-1);
}


//获取创建内部ID
int shmid = shmget(key, 4, IPC_CREAT | 0666);
if(shmid == -1){
perror("shmget"),exit(-1);
}


//映射物理内存
void* p = shmat(shmid, 0, 0);
if(p == (void *)-1){
perror("shmat"),exit(-1);
}


//使用共享物理内存
*((int *)p) = 100;


//解除映射
shmdt(p);


return 0;
}

(2)

/*
   共享内存获取演示
 */
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>


int main(){
//获取外部key
key_t key = ftok(".",100);
if(key == -1){
perror("ftok"),exit(-1);
}


//获取内部id
int shmid = shmget(key, 0, 0);
if(shmid == -1){
perror("shmget"),exit(-1);
}


//映射物理内存
void* p = shmat(shmid, 0, 0);
if(p == (void *)-1){
perror("shmat"),exit(-1);
}


//使用共享内存
printf("%d\n",*((int *)p));


//解除映射
shmdt(p);


return 0;
}

(3)

/*
   删除共享内存
 */


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


int main(){
//获取外部key
key_t key = ftok(".",100);
if(key == -1){
perror("ftok"),exit(-1);
}


//获取内部ID
int shmid = shmget(key, 0, 0);
if(shmid == -1){
perror("shmget"),exit(-1);
}


//查询
struct shmid_ds ds;
shmctl(shmid, IPC_STAT, &ds);
printf("size = %d\n",ds.shm_segsz);  //大小
printf("mode = %o\n",ds.shm_perm.mode);
printf("key = %x\n",ds.shm_perm.__key);
printf("nattch = %d\n",ds.shm_nattch);


ds.shm_segsz = 400;   //修改不成功
ds.shm_perm.mode = 0640; //可以修改
shmctl(shmid,IPC_SET,&ds); //修改
shmctl(shmid,IPC_RMID,&ds); //删除


return 0;
}

消息队列:
1、消息队列就可以解决多个进程同时写数据的问题。
2、消息队列就是存放消息的队列。队列是线性的数据结构,先入先出(FIFO)。
3、一般情况下,队列有满有空。数据先封入消息中,然后再把消息存入队列。
4、消息队列的编程步骤:
1 得到外部的key,函数ftok()。
2 用key创建/获取一个队列(消息队列),函数msgget()。
3 把数据/消息存入队列或从队列中取出。函数 msgsnd()存入/msgrcv()取出
4 如果不再使用消息队列,可以msgctl()删除。


5、msgget() msgctl()与共享内存的函数相似。
6、int msgsnd(int msgid,void* msgp,size_t size,int flag)
参数 
第一个参数msgid就是消息队列的ID
第二个参数msgp就是消息的首地址,其中消息分为有类型消息和无类型消息,更规范的是 有类型消息。有类型消息就是一个结构:
struct msgbuf{ //结构的命令可以自定义
long mtype; //消息的类型,必须大于0
... //数据区域,可以任意写
};
将来在接收消息时,可以按照类型有选择的接收消息。
第三个参数size参数是数据区域的大小,不包括mtype(有些时候包括了也行)。
第四个参数flag就是选项,可以是0代表阻塞(队列满了等待),也可以是IPC_NOWAIT代表非阻塞(队列满了直接返回错误)。
返回值:成功返回0,失败返回-1.
7、int msgrcv(int msgid,void* msgp,size_t size,long mtype,int flag)
参数
第一个参数msgid就是消息队列的ID
第二个参数msgp就是消息的首地址,其中消息分为有类型消息和无类型消息,更规范的是 有类型消息。有类型消息就是一个结构:
struct msgbuf{ //结构的命令可以自定义
long mtype; //消息的类型,必须大于0
... //数据区域,可以任意写
};
将来在接收消息时,可以按照类型有选择的接收消息。
第三个参数size参数是数据区域的大小,不包括mtype(有些时候包括了也行)
第四个参数mtype可以让接收者有选择的接收消息,值可能是:
0 接收任意类型的消息(先入先出)
>0 接收 类型为mtype的特定消息
<0 接收类型小于等于 mtype绝对值的消息,从小到大接收。
第五个参数flag就是选项,可以是0代表阻塞(队列满了等待),也可以是IPC_NOWAIT代表非阻塞(队列满了直接返回错误)。
返回值:成功返回接收到的数据大小,失败返回-1.

实例:

无形消息队列:

(1)发送消息程序

/*
   无型消息的发送
 */


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


int main(){
//创建外部key
key_t key = ftok(".",100);//可以和共享内存相同的key
if(key == -1){
perror("ftok"),exit(-1);
}


//创建内部id
int msgid = msgget(key, IPC_CREAT | 0666);
if(msgid == -1){
perror("msgget"),exit(-1);
}


//放入消息
int res = msgsnd(msgid, "hello", 5, 0);
if(!res){
printf("发送成功\n");
}
else{
perror("msgsnd"),exit(-1);
}


return 0;
}

(2)接受消息程序

/*
   无型消息的获取,先入先出
 */


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


int main(){
//创建外部key
key_t key = ftok(".",100);
if(key == -1){
perror("ftok"),exit(-1);
}


//获取内部id
int msgid = msgget(key, 0);
if(msgid == -1){
perror("msgget"),exit(-1);
}


//取出数据
char buf[50] = {};
while(1){
int res = msgrcv(msgid, buf, sizeof(buf), 0, 0);
if(res <= 0){
break;
}
printf("%s\n",buf);
}


return 0;
}

有型消息

(1)发送消息程序

/*
   有类型消息队列演示
 */


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


struct msgbuf{
long mtype;
char buf[100];
};


int main(){
//创建外部key
key_t key = ftok(".",50);
if(key == -1){
perror("ftok"),exit(-1);
}


//获取内部id
int msgid = msgget(key, IPC_CREAT | 0666);
if(msgid == -1){
perror("msgget"),exit(-1);
}


//准备发送消息
struct msgbuf msg1, msg2;
msg1.mtype = 1;
strcpy(msg1.buf,"zhangfei");
msg2.mtype = 2;
strcpy(msg2.buf,"guanyu");


int res = msgsnd(msgid, &msg1, sizeof(msg1.buf), 0);
if(res == -1){
perror("msgsnd1"),exit(-1);
}
res = msgsnd(msgid, &msg2, sizeof(msg2.buf), 0);
if(res == -1){
perror("msgsnd2"),exit(-1);
}
printf("Send OK!\n");


return 0;
}

(2)接受消息程序

/*
   有型消息队列获取
 */


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


struct msgbuf{
long mtype;
char buf[100];
};


int main(){
//获取外部key
key_t key = ftok(".",50);
if(key == -1){
perror("ftok"),exit(-1);
}


//获取内部id
int msgid = msgget(key, 0);
if(msgid == -1){
perror("msgget"),exit(-1);
}


//获取消息队列中的消息
struct msgbuf msg;
//int res = msgrcv(msgid, &msg, sizeof(msg.buf), 0, 0);   //mtype值为0时表示任意数据类型,先进先出
//int res = msgrcv(msgid, &msg, sizeof(msg.buf), 1, 0);//mtype值为1,指明获取类型为1的消息
int res = msgrcv(msgid, &msg, sizeof(msg.buf), 2, 0);//mtype值为2,指明获取类型为2的消息
if(res == -1){
perror("msgrcv"),exit(-1);
}
printf("%s\n",msg.buf);


return 0;
}


 信号量集(信号量数组)
 信号量用于 控制同时访问某资源的进程的最大数量,信号量集就是多个信号量组成的集合(信号量数组)。

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


int main(){
  key_t key = ftok(".",100);
  int semid = semget(key,0,0);
  if(semid == -1) perror("semget"),exit(-1);
  int i;
  for(i=0;i<10;i++){
    pid_t pid = fork();
    if(pid == 0){
 struct sembuf op;
 op.sem_num = 0;//对下标为0的信号量进行操作
 op.sem_op = -1;//对信号量-1
 op.sem_flg = 0;//也可以是IPC_NOWAIT 不等待
 semop(semid,&op,1);//用元素地址替换了数组地址
 printf("访问共享资源\n");
 sleep(10);
 printf("操作结束\n");
 op.sem_op = 1; //对信号量+1
 semop(semid,&op,1);
 exit(0);
}
  }  
}

0 0
原创粉丝点击