#实验要求:
编写程序创建三个线程:sender1线程、sender2线程和receive线程,三个线程的功能描述如下:
①sender1线程:运行函数sender1(),它创建一个消息队列,然后等待用户通过终端输入一串字符,并将这串字符通过消息队列发送给receiver线程;可循环发送多个消息,直到用户输入“exit”为止,表示它不再发送消息,最后向receiver线程发送消息“end1”,并且等待receiver的应答,等到应答消息后,将接收到的应答信息显示在终端屏幕上,结束线程的运行。
②sender2线程:运行函数sender2(),共享sender1创建的消息队列,等待用户通过终端输入一串字符,并将这串字符通过消息队列发送给receiver线程;可循环发送多个消息,直到用户输入“exit”为止,表示它不再发送消息,最后向receiver线程发送消息“end2”,并且等待receiver的应答,等到应答消息后,将接收到的应答信息显示在终端屏幕上,结束线程的运行。
③Receiver线程:运行函数receive(),它通过消息队列接收来自sender1和sender2两个线程的消息,将消息显示在终端屏幕上,当收到内容为“end1”的消息时,就向sender1发送一个应答消息“over1”;当收到内容为“end2”的消息时,就向sender2发送一个应答消息“over2”;消息接收完成后删除消息队列,结束线程的运行。选择合适的信号量机制实现三个线程之间的同步与互斥。
#代码(注释很详细):
名字随便.c
#include "common.h"
/**
* @brief Create mq and send message to receiver.
* @return
*/
void *sender1() {
int mq;
struct msg_st buf;
ssize_t bytes_read;
/* open the mail queue */
/*
Linux的消息队列(queue)实质上是一个链表, 它有消息队列标识符(queue ID).
msgget创建一个新队列或打开一个存在的队列; msgsnd向队列末端添加一条新消息;
msgrcv从队列中取消息, 取消息是不一定遵循先进先出的, 也可以按消息的类型字段取消息.
int msgget(key_t key, int msgflg); 获得消息队列的特征标识符。
函数需要两个参数,key 和 msgflg。
key 是该消息队列的全局唯一标识符,通过该标识符可以定位消息队列,对其进行相关操作。
msgflg 为权限控制,决定对消息队列进行的操作,
一般使用 0666 | IPC_CREAT,熟悉文件系统的可以知道,0666 表示文件所有者、同组用户和其他用户对于该消息队列的权限均为可读可写,
后面对常量 IPC_CREAT 进行的位运算作用是 “若该队列未被创建则创建它”。
(对于该函数可以简单理解为创建消息队列)
*/
mq = msgget((key_t) QUEUE_ID, 0666 | IPC_CREAT);
CHECK((key_t) -1 != mq);
do {
P(w_mutex);
P(snd_dp);
printf("sender1> ");
V(rcv_dp);
/*
头文件:#include<stdio.h>
定义函数:int fflush(FILE * stream);
函数说明:fflush()会强迫将缓冲区内的数据写回参数stream指定的文件中,如果参数stream为NULL,fflush()会将所有打开的文件数据更新。
返回值:成功返回0,失败返回EOF,错误代码存于errno中。
fflush()也可用于标准输入(stdin)和标准输出(stdout),用来清空标准输入输出缓冲区。
stdin是standard input的缩写,即标准输入,一般是指键盘;标准输入缓冲区即是用来暂存从键盘输入的内容的缓冲区。
stdout是standard output 的缩写,即标准输出,一般是指显示器;标准输出缓冲区即是用来暂存将要显示的内容的缓冲区。
清空标准输出缓冲区,
刷新输出缓冲区,即将缓冲区的东西输出到屏幕上
如果圆括号里是已写打开的文件的指针,则将输出缓冲区的内容写入该指针指向的文件,否则清除输出缓冲区。
这里的stdout是系统定义的标准输出文件指针,默认情况下指屏幕,那就是把缓冲区的内容写到屏幕上。
可是从代码中看不出缓冲区会有什么内容,所以它实际上没有起什么作用
*/
fflush(stdout);
// 接收终端输入,置入buf.buffer
fgets(buf.buffer, BUFSIZ, stdin);
// 设置消息类别,接收线程据此判断消息来源
buf.message_type = snd_to_rcv1;
/* send the message */
P(empty);
/*
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); 消息队列的发送操作。success return 0;
函数需要四个参数,msqid,msgp,msgsz 和 msgflg。
msqid 是函数 msgget 的返回值,用于表示对哪一个消息队列进行操作。
msgp 是接收消息的指针,指向消息结构体 msg_st。
msgsz 是接收消息的大小,这里可以看作结构体 msg_st 中数据段的大小。
msgflg 同函数 msgget 中的 msgflg,这里可以直接使用 0。
*/
CHECK(0 <= msgsnd(mq, (void*)&buf, MAX_SIZE, 0));
V(full);
V(w_mutex);
printf("\n");
/*
strncmp函数为字符串比较函数,字符串大小的比较是以ASCII 码表上的顺序来决定,此顺序亦为字符的值。
其函数声明为int strncmp ( const char * str1, const char * str2, size_t n );
功能是把 str1 和 str2 进行比较,最多比较前 n 个字节,若str1与str2的前n个字符相同,则返回0;若s1大于s2,则返回大于0的值;
若s1 小于s2,则返回小于0的值。 [
*/
} while (strncmp(buf.buffer, MSG_STOP, strlen(MSG_STOP)));
/* wait for response */
P(over);
/*
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg); 消息队列的接收操作。
函数需要五个参数,msqid,msgp,msgsz,msgtyp 和 msgflg。
msqid 是函数 msgget 的返回值,用于表示对哪一个消息队列进行操作。
msgp 是接收消息的指针,指向消息结构体 msg_st。
msgsz 是接收消息的大小,这里可以看作结构体 msg_st 中数据段的大小。
msgtyp 是接收消息的类别,函数可以接收指定类别的消息,默认为 0,忽视类别,接收队首消息,正值和负值有不同含义,详情查看附录。
msgflg 同函数 msgget 中的 msgflg,这里可以直接使用 0。
函数的返回值为实际接收到的消息字节数。
*/
bytes_read = msgrcv(mq, (void *) &buf, MAX_SIZE, rcv_to_snd1, 0);
CHECK(bytes_read >= 0);
printf("%s", buf.buffer);
printf("--------------------------------------------\n");
V(snd_dp);
pthread_exit(NULL);
}
/**
* @brief Send message to receiver.
* @return
*/
void *sender2() {
int mq;
struct msg_st buf;
ssize_t bytes_read;
/* open the mail queue */
mq = msgget((key_t) QUEUE_ID, 0666 | IPC_CREAT);
CHECK((key_t) -1 != mq);
do {
P(w_mutex);
P(snd_dp);
printf("sender2> ");
V(rcv_dp);
fflush(stdout);
fgets(buf.buffer, BUFSIZ, stdin);
buf.message_type = snd_to_rcv2;
/* send the message */
P(empty);
CHECK(0 <= msgsnd(mq, (void *) &buf, MAX_SIZE, 0));
V(full);
V(w_mutex);
printf("\n");
} while (strncmp(buf.buffer, MSG_STOP, strlen(MSG_STOP)));
/* wait for response */
//由receiver接收到send1、2两个进程的exit后会分别对over--,即v(over)。让send1、2一直在 wait for response
P(over);
bytes_read = msgrcv(mq, (void *) &buf, MAX_SIZE, rcv_to_snd2, 0);
CHECK(bytes_read >= 0);
printf("%s", buf.buffer);
printf("--------------------------------------------\n");
V(snd_dp);
pthread_exit(NULL);
}
/**
* @brief Receive message from sender, and response when sender exit.
* @return
*/
void *receiver() {
struct msg_st buf, over1, over2;
int mq, must_stop = 2;
//消息队列状态:struct msqid_ds
struct msqid_ds t;
//定义两个结束信号 over1,over2
over1.message_type = 3;
/*
strcpy,即string copy(字符串复制)的缩写。
strcpy是一种C语言的标准库函数,strcpy把含有'\0'结束符的字符串复制到另一个地址空间,返回值的类型为char*。
*/
strcpy(over1.buffer, "over1\n");
over2.message_type = 4;
strcpy(over2.buffer, "over2\n");
/* open the mail queue */
mq = msgget((key_t) QUEUE_ID, 0666 | IPC_CREAT);
CHECK((key_t) -1 != mq);
do {
/*
size_t是无符号整型,至于是long型,还是int型,可能不同的编译器有不同的定义,我这里没有64位的机器,无法验证。这也验证了我们上面所说的ssize_t其实就是一个long*/
ssize_t bytes_read, bytes_write;
/* receive the message */
P(full);
//接收send进程发来的信息
bytes_read = msgrcv(mq, (void *) &buf, MAX_SIZE, 0, 0);
V(empty);
CHECK(bytes_read >= 0);
//接收MSG_STOP:exit 通过type找到send,over--
if (!strncmp(buf.buffer, MSG_STOP, strlen(MSG_STOP))) {
if (buf.message_type == 1) {//type=1,指向send1
bytes_write = msgsnd(mq, (void *) &over1, MAX_SIZE, 0);
CHECK(bytes_write >= 0);
V(over);
must_stop--;
} else if (buf.message_type == 2) {//type=2,指向send2
bytes_write = msgsnd(mq, (void *) &over2, MAX_SIZE, 0);
CHECK(bytes_write >= 0);
V(over);
must_stop--;
}
} else {
P(rcv_dp);
printf("Received%d: %s", buf.message_type, buf.buffer);
printf("--------------------------------------------\n");
V(snd_dp);
}
} while (must_stop);//将must_stop设为二,
/* cleanup */
P(snd_dp);
CHECK(!msgctl(mq, IPC_RMID, &t));
pthread_exit(NULL);
}
/**
* Create three thread to test functions
* @param argc Argument count
* @param argv Argument vector
* @return Always 0
*/
int main(int argc, char **argv) {
pthread_t t1, t2, t3;
int state;
//sem_init函数是Posix信号量操作中的函数。sem_init() 初始化一个定位在 sem 的匿名信号量。value 参数指定信号量的初始值。
sem_init(&snd_dp, 1, 1);
sem_init(&rcv_dp, 1, 0);
sem_init(&empty, 1, 10);
sem_init(&full, 1, 0);
sem_init(&w_mutex, 1, 1);
sem_init(&over, 1, 0);
state = pthread_create(&t1, NULL, receiver, NULL);
CHECK(state == 0);
state = pthread_create(&t3, NULL, sender1, NULL);
CHECK(state == 0);
state = pthread_create(&t2, NULL, sender2, NULL);
CHECK(state == 0);
/*
pthread_join()函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。
如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。
参数 :thread: 线程标识符,即线程ID,标识唯一线程。retval: 用户定义的指针,用来存储被等待线程的返回值。
返回值 : 0代表成功。 。
*/
pthread_join(t3, NULL);
pthread_join(t2, NULL);
pthread_join(t1, NULL);
return 0;
}
common.h文件:
#ifndef EXP3_3_COMMON_H
#define EXP3_3_COMMON_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <sys/msg.h>
#include <pthread.h>
#include <semaphore.h>
#define QUEUE_ID 10086
#define MAX_SIZE 1024
#define MSG_STOP "exit"
#define snd_to_rcv1 1
#define snd_to_rcv2 2
#define rcv_to_snd1 3
#define rcv_to_snd2 4
#define CHECK(x) \
do { \
if (!(x)) { \
fprintf(stderr, "%s:%d: ", __func__, __LINE__); \
perror(#x); \
exit(-1); \
} \
} while (0) \
#define P(x) sem_wait(&x)
#define V(x) sem_post(&x)
//消息队列需要自定义一个消息缓冲区,这里设计一个只包含两个成员变量的结构体作为消息缓冲区:
//其中 message_type 为消息种类,buffer 用来储存消息的数据段,最大可存储 MAX_SIZE 大小,+1 操作为了给结尾留出 \0。
struct msg_st {
long int message_type;
char buffer[MAX_SIZE + 1];
};
/* function */
void *sender1();
void *sender2();
void *receiver();
/* global variable */
/*
sender 和 receiver 之间的进程同步比较简单,临界资源为消息队列。是有:
receiver 接收消息,sender 发送消息,receiver 和 sender 存在同步关系,使用 full=0 和 empty=1 进行约束;因为full和empty设为一,send进程发一个receiver就接受一个
sender 之间存在互斥关系,两个发送线程不能同时工作 ,使用 w_mutex=1 进行约束;
receiver 等待发送进程结束后,返回应答,sender 收到应答后进行输出,receiver 和 sender 存在同步关系,使用 over=0 进行约束;
这里对于终端输出也进行了约束,使用 rcv_dp 和 snd_dp 进行约束,可以忽视这部分的处理,这一部分只是为了美观。
*/
sem_t w_mutex, empty, full, over, rcv_dp, snd_dp;
#endif //EXP3_3_COMMON_H