学习如何利用管道机制或消息缓冲队列进行进程间的通信,并加深对上述通信机制的理解。提高学生分析问题和解决问题的能力,并学习撰写规范的科学研究报告。
编写一段程序,使用管道来实现父子进程之间的进程通信。子进程向父进程发送自己的进程表示符,以及某字符串。父进程则通过管道读出子进程发来的消息,将消息显示在屏幕上,然后终止。或者,编写一段程序,使其用消息缓冲队列来实现client和server进程之间的通信。
(1)了解系统pipe() ,msgsnd(), msgrcv()的功能和实现过程。
(2)编写一段程序,使其用管道来实现父子进程之间的进程通信。
仪器:PC机
实验环境: Ubuntu16.04
管道是连接读写进程的一个共享文件,允许进程以先进先出的方式写入和读出数据,并对读写操作进行同步。发送进程以字符流形式把大量数据送入管道尾部,接收进程从管道头部接收数据。
(1)管道应该互斥使用,管道读写不能同时进行。一个进程正在执行管道写入或读出操作时,另一进程必须等待。读写结束时,唤醒等待进程,自己应阻塞。
(2)读写双方必须能够知道对方是否存在,只有存在才能通信。系统发送SIGPIPE信号通知进程对方不存在。
(3)管道空间的大小通常是固定的,读写操作时需要考虑写满和读空问题。
int fds[2];
pipe (fds);
函数调用成功返回r/w两个文件描述符,规定fd[0]->r;fd[1]->w。有点类似于0对应标准输入,1对应标准输出。向管道文件读写数据其实是在读写内核缓冲区。
头文件:
#include
函数定义:
int msgsnd(int msgid, struct msgbuf *msgp, int msgsz, int msgflg);
功能:往队列中发送一个消息
返回值:成功返回0,失败返回-1;
参数:
msgid:消息标识id,也是msgget()函数的返回值。
msgp:指向消息缓冲区的指针,该结构体为:
struct mymesg{
long mtype;//消息类型
char mtext[512];//存放的数据内容
}
msgsz:消息的字节大小,不包含消息类型的长度(4字节)
msgflg:可以设置为0,控制消息进入消息队列或返回进程中。
头文件:
#include
格式:
int msgrcv(int msgid, struct msgbuf *msgp, int msgsz, long mtype, int msgflg);
功能:读取消息,并且把消息队列中消息读完。
返回值:成功返回0,失败返回-1;
参数:
msgid:消息队列的id号
msgp:我们要读到的消息数据存储消息的地址
msgsz:消息的长度
mtype:我们要从消息队列中读取的消息类型。如果此值为0,则会读取时间最长的那一条消息,即我们第一个写入消息队列中的消息。
msgflg:可以设置为0,控制消息进入消息队列或返回进程中。
头文件:#include
格式:int msgget(key_t key,int msgflg);
功能:创建一个消息队列或者取得一个消息队列;
返回值:成功则返回消息队列的标识符,失败返回-1;
参数:
key:消息队列的键值,为IPC_PRIVATE时创建一个只能被创建进程读写的消息队列。IPC_CREAT如果内存不存在则创建一个消息队列,否则直接取得。
头文件:#include
格式:int msgctl(int msgid,int cmd,struct msgqid_ds *buf)
功能:对消息队列的控制处理函数
返回值:成功返回0,失败返回-1
参数:
msgid是msgget()函数的返回值
cmd 是对消息队列的处理,如IPC_RMID是从系统内核中删除队列消息、IPC_STAT获取消息队列的详细信息。IPC_SET设置消息队列的信息。
buf是存放消息队列状态结构体的地址。
#include
#include
#include
#include
#include
#include
int main()
{
pid_t pid;
char buf[1024];
int fd[2];
char *p1 = "this is child.\nAnd child's id is:";//定义字符数组p1
if(-1==pipe(fd))
{printf("pipe error.");}
pid = fork();
if(pid<0)
{printf("fork error.");}
else if(0==pid)
{
int id = getpid();//获得子进程序列号
char *p2;
sprintf(p2,"%d",id);//用sprintf把子进程序列号从int型转为char型数组
close(fd[0]);//子进程关闭管道读端
write(fd[1],p1,strlen(p1));//子进程向管道写入p1、p2
write(fd[1],p2,strlen(p2));
close(fd[1]);
}
else
{
close(fd[1]);//父进程关闭管道写端
int len = read(fd[0],buf,sizeof(buf));
write(STDOUT_FILENO,buf,len);
close(fd[0]);
}
}
调试过程:
在使用pipe()进行管道通信时,只能使用write()函数向管道里面写入数据。
write函数用法为:write(int fd, const void *buf, size_t nbyte);
但是子进程序列号是int型,所以要用sprintf()函数把int型的id转为字符数组。
利用消息缓冲队列来实现server和client端的通信。
#include
#include
#include
#include
#include
#include
#include
#define MSGKEY 75 //设置msgkey
struct msgform{//定义msgform结构体
long mtype;
char mtext[1024];
};
int main()
{
int id;
struct msgform msg;
id = msgget(MSGKEY,0777|IPC_CREAT);//创建消息缓冲队列
if(-1==fork())
{
printf("fork error!\n");
}
else if(0==fork())
{
msg.mtype = 1;
sprintf(msg.mtext,"%d",getpid());//获取子进程标识符
msgsnd(id,&msg,sizeof(msg),0);//子进程通信过程
sleep(1);
msgrcv(id,&msg,sizeof(msg),0,0);
printf("receive reply form %s\n",msg.mtext);
}
else
{
msgrcv(id,&msg,sizeof(msg),0,0);//获取消息
if(1==msg.mtype)//父进程通信
{
printf("%d\n",getpid());
printf("serving for client,%s\n",msg.mtext);
sprintf(msg.mtext,"%d",getppid());
msgsnd(id,&msg,sizeof(msg),0);
sleep(2);
msgctl(id,IPC_RMID,0);//删除队列消息
}
}
return 0;
}