HDU_實驗三(3):利用Linux的消息隊列通信機制實現兩個線程間的通信

#實驗要求:

編寫程序創建三個線程: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

參考於:
https://lsvm.xyz/2019/04/os-lab-3-3/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章