实验目的:了解和熟悉linux支持的消息通信机制。
任务:使用linux系统提供的系统调用msgget(),msgrev(),msgctl()编制一个长度为1K的消息发送和接受的程序。
实验要求:
(1) 用一个程序作为“引子”,先后fork()两个进程,SERVER和CLIENT,进行通信
(2) SERVER端建立一个Key为75的消息队列,等待其他进程发来的消息。当遇到类型为1的消息,则作为结束信号,取消该队列,并退出SERVER。SERVER每接受到一个消息后显示一句“(Server)received”,然后发送一个返回消息给CLIENT端,显示一句“(Server)sent”。
(3) CLIENT端使用key为75的消息队列,先后发送类型从10到1的消息,然后退出。最后一个消息,即是SERVER端需要的结束信号。CLIENT每发送一条消息后显示一句“(Client)sent”,等接受SERVER端的返回消息后,显示一句“(Client)received”,再在发送下一条消息。
(4) 父进程在SERVER和CLIENT都退出后结束。
下面的函数使用的头文件:
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
- msgget( )
创建一个消息,获得一个消息的描述符。
系统调用格式:int msgqid=msgget(key,flag)
参数定义
key_t key;
int flag;
key是用户指定的消息队列的名字;
flag是用户设置的标志和访问方式。如
IPC_CREAT |0400 是否该队列已被创建。无则创建,是则打开;
IPC_EXCL |0400 是否该队列的创建应是互斥的。
msgqid 是该系统调用返回的描述符,失败则返回-1。
- msgsnd()
发送一消息。向指定的消息队列发送一个消息,并将该消息链接到该消息队列的尾部。
系统调用格式:int msgsnd(msgqid,msgp,size,flag)
参数定义:
int msgqid,size,flag;
struct msgbuf * msgp;
其中msgqid是返回消息队列的描述符;msgp是指向用户消息缓冲区的一个结构体指针,包括消息类型和消息正文,即
{
long mtype; /*消息类型*/
char mtext[ ]; /*消息的文本*/
}
size指示由msgp指向的字符数组的长度;即消息的长度。这个数组的最大值由MSG-MAX( )系统可调用参数来确定。
flag 规定当核心用尽内部缓冲空间时应执行的动作: 进程是等待,还是立即返回。若在标志flag中未设置IPC_NOWAIT位,则当该消息队列中的字节数超过最大值时,或系统范围的消息数超过某一最大值时,调用msgsnd进程睡眠。若是设置IPC_NOWAIT,则在此情况下,msgsnd立即返回。
对于msgsnd( ),核心须完成以下工作:
(1)对消息队列的描述符和许可权及消息长度等进行检查。若合法才继续执行,否则返回;
(2)核心为消息分配消息数据区。将用户消息缓冲区中的消息正文,拷贝到消息数据区;
(3)分配消息首部,并将它链入消息队列的末尾。在消息首部中须填写消息类型、消息大小和指向消息数据区的指针等数据;
(4)修改消息队列头中的数据,如队列中的消息数、字节总数等。最后,唤醒等待消息的进程。
- msgrcv( )
接受一消息。从指定的消息队列中接收指定类型的消息。
系统调用格式:int msgrcv(msgqid,msgp,size,type,flag)
参数定义:
int msgqid,size,flag;
struct msgbuf *msgp;
long type;
其中,msgqid,msgp,size,flag与msgsnd中的对应参数相似,type是规定要读的消息类型,
flag规定倘若该队列无消息,核心应做的操作。如此时设置了IPC_NOWAIT标志,则立即返回,若在flag中设置了MS_NOERROR,且所接收的消息大于size,则核心截断所接收的消息。
对于msgrcv系统调用,核心须完成下述工作:
(1)对消息队列的描述符和许可权等进行检查。若合法,就往下执行;否则返回;
(2)根据type的不同分成三种情况处理:
type=0,接收该队列的第一个消息,并将它返回给调用者;
type为正整数,接收类型type的第一个消息;
type为负整数,接收小于等于type绝对值的最低类型的第一个消息。
(3)当所返回消息大小等于或小于用户的请求时,核心便将消息正文拷贝到用户区,并从消息队列中删除此消息,然后唤醒睡眠的发送进程。但如果消息长度比用户要求的大时,则做出错返回。
- msgctl( )
消息队列的操纵。读取消息队列的状态信息并进行修改,如查询消息队列描述符、修改它的许可权及删除该队列等。
系统调用格式: int msgctl(msgqid,cmd,buf);
参数定义:
int msgqid,cmd;
struct msgqid_ds *buf;
其中,函数调用成功时返回0,不成功则返回-1。buf是用户缓冲区地址,供用户存放控制参数和查询结果;cmd是规定的命令。命令可分三类:
(1)IPC_STAT。查询命令。如查询队列中的消息数目、队列中的最大字节数、最后一个发送消息的进程标识符、发送时间等;
(2)IPC_SET。按buf指向的结构中的值,设置和改变有关消息队列属性的命令。如改变消息队列的用户标识符、消息队列的许可权等;
(3)IPC_RMID。消除消息队列的标识符。
原文链接:https://blog.youkuaiyun.com/qq_43813697/article/details/105939109
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <wait.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#define MSGKEY 75 /*定义关键词MEGKEY*/
struct msgform /*消息结构*/
{
long mtype;
char mtexe[1030]; /*文本长度*/
}msg;
int msgqid,i;
void CLIENT()
{
int i;
msgqid=msgget(MSGKEY,0777); //创建消息队列
for(i=10;i>=1;i--)
{
msg.mtype=i;
printf("(client)sent\n");
msgsnd(msgqid,&msg,1024,0); //发送消息msg入msgid消息队列
}
exit(0);
}
void SERVER()
{
msgqid=msgget(MSGKEY,0777|IPC_CREAT); //创建一个所有用户都可以读、写、执行的队列,因为读操作的数字代号是4,写是2,执行是1,7=4+2+1,四位数字分别代表特殊权限位,拥有者位,同组用内户位,其余用户位
do
{
msgrcv(msgqid,&msg,1030,0,0); //从队列msgid接受消息msg
printf("(server)receive\n");
}while(msg.mtype!=1); //消息类型为1时,释放队列
msgctl(msgqid, IPC_RMID,0); //消除消息队列的标识符
exit(0);
}
int main()
{
if(fork()) //父进程
SERVER();
else //子进程
CLIENT();
wait(0);
wait(0);
}
<结果>
从理想的结果来说,应当是每当Client发送一个消息后,server接收该消息,Client再发送下一条。也就是说“(Client)sent”和“(server)received”的字样应该在屏幕上交替出现。实际的结果大多是,先由 Client 发送两条消息,然后Server接收一条消息。此后Client Server交替发送和接收消息.最后一次接收两条消息. Client 和Server 分别发送和接收了10条消息,与预期设想一致
<分析>
message的传送和控制并不保证完全同步,当一个程序不再激活状态的时候,它完全可能继续睡眠,造成上面现象,在多次send message 后才 receive message.这一点有助于理解消息转送的实现机理.
**
2.共享存储区的创建,附接和断接**
<任务>
使用系统调用shmget(),sgmat(),smgdt(),shmctl()编制一个与上述功能相同的程序.
<程序设计>
(1)为了便于操作 和观察结果,用一个 程序为“引子”,先后fork( )两个子进程,SERVER 和 CLIENT,进行通信。 (2)SERVER端建立一个KEY为75的共享区,并将第一个字节置为-1.作为数据空的标志.等待其他进程发来的消息.当该字节的值发生变化时,表示收到了该消息,进行处理.然后再次把它 的值设为-1.如果遇到的值为0,则视为结束信号,取消该队列,并退出SERVER.SERVER每接 收到一次数据后显示”(server)received”.
(3)CLIENT端建立一个为75的共享区,当共享取得第一个字节为-1时, Server端空闲,可发送 请求. CLIENT 随即填入9到0.期间等待Server端再次空闲.进行完这些操作后, CLIENT 退出. CLIENT每发送一次数据后显示”(client)sent”.
(4)父进程在SERVER和CLIENT均退出后结束.
#include<stdlib.h>
#include<unistd.h>
#include<stdio.h>
#include<wait.h>
#include<sys/types.h>
#include<sys/msg.h>
#include<sys/ipc.h>
#include <sys/shm.h>
#define SHMKEY 75 /*定义共享区关键词*/
int shmid,i;
int *addr;
void CLIENT()
{
int i;
shmid=shmget(SHMKEY,1024,0777); /*获取共享区,长度1024,关键词SHMKEY*/
addr=shmat(shmid,0,0); /*共享区起始地址为addr*/
for(i=9;i>=0;i--)
{
while(*addr!= -1);
printf("(client)sent\n"); /*打印(client)sent*/
*addr=i; /*把i赋给addr*/
}
exit(0);
}
void SERVER()
{
shmid=shmget(SHMKEY,1024,0777|IPC_CREAT); /*创建共享区*/
addr=shmat(shmid,0,0); /*共享区起始地址为addr*/
do
{
*addr=-1;
while(*addr==-1);
printf("(server)received\n"); /*服务进程使用共享区*/
}
while(*addr);
shmctl(shmid,IPC_RMID,0);
exit(0);
}
int main()
{
if(fork())
SERVER();
if(fork())
CLIENT();
wait(0);
wait(0);
}
运行的结果和预想的完全一样。但在运行的过程中,发现每当client发送一次数据后,server要等大约0.1秒才有响应。同样,之后client又需要等待大约0.1秒才发送下一个数据。出现上述的应答延迟的现象是程序设计的问题。当client端发送了数据后,并没有任何措施通知server端数据已经发出,需要由client的查询才能感知。此时,client端并没有放弃系统的控制权,仍然占用CPU的时间片。只有当系统进行调度时,切换到了server进程,再进行应答。这个问题,也同样存在于server端到client的应答过程之中。
3.进程的管道通信 〈任务〉 编制一段程序,实现进程的管道通信。使用系统调用pipe()建立一条管道线。两个子进程p1和p2分别向通道个写一句话: child1 process is sending message! child2 process is sending message! 而父进程则从管道中读出来自两个进程的信息,显示在屏幕上。
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <wait.h>
#include <stdlib.h>
int pid1, pid2;
int main()
{
int fd[2];
char outpipe[100], inpipe[100];
pipe(fd); //创建一个管道
while ((pid1 = fork()) == -1); //fork函数是在当前进程中新建立一个子进程,如果这个创建子进程失败,那么返回-1,这个实际是把创建进程的返回值和百-1比较看看是否创建失败。因为是写在while语句里,那么当创建失败之后,如果在while里面没有break或者跳出,当while执行体执行结束后又会执行(p1 = fork()) == -1,等于不断重复创建子进程一度直到创建成功为止。返回两次是父进程返回的是子进程的ID,子进程返回的是0
if (pid1 == 0)
{//进入子进程
lockf(fd[1], 1, 0); //锁定写入口往里写保证不被打断
sprintf(outpipe, "child 1 process is sending message!");
/*把串放入数组outpipe中*/
write(fd[1], outpipe, 50); /*向管道写长为50字节的串*/
sleep(5); /*自我阻塞5秒*/
lockf(fd[1], 0, 0); //释放写口
exit(0);
}
else
{//进入父进程
while ((pid2 = fork()) == -1); //创建进程2
if (pid2 == 0)
{//进入进程2的子进程
lockf(fd[1], 1, 0); //互斥,保证同一时刻只能往里写
sprintf(outpipe, "child 2 process is sending message!");
write(fd[1], outpipe, 50);
sleep(5);
lockf(fd[1], 0, 0);
exit(0);
}
else
{//进入进程2的父进程
wait(0); //同步,只有检测到子进程结束读入才能读出
read(fd[0], inpipe, 50); //从管道中读出长为50字节的串
printf("%s\n", inpipe);
wait(0);
read(fd[0], inpipe, 50);
printf("%s\n", inpipe);
exit(0); //程序并发执行,所以从管道读出哪一个顺序不确定
}
}
}
1、程序中的sleep(5)起什么作用?
如果运行结果没有发生变化,可在进程中使用sleep(5)来增加运
行时间来查看结果的变化。
2、子进程1和2为什么也能对管道进行操作?
实验中所用到的无名管道实际上是一个没有路径的临时文件,进程通过该文件的文件描述符来识别它,而子进程会继承父进程的环境和上下文中的大部分内容,包括文件描述符,从而子进程也能对父进程中创建的管道进行操作。