2021-9-24 Linux操作系统实验二:进程通信

Linux操作系统实验二:进程通信

题目:

【实验目的】

进一步提高 Linux 环境下 C 编程能力,了解和熟悉 Linux 支持的多种IPC 机制。Linux 作为一个多任务多进程的操作系统,各个进程间信息交互不可避免,进程间通信可分为本地进程间通信和远程进程间通信。本地进程间通信主要包括信号,管道,消息队列,信号量,共享内存等通信方式。

【实验预备内容】

(1)阅读 Linux 进程间通信章节的相关参考资料。
(2)重点学习信号量、共享内存机制。

【实验内容】

阅读参考资料,分析示例程序代码,用 C 语言编程实现以下要求。

  • 编写程序自选某种本地进程间通信机制实现客户端进程与服务器端进程之间信息的发送接收。

学号:021900208 姓名:高旭 专业:计算机科学与技术 班级:02

一、实验环境:

Oracle VM VirtualBox、Ubuntu(64-bit)

二、实验内容:

代码部分:

my.h

// 本次实验实现了3种进程通信,1.命名管道FIFO 2.消息队列 3.共享内存&信号量

// my.h 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <error.h>
#include <errno.h>
#include <unistd.h>
#include <linux/stat.h>

#define BUFES 80  

// 消息的组成
struct msgbuf  
{
    
    
	long int my_type;  // 消息的类型域
	char text[BUFES];  // 消息传递的数据域
};

union semun
{
    
    
    int val;
    struct semid_ds *buf;
    unsigned short *array;
    struct seminfo *buf_info;
    void *pad;
};

mode_t mode = 00666;  // IPC设置为默认模式
char pathname1[] = "/tmp/myfifo";  // FIFO路径
char pathname2[] = "/tmp/my_shm";  // 共享内存路径
char pathname3[] = "/tmp/my_sem";  // 信号量路径
char proj_id = 'a';  // 用于生成key

// p操作, 获取信号量
int sem_p(int semid, int index)
{
    
    
    struct sembuf sbuf = {
    
    0, -1, IPC_NOWAIT};  // 每个sembuf结构描述了一个对信号量的操作
    if(index < 0)
    {
    
    
        perror("index指定的信号量不存在!\n");
        return -1;
    }
    sbuf.sem_num = index;
    if(semop(semid, &sbuf, 1) == -1)
    {
    
    
        perror("在对信号量进行 p 操作时出现了一个错误!\n");
        return -1;
    }
    return 0;
}

// V操作, 释放信号量
int sem_v(int semid, int index)
{
    
    
    struct sembuf sbuf = {
    
    0, 1, IPC_NOWAIT};  // 每个sembuf结构描述了一个对信号量的操作
    if(index < 0)
    {
    
    
        perror("index指定的信号量不存在!\n");
        return -1;
    }
    sbuf.sem_num = index;
    if(semop(semid, &sbuf, 1) == -1)
    {
    
    
        perror("在对信号量进行 v 操作时出现了一个错误!\n");
        return -1;
    }
    return 0;
}

// 等待信号量
int wait_sem(int semid, int index)
{
    
    
    while(semctl(semid, index, GETVAL, 0) == 0) ;
    return 1;
}

客户端程序代码myclient.c:

// myclient.c

#include "my.h"

int running = 1;

void  stop()
{
    
    
	running = 0;
}

