- 一、实验目的
- 二、实验任务
- 三、实验要求
- 四、实验过程
- 五、思考
了解和熟悉linux支持的消息通信机制。
二、实验任务使用linux系统提供的系统调用msgget(),msgrev(),msgctl()编制一个长度为1K的消息发送和接受的程序。
三、实验要求- 用一个程序作为“引子”,先后fork()两个进程,SERVER和CLIENT,进行通信。
- 由SERVER端创建一个Key为75的消息队列,等待CLIENT端进程发来的消息。当遇到类型为1的消息,则作为结束信号,删除该队列,并退出SERVER。SERVER每接受到一个消息后显示一句“(Server)received from + CLIENT端pid+接受消息的类型”,然后发送一个返回消息给CLIENT端,显示一句“(Server)sent”。
- CLIENT端使用key为75的消息队列,先后发送类型从10到1的消息,然后退出。发出的最后一个消息是SERVER端需要的结束信号。CLIENT每发送一条消息后显示一句“(Client)sent+发送消息的类型”,然后等待接受SERVER端的返回消息后,显示一句“(Client) received from + SERVER端pid+接受消息的类型”,再在发送下一条消息。
- 父进程在SERVER和CLIENT都退出后结束。
-
用一个程序作为“引子”,先后fork()两个进程,SERVER和CLIENT,进行通信。
int main() { pid_t pid1, pid2; while ((pid1 = fork()) == -1); if (pid1 == 0) { // 子进程 1 SERVER(); } else { while ((pid2 = fork()) == -1); if (pid2 == 0) // 子进程 2 CLIENT(); else wait(0); } }
程序创建了两个子进程,一个是用 pid1 标识,另一个用 pid2 标识。根据程序代码可知,pid1 负责 SERVER() 函数,pid2 负责 CLIENT() 函数。最后父进程执行 wait(0) 函数,使得子进程存在时,父进程保持睡眠。
-
由SERVER端创建一个Key为75的消息队列,等待CLIENT端进程发来的消息。当遇到类型为1的消息,则作为结束信号,删除该队列,并退出SERVER。SERVER每接受到一个消息后显示一句“(Server)received from + CLIENT端pid+接受消息的类型”,然后发送一个返回消息给CLIENT端,显示一句“(Server)sent”。
#define MSGKEY 75 // 消息队列的 key 为 75 struct msgform { // 消息结构 long mtype; // 消息类型 char mtext[1024]; // 消息的文本长度为 1k } msg; int msgqid, i = 10; void CLIENT() { msgqid = msgget(MSGKEY, 0777); // 打开消息队列 while (i >= 1) { msg.mtype = i; // 消息的类型从 10 到 1 // 对消息内容进行赋值 sprintf(msg.mtext, "(Server) received from CLIENT %d, type %d\n", getpid(), msg.mtype); msgsnd(msgqid, &msg, 1024, 0); // 发送消息 msg 到 msgid 消息队列 printf("(client) sent %ld\n", msg.mtype); i = i - 1; sleep(1); // 暂停 1 s,等待 SERVER 接收 msgrcv(msgqid, &msg, 1024, 0, 0); // 接收消息 printf(msg.mtext); // 打印消息内容 } exit(0); // 退出 }
在程序之初,我们定义了消息结构 msgform,并且使用 msgqid 来标识消息队列。 在 CLIENT() 函数中,首先使用 msgget() 函数打开由 SERVER 创建的消息队列,然后使用 while 循环尝试 10 次消息发送。发送完成后使用 sleep() 函数进行 1s 的时间间隔,使得 SERVER 能够接收到并做出反馈。最后进行来自 SERVER 消息的接收。
-
CLIENT端使用key为75的消息队列,先后发送类型从10到1的消息,然后退出。发出的最后一个消息是SERVER端需要的结束信号。CLIENT每发送一条消息后显示一句“(Client)sent+发送消息的类型”,然后等待接受SERVER端的返回消息后,显示一句“(Client) received from + SERVER端pid+接受消息的类型”,再在发送下一条消息。
void SERVER() { msgqid = msgget(MSGKEY, 0777 | IPC_CREAT); // 创建一个所有用户都可以读、写、执行的队列 do { msgrcv(msgqid, &msg, 1024, 0, 0); // 从队列 msgid 接受消息 msg printf(msg.mtext); // 打印消息内容 // 对消息内容进行赋值 sprintf(msg.mtext, "(Client) received from SERVER %d, type %d\n", getpid(), msg.mtype); msgsnd(msgqid, &msg, 1024, 0); // 接收消息 printf("(Server) sent\n"); sleep(1); // 暂停 1 s,等待 CLIENT 接收 } while (msg.mtype != 1); // 消息类型为 1 时,释放队列 msgctl(msgqid, IPC_RMID, 0); // 消除消息队列的标识符 exit(0); // 退出 }
在 SERVER() 函数中,首先创建一个消息队列,名为 MSGKEY(75)。之后进入 do-while 循环,一直接收消息,直至消息类型为 1,退出循环并清空消息队列,函数结束。
-
父进程在SERVER和CLIENT都退出后结束。
源程序:
#include #include #include #include #include #include #include #define MSGKEY 75 // 消息队列的 key 为 75 struct msgform { // 消息结构 long mtype; // 消息类型 char mtext[1024]; // 消息的文本长度为 1k } msg; int msgqid, i = 10; void CLIENT() { msgqid = msgget(MSGKEY, 0777); // 打开消息队列 while (i >= 1) { msg.mtype = i; // 消息的类型从 10 到 1 // 对消息内容进行赋值 sprintf(msg.mtext, "(Server) received from CLIENT %d, type %d\n", getpid(), msg.mtype); msgsnd(msgqid, &msg, 1024, 0); // 发送消息 msg 到 msgid 消息队列 printf("(client) sent %ld\n", msg.mtype); i = i - 1; sleep(1); // 暂停 1 s,等待 SERVER 接收 msgrcv(msgqid, &msg, 1024, 0, 0); // 接收消息 printf(msg.mtext); // 打印消息内容 } exit(0); // 退出 } void SERVER() { msgqid = msgget(MSGKEY, 0777 | IPC_CREAT); // 创建一个所有用户都可以读、写、执行的队列 do { msgrcv(msgqid, &msg, 1024, 0, 0); // 从队列 msgid 接受消息 msg printf(msg.mtext); // 打印消息内容 // 对消息内容进行赋值 sprintf(msg.mtext, "(Client) received from SERVER %d, type %d\n", getpid(), msg.mtype); msgsnd(msgqid, &msg, 1024, 0); // 接收消息 printf("(Server) sent\n"); sleep(1); // 暂停 1 s,等待 CLIENT 接收 } while (msg.mtype != 1); // 消息类型为 1 时,释放队列 msgctl(msgqid, IPC_RMID, 0); // 消除消息队列的标识符 exit(0); // 退出 } int main() { pid_t pid1, pid2; while ((pid1 = fork()) == -1); if (pid1 == 0) { // 子进程 1 SERVER(); } else { while ((pid2 = fork()) == -1); if (pid2 == 0) // 子进程 2 CLIENT(); else wait(0); } }
实验截图:
可以看到,在由 Client 发送消息后,Server 接收到消息并打印出“(Server)received from + CLIENT端pid+接受消息的类型”,然后发送一个返回消息“(Server) sent”给 Client,由 Client 接收并显示“(Client) received from + SERVER端pid+接受消息的类型”。由此循环10次,程序运行结束。
(1)最初在实现 CLIENT 与 SERVER 的消息通信时,采用简单的“发送-接收”的方式,会发现两个进程发生异步现象:
void CLIENT() {
msgqid = msgget(MSGKEY, 0777); // 打开消息队列
while (i >= 1) {
msg.mtype = i; // 消息的类型从 10 到 1
// 对消息内容进行赋值
sprintf(msg.mtext, "(Server) received from CLIENT %d, type %d\n", getpid(), msg.mtype);
msgsnd(msgqid, &msg, 1024, 0); // 发送消息 msg 到 msgid 消息队列
printf("(client) sent %ld\n", msg.mtype);
i = i - 1;
}
exit(0); // 退出
}
void SERVER() {
msgqid = msgget(MSGKEY, 0777 |
IPC_CREAT); // 创建一个所有用户都可以读、写、执行的队列
do {
msgrcv(msgqid, &msg, 1024, 0, 0); // 从队列 msgid 接受消息 msg
printf(msg.mtext); // 打印消息内容
} while (msg.mtype != 1); // 消息类型为 1 时,释放队列
msgctl(msgqid, IPC_RMID, 0); // 消除消息队列的标识符
exit(0); // 退出
}
因为没有限制 CLIENT 和 SERVER 打印消息的顺序。
(2)后来尝试使用 P/V 操作实现前趋关系,为了操作方便,采用整型信号量:
typedef int semaphore;
semaphore S = 0;
void P(semaphore *S) {
while (*S = 1) {
msg.mtype = i; // 消息的类型从 10 到 1
// 对消息内容进行赋值
sprintf(msg.mtext, "(Server) received from CLIENT %d, type %d\n", getpid(), msg.mtype);
msgsnd(msgqid, &msg, 1024, 0); // 发送消息 msg 到 msgid 消息队列
printf("(client) sent %ld\n", msg.mtype);
i = i - 1;
V(&S);
}
exit(0); // 退出
}
void SERVER() {
msgqid = msgget(MSGKEY, 0777 |
IPC_CREAT); // 创建一个所有用户都可以读、写、执行的队列
do {
P(&S);
msgrcv(msgqid, &msg, 1024, 0, 0); // 从队列 msgid 接受消息 msg
printf(msg.mtext); // 打印消息内容
} while (msg.mtype != 1); // 消息类型为 1 时,释放队列
msgctl(msgqid, IPC_RMID, 0); // 消除消息队列的标识符
exit(0); // 退出
}

发现仅输出了 CLIENT 的打印消息,推测原因可能是 P/V 操作放在循环里,CLIENT 总是先到达而一直运行,使得 SERVER 没有机会执行,父进程便结束了。 PS:在第四次实验中,采用了信号量集实验能够获得成功。仔细想想,应该还是该程序的信号量并没有进行共享,导致失败。
(3)最后多次尝试,发现使用 sleep() 函数能够达到较好的效果。整个过程中,思维得到了训练和提升。