[实验内容] 1 消息的创建,发送和接收
使用系统调用msgget( ), megsnd( ), msgrev( )及msgctl()编制一长度为1K的消息发送和接收的程序 。
(1) 为了便于操作和观察结果,用一个 程序为“引子”,先后fork( )两个子进程,SERVER和CLIENT,进行通信。
(2) SERVER端建立一个Key为75的消息队列,等待其他进程发来的消息。当遇到类型为1的消息,则作为结束信号,取消该队列,并退出SERVER 。SERVER每接收到一个消息后显示一句“(server)received”。
(3) CLIENT端使用Key为75的消息队列,先后发送类型从10到1的消息,然后退出。最后的一个消息,既是 SERVER端需要的结束信号。CLIENT每发送一条消息后显示一句“(client)sent”。
(4) 父进程在 SERVER和 CLIENT均退出后结束。
下面的函数使用的头文件:
#include
#include
#include
1. msgget( )
创建一个消息,获得一个消息的描述符。
系统调用格式:int msgqid=msgget(key,flag)
参数定义
key_t key;
int flag;
key是用户指定的消息队列的名字;
flag是用户设置的标志和访问方式。如
IPC_CREAT |0400 是否该队列已被创建。无则创建,是则打开;
IPC_EXCL |0400 是否该队列的创建应是互斥的。
msgqid 是该系统调用返回的描述符,失败则返回-1。
2. 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)修改消息队列头中的数据,如队列中的消息数、字节总数等。最后,唤醒等待消息的进程。
3. 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)当所返回消息大小等于或小于用户的请求时,核心便将消息正文拷贝到用户区,并从消息队列中删除此消息,然后唤醒睡眠的发送进程。但如果消息长度比用户要求的大时,则做出错返回。
4. 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.csdn.net/gnosed/article/details/80664052
#include
#include #include #include #include #include #include #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
#include #include #include #include #include #include #include #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
#include #include #include #include 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为什么也能对管道进行操作?
实验中所用到的无名管道实际上是一个没有路径的临时文件,进程通过该文件的文件描述符来识别它,而子进程会继承父进程的环境和上下文中的大部分内容,包括文件描述符,从而子进程也能对父进程中创建的管道进行操作。