int main(int argc, char *argv[])
{
    
    
    FILE *fp;  //命名管道文件指针
    const int flag1 = IPC_CREAT | IPC_EXCL | mode;  // 创建对应IPC,若已存在则返回-1
    const int flag2 = IPC_CREAT | mode;  // 打开对应IPC,若不存在则创建
    struct msgbuf msg_sbuf;  // 消息队列所传递的消息
    struct msqid_ds msg_info;  // 用来设置或返回消息队列信息
    union semun arg;  // 函数semid的最后一个参数
    key_t semkey, shmkey;  // 信号量、共享内存键值
    int semid, shmid,msgid;  // 信号量id,共享内存id,消息队列的ID
    int sem_members=4;  // 信号量数量为4,0号信号为是否有人占用client共享内存,1号信号为是否有人占用server共享内存,2号信号为client共享内存是否可读,3号信号为server共享内存是否可读
    int reval;  // 接受消息队列操纵函数msgctl的返回值
    char writebuf[BUFES];  // 命名管道的写缓冲区  
    char write_str[BUFES];  // 共享内存的缓冲区
    char *shmaddr;  // 存放共享内存接口
    signal(SIGINT, stop);  // 注册SIGINT信号
    while (running)
    {
    
    
        if((fp=fopen(pathname1, "w")) == NULL)
	    {
    
    
		    printf("打开命名管道失败 \n");
		    exit(1);
	    }
        printf("请输入: ");
        fgets(writebuf, BUFES, stdin);
        if(strncmp(writebuf, "end", 3) == 0)
        {
    
    
            printf("接受到结束信号,命名管道通信结束 \n");
            running = 0;
        }
        if(fputs(writebuf, fp) == EOF)
		{
    
    
			printf("写入命名管道失败 \n");
			exit(1);
		}
        fclose(fp);
    }
    running = 1;
    while(running);
    running = 1;
    printf("接受到SIGINT信号,接下来开始进行消息队列通信 \n");   
    printf("-------------------------------------------------------------------------\n");
    if((msgid=msgget((key_t)1111, flag2)) == -1)  // 打开key值为1234的消息队列,如不存在则创建之
    {
    
    
        printf("创建/打开消息队列失败! \n");
        exit(1);    
    }
    else printf("消息队列创建成功 \n");
    while (running)
    {
    
    
		printf("请输入: ");
		fgets(msg_sbuf.text, BUFES, stdin);  // 读入键盘输入的消息
		msg_sbuf.my_type = 1;	
		if(msgsnd(msgid, (void *) &msg_sbuf, BUFES, 0) == -1)  // 发送消息
		{
    
    
			printf("发送信息失败!\n");
			exit(1);
		}
		if(strncmp(msg_sbuf.text, "end", 3) == 0)  // 输入end表示程序结束
		{
    
    
            printf("接受到结束信号,消息队列通信结束 \n");
            running = 0;
        }
    }
    if((reval=msgctl(msgid, IPC_STAT, &msg_info)) == -1)
    {
    
    
        printf("获取消息队列信息失败! \n");
        exit(1);
    }
    printf("消息队列信息: \n");
    printf("消息队列的容量: %ld \n", msg_info.msg_qbytes);
    printf("最近一个执行 msgsnd 函数的进程ID是:%d \n", msg_info.msg_lspid);
    printf("最近一个执行 msgrcv 函数的进程ID是:%d \n", msg_info.msg_lrpid);
	if(msgctl(msgid, IPC_RMID, 0)==-1)  //  删除消息队列
	{
    
    
		printf("删除消息队列失败! \n");
		exit(1);
	}
    else printf("删除消息队列成功\n");
    printf("接下来开始共享内存&信号量通信 \n"); 
    printf("-------------------------------------------------------------------------\n");
    if((shmkey = ftok(pathname2, proj_id)) == -1)
    {
    
    
        perror("返回共享内存键值失败!\n");
        exit(1);
    }
    else printf("获取共享内存键值成功!\n");
    if((shmid = shmget(shmkey, BUFES, flag2)) == -1){
    
    
        perror("创建/打开共享内存失败!\n");
        exit(1);
    }
    else printf("打开共享内存成功!\n");
    if((shmaddr=shmat(shmid, (char*)0, 0)) == (char *)-1)
    {
    
    
        perror("附加共享内存失败!\n");
        exit(1);
    }
    else printf("附加共享内存成功!\n");
    if((semkey = ftok(pathname3, proj_id)) == -1)
    {
    
    
        perror("返回信号量键值失败!\n");
        exit(1);
    }
    else printf("获取信号量键值成功!\n");
    if((semid = semget(semkey, sem_members, flag2)) == -1)  // 打开键值为semkey的信号量集,如不存在则创建之,返回信号量集标识符。members为信号量集中含信号量的数目。
    {
    
      
        perror("打开信号量失败!\n");
        exit(1);
    }
    else printf("打开信号量成功!\n");
    arg.val = 1;
    for(int index = 0; index < sem_members; index++)
    {
    
    
        semctl(semid, index, SETVAL, arg);
        if(index==0) arg.val = 0;
    }
    printf("初始化信号量成功!\n");
    while(1)
    {
    
    
        wait_sem(semid, 0);  // 等待client共享内存可写信号量可以被获取
        sem_p(semid, 0);  // 获取client可写信号量
        printf("发送:");
        fgets(write_str, BUFES, stdin);
        int len = strlen(write_str);
        write_str[len] = '\0';
        strcpy(shmaddr, write_str);
        sem_v(semid, 1);  // 向server传递共享内存可读信号量
        if(strncmp(write_str, "end", 3) == 0)  // 输入end表示程序结束
		{
    
    
            printf("接受到结束信号,共享内存&信号量通信结束 \n");
            break;
        }
        wait_sem(semid, 3);  // 等待client共享内存可读信号量可以被获取
        sem_p(semid, 3);  // 获取client共享内存可读信号量
        printf("接收:%s", shmaddr);
        sem_v(semid, 0);  // 向client传递共享内存可写信号量
        if(strncmp(shmaddr, "end", 3) == 0)  // 输入end表示程序结束
		{
    
    
            printf("接受到结束信号,共享内存&信号量通信结束 \n");
            break;
        }
    }
    sleep(3);
    if((semctl(semid, 0, IPC_RMID)) == 0)  printf("删除信号量成功 \n");
    else printf("删除信号量失败");
    if((shmctl(shmid, IPC_RMID, NULL)) == 0) printf("删除共享内存成功 \n");
    else printf("删除共享内存失败");
    return 0;
}

服务器程序代码myserver.c

// myserver.c 

#include "my.h"

int running = 1;

void  stop()
{
    
    
	running = 0;
}

int main(int argc, char *argv[])
{
    
           
    FILE *fp;  //命名管道文件指针
    const int flag1 = IPC_CREAT | IPC_EXCL | mode;  // 创建对应IPC,若已存在则返回-1
    const int flag2 = IPC_CREAT | mode;  // 打开对应IPC,若不存在则创建
    struct msgbuf msg_rbuf;  // 消息队列所传递的消息
    union semun arg;  // 函数semid的最后一个参数
    key_t semkey, shmkey;  // 信号量、共享内存键值
    long int msg_to_receive = 0;  // 接受消息队列的第一条消息
    int msgid,semid, shmid;  // 信号量id,共享内存id,消息队列的ID
    int sem_members = 4;  // 信号量数量为4,0号信号为是否有人占用client共享内存,1号信号为是否有人占用server共享内存,2号信号为client共享内存是否可读,3号信号为server共享内存是否可读  
	char readbuf[BUFES];  // 命名管道的读缓冲区 
    char write_str[BUFES];  // 共享内存的缓冲区
    char *shmaddr;  // 存放共享内存接口
    signal(SIGINT, stop);  // 注册SIGINT信号
    if((mkfifo(pathname1, mode)) < 0)
    {
    
    
        perror("创建命名管道失败!\n");
        exit(1);
    }
    else printf("你成功创建了一个命名管道 \n");
    while(running)
    {
    
    
        if((fp = fopen(pathname1, "r")) < 0)
        {
    
    
            printf("打开命名管道失败 \n");
		    exit(1);
        }
        if(fgets(readbuf, BUFES, fp) != NULL)
        {
    
    
			printf("读取字符串: %s", readbuf);
			fclose(fp);
        }
        else
		{
    
    
			if(ferror(fp))
			{
    
    
				printf("读取命名管道失败 \n");
				exit(1);
			}
		}
        if(strncmp(readbuf, "end", 3) == 0)
        {
    
    
            printf("接受到结束信号,命名管道通信结束 \n");
            running = 0;
        }
    }
    running = 1;
    while(running);
    running = 1;
    printf("接受到SIGINT信号,接下来开始进行消息队列通信 \n"); 
    printf("-------------------------------------------------------------------------\n");
    if((msgid=msgget((key_t) 1111, flag2)) == -1)  // 打开key值为1234的消息队列,如不存在则创建之
    {
    
    
        printf("创建/打开消息队列失败! \n");
        exit(1);    
    }
	while(running)
	{
    
    
		if(msgrcv(msgid, (void *) &msg_rbuf, BUFES, msg_to_receive, 0) == -1)  //接收消息,最后一个参数为设置默认模式
		{
    
    
			printf("获取消息失败!\n");
			exit(1);
		}
		printf("接受到消息 : %s", msg_rbuf.text);
		if(strncmp(msg_rbuf.text, "end", 3) == 0)  // 输入end表示程序结束
		{
    
    
            printf("接受到结束信号,消息队列通信结束 \n");
            running = 0;
        }
	}
    printf("接下来开始共享内存&信号量通信 \n"); 
    printf("-------------------------------------------------------------------------\n");
    if((shmkey = ftok(pathname2, proj_id)) == -1)
    {
    
    
        perror("返回共享内存键值失败!\n");
        exit(1);
    }
    else printf("获取共享内存键值成功!\n");
    if((shmid = shmget(shmkey, BUFES, flag2)) == -1){
    
    
        perror("创建/打开共享内存失败!\n");
        exit(1);
    }
    else printf("打开共享内存成功!\n");
    if((shmaddr=shmat(shmid, (char*)0, 0)) == (char *)-1)
    {
    
    
        perror("附加共享内存失败!\n");
        exit(1);
    }
    else printf("附加共享内存成功!\n");
    sleep(2);
    if((semkey = ftok(pathname3, proj_id)) == -1)
    {
    
    
        perror("返回信号量键值失败!\n");
        exit(1);
    }
    else printf("获取信号量键值成功!\n");
    if((semid = semget(semkey, sem_members, flag2)) == -1)  // 打开键值为semkey的信号量集,如不存在则创建之,返回信号量集标识符。members为信号量集中含信号量的数目。
    {
    
      
        perror("打开信号量失败!\n");
        exit(1);
    }
    else printf("打开信号量成功!\n");
    while(1)
    {
    
    
        wait_sem(semid, 1);  // 等待server共享内存可读信号量可以被获取
        sem_p(semid, 1);  // 获取server共享内存可读信号量
        printf("接收:%s", shmaddr);
        sem_v(semid, 2);  // 向server传递共享内存可写信号量
        if(strncmp(shmaddr, "end", 3) == 0)  // 输入end表示程序结束
		{
    
    
            printf("接受到结束信号,共享内存&信号量通信结束 \n");
            break;
        }
        wait_sem(semid, 2);  // 等待server共享内存可写信号量可以被获取
        sem_p(semid, 2);  // 获取server共享内存可写信号量
        printf("发送:");
        fgets(write_str, BUFES, stdin);
        int len = strlen(write_str);
        write_str[len] = '\0';
        strcpy(shmaddr, write_str);
        sem_v(semid, 3);  // 向client传递共享内存可读信号量
        if(strncmp(write_str, "end", 3) == 0)  // 输入end表示程序结束
		{
    
    
            printf("接受到结束信号,共享内存&信号量通信结束 \n");
            break;
        }
    }
    return 0;
}

实验结果截图:

在这里插入图片描述
在这里插入图片描述

三、实验总结:

1.所编写程序思想:

本次实验编写了:client客户端程序与server服务器程序
主要目的是实现:
①server调用mkfifo创建命名管道
②client通过命名管道单向向server发送信息
③在接收到SIGINT信号后两个进程关闭命名管道进入消息队列通信阶段
④client单向通过消息队列发送信息给server
⑤在结束消息队列通信前调用msgctl函数查看消息队列部分信息
⑥client与server进入共享内存&信号量通信阶段,打开共享内存与信号量,信号量数量为4由client完成初始化操作
⑦调用封装在my.h的函数,实现clinet发送->server接收->server发送->client接收的顺序执行
⑧删除共享内存与信号量,进程结束。

2.本次实验自学哪些知识:

①管道

②命名管道

③ftok函数

④open函数

⑤消息队列

⑥共享内存

⑦信号量

3.本次实验遇到的困难与解决:

①第二次使用mkfifo报错

mkfifo不能存在同名文件,所以在第二次执行时应该删去前一次执行生成的fifo文件。

②flag=IPC_CREAT | IPC_EXCL | mode不能在my.h里定义

解决:放弃在my.h里定义,而是在客户端与服务器程序中都定义。

③删除消息队列与共享内存以及信号量时,另一方没有退出循环

解决:使用了上一次实验的方法,调用sleep函数。

④共享内存与信号量的路径报错

解决:共享内存与信号量的shmget已经semget的参数pathname必须是现在存在的路径,可以创建对应文件名的文件夹。

⑤由于同时实现了3种机制导致本次实验代码行数过多

解决:暂无

4.附上本次实验程序执行流程图:

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/ShakingSH/article/details/120448243