Linux的进程线程间通信

线程的同步互斥机制

同步互斥基本概念

  • 临界资源(共享资源)
    • 在多任务并发执行时,访问的同一个资源
  • 临界区
    • 访问临界资源的代码
  • 竞态
    • 多个任务共同抢占临界资源的现象叫做竞态
  • 互斥
    • 指某一个临界资源,同一时刻只允许一个访问者,具有唯一性和排他性
  • 同步
    • 使用相关手段,将任务有先后顺序的进行

解决竞态的方法

  • 互斥锁
  • 无名信号量
  • 条件变量

互斥锁

  • 互斥锁的使用逻辑
    1. 给临界区上锁,获取到锁的线程可以使用临界资源
    2. 释放锁,当一个线程释放锁资源后,其他线程对该锁可以继续抢占
    3. 互斥锁被一个任务抢占后,其他任务就不能获取到该锁资源了

互斥锁的API

#include <pthread.h>
1、初始化一个互斥锁

//静态初始化
pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;

pthread_mutex_t recmutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
 
pthread_mutex_t errchkmutex = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;

/*
2、初始化一个互斥锁
函数功能:初始化一个互斥锁
函数参数:互斥锁变量地址(是一个指针),互斥锁状态(默认为NULL)
返回值:永远返回0
*/
int pthread_mutex_init(pthread_mutex_t *mutex,  const  pthread_mutexattr_t *mutexattr);

/*
3、上锁
函数功能:给临界区上锁,获取锁资源
函数参数:互斥锁地址
返回值:成功:返回0		失败:返回非0错误码
*/
int pthread_mutex_lock(pthread_mutex_t *mutex);
 
int pthread_mutex_trylock(pthread_mutex_t *mutex);

/*
4、解锁
函数功能:释放锁资源
函数参数:互斥锁地址
返回值:成功:返回0		失败:返回非0错误码
*/
int pthread_mutex_unlock(pthread_mutex_t *mutex);

/*
5、销毁锁
函数功能:销毁互斥锁
函数参数:互斥锁地址
返回值:成功:返回0		失败:返回非0错误码
*/
int pthread_mutex_destroy(pthread_mutex_t *mutex);
 

线程同步之无名信号量

  • 线程同步
    • 在已经知道线程的执行顺序,并且线程是按顺序进行执行,这种操作成为线程同步
  • 线程同步适用的模型
    • 生产者消费者模型,即生产者先生产商品,然而消费者再对商品进行消费

无名信号量的API

1、要定义一个无名信号量
sem_t sem;		//定义一个信号量


/*
2、初始化无名信号量
函数功能:初始化一个无名信号量
函数参数:无名信号量地址,
		进程或线程同步标识(0表示多线程,1表示多进程(亲缘间)),
		无名信号量维护的value值,每进行一次wait,该值就会减1,每进行一次post,该值就		 会加1,当该值为0时,wait会阻塞
返回值:成功:返回0		失败:返回-1置位错误码
*/
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);


/*
3、资源申请
函数功能:申请当前无名信号量的资源,是的value值减1,当资源为0时,该函数会被阻塞,知道value值大于0
函数参数:无名信号量地址
返回值:成功:返回0		失败:返回-1置位错误码,value不变
*/
#include <semaphore.h>
int sem_wait(sem_t *sem);

/*
4、释放资源
函数功能:释放资源(V操作,是的value值增加)
函数参数:无名信号量地址
返回值:成功:返回0		失败:返回-1置位错误码,value不变
*/
#include <semaphore.h>
int sem_post(sem_t *sem);

/*
5、销毁无名信号量
函数功能:销毁无名信号量
函数参数:无名信号量地址
返回值:成功:返回0		失败:返回-1置位错误码
*/
#include <semaphore.h>
int sem_destroy(sem_t *sem);

线程同步之条件变量

  • 无名信号量维护了一个value值,而条件变量维护了一个队列
  • 条件变量对应的模型
    • 生产消费者模型(一个生产者对应多个消费者)

条件变量的API

#include <pthread.h>
1、定义一个条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;		//静态初始化

/*
2、初始化条件变量
函数功能:动态初始化一个条件变量
函数参数:条件变量地址,条件变量属性(默认NULL)
返回值:成功:返回0		失败:返回非0错误码
*/
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);

/*
3、给休眠的线程发送唤醒信号
函数功能:唤醒休眠队列中的队头线程
函数参数:条件变量地址
返回值:成功:返回0		失败:返回非0错误码
*/
int pthread_cond_signal(pthread_cond_t *cond);

/*
函数功能:唤醒休眠队列中的所有线程
函数参数:条件变量地址
返回值:成功:返回0		失败:返回非0错误码
*/
int pthread_cond_broadcast(pthread_cond_t *cond);

/*
4、阻塞等待条件变量
函数功能:等待条件变量,并让该线程进入休眠队列
函数参数:条件变量地址,互斥锁(由于进入休眠队列时可能会产生竞态,所以需要互斥锁)
返回值:成功:返回0		失败:返回非0错误码
*/
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

/*
5、销毁条件变量
函数功能:销毁条件变量
函数参数:条件变量地址
返回值:成功:返回0		失败:返回非0错误码
*/
int pthread_cond_destroy(pthread_cond_t *cond);

进程间通信(IPC)

相关概念

  • 线程间通信,可以使用全局变量来完成,原因是多个线程共享进程的资源
  • 由于进程间的通信时,每个进程的用户空间是独立的,所以不能使用全局变量来完成进程间的通信
  • 可以使用文件来完成两个进程间通信,一个进程向文件中写数据,另一个进程从文件中读数据,但是,又因为进程的调度是时间片轮询,上下文切换,所以,不确定先执行哪一个进程,文件操作有些行不通,但是不完全行不通
  • 由于多个进程用户空间是独立的,但是共享内核空间

进程间通信的方式

  • 内核提供的传统通信方式
    1. 无名管道
    2. 有名管道
    3. 信号
  • system V提供的三种
    4. 消息队列
    5. 共享内存
    6. 有名信号量(信号灯集)
  • 用于跨主机传输的通信机制
    7. socket套接字通信

管道通信

  • 原理
    • 在3-4G内核空间中,创建一个特殊的文件(管道文件),管道文件直接保存在内存中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3xWeAY8U-1692791464650)(C:\Users\无实的谎言花\Desktop\上海\Linux系统编程\图片\管道文件.png)]

管道的特性

  1. 管道可以看成特殊的文件,一般文件存储在磁盘文件中,而管道文件可以存在内存中
  2. 管道文件遵循的规则是先进先出,被读取的文件不会在管道文件中存留
  3. 对管道的操作是一次性的
  4. 管道通信的方式是一种半双工的通信方式
  5. 在对管道操作时,只能使用文件IO进行操作,因为打开管道时会提供读端和写端的两个文件描述符
  6. 当管道文件所有的读端和写端都被关闭后,管道文件会自动关闭
  7. 管道文件的大小为64K = 65536Byte
  8. 管道文件可以自己与自己进行通信
  9. 读写管道文件时,不能使用lseek移动光标
  10. 读写的特点
    • 如果读端存在,写管道,有多少写多少,直到写满64K,就进入阻塞
    • 如果读端不存在,写管道破裂,会发射一个SIGPIPE的信号
    • 如果写端存在,读管道,有多少读多少,管道中没有数据,会在read处阻塞
    • 如果写端不存在,读管道有多少读多少,如果管道中没有数据,也不会阻塞

无名管道

  • 无名管道,顾名思义就是没有名字的管道文件,由于该文件没有名字,会导致其他进程不能打开该文件,所以就不能完成非亲缘间的进程的通信
  • 无名管道,只适用于亲缘进程间通信
无名管道API
  • 功能
    • 创建一个管道用于进程间通信,并通过参数返回该管道的两端,pipefd[0]是管道文件的读端,pipefd[1]是管道文件的写端
  • 原型
#include <unistd.h>
int pipe(int pipefd[2]);
  • 参数
    • int pipefd[2]:整型数组,存放管道文件的文件描述符
  • 返回值
    • 成功:返回0
    • 失败:返回-1,置位错误码
  • 应用实例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/wait.h>
#include <time.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>

int main(int argc, char *argv[])
{

	//定义一个数组存放管道文件的两端
	
	int pipefd[2];

	//定义进程号
	pid_t pid;

	//打开管道文件
	if(pipe(pipefd) == -1)
	{
		perror("pipe error");
		return -1;
	}

	//创建子进程
	pid = fork();
	if(pid < 0)
	{
		perror("fork error");
		return -1;
	}
	else if(pid == 0)
	{
		//子进程,像管道中写入数据
		//关闭子进程的读端
		close(pipefd[0]);

		while(1)
		{
			char buf[128] = "";
			fgets(buf, sizeof(buf), stdin);
			//将回车变成\0
			buf[strlen(buf) - 1] = '\0';

			//将该字符串写入到管道文件中
			write(pipefd[1], buf, strlen(buf));

			//判断字符串
			if(strcmp(buf, "quit") == 0)
			{
				break;
			}

		}

		//关闭写端
		close(pipefd[1]);
		//退出子进程
		exit(EXIT_SUCCESS);
	}
	else
	{
		//父进程
		//关闭写端
		close(pipefd[1]);

		while(1)
		{
			char buf[128] = "";

			//从管道文件中读取数据
			read(pipefd[0], buf, sizeof(buf));
			//对读取的数据判断
			if(strcmp(buf, "quit") == 0)
			{
				break;
			}

			//将数据输出
			printf("父进程读取了数据:%s\n", buf);

		}

		//关闭读端
		close(pipefd[0]);
		
		//回收子进程
		wait(NULL);
	}

    return 0;
}

有名管道

  • 有名管道顾名思义就是有名字的管道,在创建管道文件时,会给出管道的名称
  • 有名管道适用于亲缘或非亲缘进程间的通信
有名管道的API
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_ mode);

信号实现进程间通信

信号的使用机制

  • 在Linux应用层的信号,其实是模拟底层中的中断,当一个进程正在运行时,如果有信号产生,则会执行该信号对应的操作
  • 信号的处理方式
    1. 捕获:当捕获一个信号后,可以进行处理信号处理函数
    2. 忽略:当信号发射后,不做任何处理
    3. 默认:一般都是杀死进程

信号的种类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l2GqpmaM-1692791464651)(C:\Users\无实的谎言花\Desktop\上海\Linux系统编程\图片\62个信号表.png)]

常用的信号

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yTNvGdZ9-1692791464652)(C:\Users\无实的谎言花\Desktop\上海\Linux系统编程\图片\常用信号1.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GmYAKpIU-1692791464652)(C:\Users\无实的谎言花\Desktop\上海\Linux系统编程\图片\c常用信号2.png)]

  • 注意:SIGKILL和SIGSTOP信号既不能被捕获,也不能被忽略,只能默认处理
  • SIGUSR1和SIGUSR2这两个信号,没有具体的使用场景,是内核提供给用户自己使用
  • SIGCHLD信号很重要,当子进程死后,会向父进程发射该信号

signal()

  • 功能

    • 将指定的信号与信号处理函数进行绑定,当信号发射时,信号处理函数会被执行
  • 原型

    •   #include <signal.h>
        typedef void (*sighandler_t)(int);
        sighandler_t signal(int signum, sighandler_t handler);
      
  • 参数

    • int signum:要绑定的信号
    • sighandler_t handler:信号处理函数
      • SIG_IGN:表示忽略该信号
      • SIG_DFL:表示默认处理该信号
  • 返回值

    • 成功:返回信号处理函数地址
    • 失败:返回SIG_ERR,并置位错误码

发射信号函数(kill / raise)

  • kill()功能

    • 给指定的进程或进程组发射信号
  • 原型

    •   #include <signal.h>
        int kill(pid_t pid, int sig);
      
  • 参数

    • pid_t pid:进程号
      • 大于0:表示想指定的进程发送信号
      • 等于0:表示向当前进程所在的进程组每个进程发送该信号
      • 等于-1:表示向所有有收发信号权限的进程发送该信号
      • 小于-1:表示向绝对值为pid的进程组中的所有进程发送该信号
    • int sig:要发生的信号号
  • 返回值

    • 成功:返回0
    • 失败:返回-1,并置位错误码
  • raise()功能

    • 给自己发送一个信号
  • 原型

    •   #include <signal.h>
        int raise(int sig);
      
  • 参数

    • int sig:信号号
  • 返回值

    • 成功:返回0
    • 失败:返回非0

SIGALRM信号的使用

  • 当调用alarm()函数时,定时器时间到位后,回向该进程发射一个SIGALRM信号

  • alarm()功能

    • 创建一个定时器
  • 原型

    •   #include <unistd.h>
        unsigned int alarm(unsigned int seconds);
      
  • 参数

    • unsigned int seconds:时长,以秒为单位
  • 返回值

    • 如果之前没有定时器,则返回0
    • 如果之前启动过定时器,则返回上一个定时器所剩余的秒数

消息队列

  • system V提供的三种
    1. 消息队列
    2. 共享内存
    3. 有名信号量(信号灯集)

相关的指令

  • ipcs:可以查看所有进程间通信的信息(包括消息队列、共享内存、信号灯)
  • ipcs -q:可以单独查看消息队列的信息
  • ipcs -m:可以单独查看共享内存的信息
  • ipcs -s:可以单独查看信号灯的信息
  • ipcrm -q\-m\-s ID:删除为ID的进程间通信对象

消息队列的API

ftok()

  • 功能

    • 创建一个key值
  • 原型

    •   #include <sys/types.h>
        #include <sys/ipc.h>
        key_t ftok(const char *pathname, int proj_id);
      
  • 参数

    • const char *pathname:需要一个已存在的路径
    • int proj_id:一个项目ID,就是任意一个数
  • 注意

    • key值的组成:proj_id(高8位) + st_dev(8位) + inode(剩余16位)
  • 返回值

    • 成功:返回创建的key值
    • 失败:返回-1,置位错误码

msgget()

  • 功能

    • 通过给定的key值,创建一个消息队列
  • 原型

    •   #include <sys/types.h>
        #include <sys/ipc.h>
        #include <sys/msg.h>
        int msgget(key_t key, int msgflag);
      
  • 参数

    • key_t key:key值,可以是IPC_PRIVATE,如果是IPC_PRIVATE,则该消息队列适用于亲缘进程通信
    • int msgflag:消息队列标志位
      • IPC_CREAT | 0666
        • 如果消息队列不存在,则创建,如果存在,则打开
      • IPC_CREAT | IPC_EXCL | 0666
        • 如果消息队列不存在,则创建,如果消息队列存在,则报错EEXIST
  • 返回值

    • 成功:返回消息队列ID
    • 失败:返回-1,并置位错误码

msgsnd()

  • 功能

    • 向给定的消息队列中发送数据
  • 原型

    •   #include <sys/types.h>
        #include <sys/ipc.h>
        #include <sys/msg.h>
        int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
      
  • 参数

    • int msqid:消息队列id号
    • const void *msgp:要发送消息的地址
    • size_t msgsz:消息正文的大小
    • int msgflg:发送消息的标志位
      • 0:阻塞方式进行发送消息
      • IPC_NOWAIT:非阻塞方式调用
  • 返回值

    • 成功:返回0
    • 失败:返回-1
  • 注意

    • 消息队列的大小为16K = 16384Byte
  • 消息结构体原型

 struct msgbuf {
               long mtype;       /* message type, must be > 0 */
               char mtext[1];    /* message data */
           };
  • 正文大小:sizeof(struct msgbuf) - sizeof(long)

msgrcv()

  • 功能

    • 从给定的消息队列中取出消息内容
  • 原型

    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    int msgrcv(int msqid, void *msgp, size_t msgsz, long msgtype, int msgflg);
    
  • 参数

    • int msqid:消息队列
    • void *msgp:消息容器指针
    • size_t msgsz:消息正文的大小
    • long msgtype:消息类型
      • 等于0:每次读取消息队列中的第一个
      • 大于0:读取队列中的第一个为该类型的消息
      • 小于0:读取队列中第一个类型小于等于该值的绝对值的消息
    • int msgflg:接收消息的标志位
      • 0:阻塞方式进行发送消息
      • IPC_NOWAIT:非阻塞方式调用

msgctl()

  • 功能

    • 消息队列的控制
  • 原型

    •   #include <sys/types.h>
        #include <sys/ipc.h>
        #include <sys/msg.h>
        int msgctl(int msqid, int cmd, struct msqid_ds *buf);
      
  • 参数

    • int msqid:消息队列id
    • int cmd:控制方式
      • IPC_STAT:获取消息队列的状态
      • IPC_SET:设置消息队列的状态
      • IPC_RMID:删除当前消息队列,如果第二个参数为IPC_RMID,则第三个参数填NULL即可
      • IPC_INFO:获取消息队列的信息
    • struct msqid_ds *buf:消息队列信息结构体
    struct msqid_ds {
                   struct ipc_perm msg_perm;     /* Ownership and permissions */    权限
                   time_t          msg_stime;    /* Time of last msgsnd(2) */   最后一次向队列发送消息的时间
                   time_t          msg_rtime;    /* Time of last msgrcv(2) */最后一次从队列读消息的时间
                   time_t          msg_ctime;    /* Time of last change */   最后一次更改消息队列的时间
                   unsigned long   __msg_cbytes; /* Current number of bytes in    当前队列中的数据大小
                                                    queue (nonstandard) */
                   msgqnum_t       msg_qnum;     /* Current number of messages     当前队列中消息个数
                                                    in queue */
                   msglen_t        msg_qbytes;   /* Maximum number of bytes     最大的字节数:16K
                                                    allowed in queue */
                   pid_t           msg_lspid;    /* PID of last msgsnd(2) */   最后一次向队列发消息的进程的pid
                   pid_t           msg_lrpid;    /* PID of last msgrcv(2) */  最后一次从消息队列中读数据的进程pid
               };
          第一个成员的结构体
      struct ipc_perm {
                   key_t          __key;       /* Key supplied to msgget(2) */    创建消息队列的key值
                   uid_t          uid;         /* Effective UID of owner */     用户id
                   gid_t          gid;         /* Effective GID of owner */    组id
                   uid_t          cuid;        /* Effective UID of creator */   创建者的uid
                   gid_t          cgid;        /* Effective GID of creator */   创建者的组id
                   unsigned short mode;        /* Permissions */        权限
                   unsigned short __seq;       /* Sequence number */    队列号
               };
    

共享内存

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oK1SU5Sz-1692791464654)(C:\Users\无实的谎言花\Desktop\上海\Linux系统编程\图片\共享内存原理图.png)]

共享内存API

int shmget()

  • 功能

    • 获取共享内存段的ID
  • 原型

    •   int shmget(key_t key, size_t size, int shmflg);
      
  • 参数

    • key_t key:IPC_PRIVATE 或 ftok()
    • size_t size:申请的共享内存段大小 [4k的倍数]
    • int shmflg:创建消息队列的标志位
      • IPC_CREAT | 0666:如果共享内存不存在就创建,如果存在就返回共享内存的ID
      • IPC_CREAT | IPC_EXCL | 0666:如果共享内存不存在就创建,如果存在就报已存在错误
  • 返回值

    • 成功:返回ID
    • 失败:返回-1

void *shmat()

  • 功能

    • 映射共享内存到用户空间
  • 原型

    •   void *shmat(int shmid, const void *shmaddr, int shmflg);
      
  • 参数

    • int shmid:共享内存段ID
    • const void *shmaddr:映射到的地址
      • NULL:系统自动完成映射
    • int shmflg:共享内存操作的标志位
      • SHM_RDONLY:只读
      • 0:读写
  • 返回值

    • 成功:返回映射后的地址
    • 失败:返回(void *)-1

int shmdt()

  • 功能

    • 撤销映射
  • 原型

    •   int shmdt(const void *shmaddr);
      
  • 参数

    • const void *shmaddr:共享内存映射的地址
  • 返回值

    • 成功:返回0
    • 失败:返回-1置位错误码
  • 注意

    • 当一个进程结束的时候,它映射共享内存,会自动撤销映射

int shmctl()

  • 功能

    • 操作共享内存
  • 原型

    •   #include <sys/ipc.h>
        #include <sys/shm.h>
        int shmctl(int shmid, int cmd, struct shmid_ds *buf);
      
  • 参数

    • int shmid:共享内存id
    • int cmd:控制方式
      • IPC_STAT:队列的状态
        • 如果参数2位该值,则参数三就是当前队列的状态,使用一个结构体进行接收
      • IPC_SET:设置消息队列的属性
        • 如果参数2位该值,更改当前消息队列的信息,需要传入一个结构体变量,在主 调函数中初始化后,进行传递,结构体类型如下:
    struct msqid_ds {
                   struct ipc_perm msg_perm;     /* Ownership and permissions */
                   time_t          msg_stime;    /* Time of last msgsnd(2) */
                   time_t          msg_rtime;    /* Time of last msgrcv(2) */
                   time_t          msg_ctime;    /* Time of last change */
                   unsigned long   __msg_cbytes; /* Current number of bytes in
                                                    queue (nonstandard) */
                   msgqnum_t       msg_qnum;     /* Current number of messages
                                                    in queue */
                   msglen_t        msg_qbytes;   /* Maximum number of bytes
                                                    allowed in queue */
                   pid_t           msg_lspid;    /* PID of last msgsnd(2) */
                   pid_t           msg_lrpid;    /* PID of last msgrcv(2) */
               };
               
                该结构体的第一个成员:
                 struct ipc_perm {
                   key_t          __key;       /* Key supplied to msgget(2) */
                   uid_t          uid;         /* Effective UID of owner */
                   gid_t          gid;         /* Effective GID of owner */
                   uid_t          cuid;        /* Effective UID of creator */
                   gid_t          cgid;        /* Effective GID of creator */
                   unsigned short mode;        /* Permissions */
                   unsigned short __seq;       /* Sequence number */
               };
    
    • IPC_RMID:删除某个队列,如果第二个参数为IPC_RMID,那么第三个参数可以填NULL(常用)

    • IPC_INFO:如果参数是这个,则会在第三个参数中返回消息队列的信息

    • struct shmid_ds *buf:状态的指针

  • 返回值

    • 成功:返回0
    • 失败:失败返回-1并置位错误码

运用消息队列实现进程间的全双工通信

进程1

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/wait.h>
#include <time.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define SIZE sizeof(Msg_ds) - sizeof(long) 	//求正文长度

//创建消息结构体
typedef struct
{
	long msgtype; 	//消息类型
	char data[1024];//消息正文
} Msg_ds;


int main(int argc, char *argv[])
{

	//创建key值
	key_t key;
	if((key = ftok("/", 'k')) == -1)
	{
		perror("ftok error");
		return -1;
	}

	//创建消息队列
	int msgid;
	if((msgid = msgget(key, IPC_CREAT | 0664)) == -1)
	{
		perror("msgget error");
		return -1;
	}

	pid_t pid = fork();
	if(pid < 0)
	{
		perror("fork error");
		return -1;
	}
	else if(pid == 0)
	{
		Msg_ds msg = {.msgtype = 100};
		while(1)
		{
			printf("请输入消息内容:");
			fgets(msg.data, sizeof(msg.data), stdin);    //从终端输入字符串
			//将字符串的'\n'换成'\0'
			msg.data[strlen(msg.data)-1] = '\0';

			//将消息发送到消息队列中
			if(msgsnd(msgid, &msg, SIZE, 0) == -1)
			{
				perror("msgsnd error");
				return -1;
			}

			if(strcmp(msg.data, "quit") == 0)
			{
				break;
			}
					
			kill(getppid(), 9);
		}



	}
	else
	{
		//在消息队列中读数据
		Msg_ds msg1;
		while(1)
		{
			if(msgrcv(msgid, &msg1, SIZE, 200, 0) == -1)
			{
				perror("msgrcv error");
				return -1;
			}

			if(strcmp(msg1.data, "quit") == 0)
			{
				break;
			}

			printf("rcv: %s\n", msg1.data);
		}
		kill(pid, 9);
		wait(NULL);
	}

	msgctl(msgid, IPC_RMID, NULL);	
    return 0;
}



进程2

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/wait.h>
#include <time.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define SIZE sizeof(Msg_ds) - sizeof(long) 	//求正文长度

//创建消息结构体
typedef struct
{
	long msgtype; 	//消息类型
	char data[1024];//消息正文
} Msg_ds;


int main(int argc, char *argv[])
{
	//1、创建key值
	key_t key;
	if((key = ftok("/", 'k')) == -1)
	{
		perror("ftok error");
		return -1;
	}

	//2、创建消息队列
	int msgid;
	if((msgid = msgget(key, IPC_CREAT | 0664)) == -1)
	{
		perror("msgget error");
		return -1;
	}

	pid_t pid = fork();
	if(pid < 0)
	{
		perror("fork error");
		return -1;
	}
	else if(pid == 0)
	{
		//向消息队列发送数据
		Msg_ds msg = {.msgtype = 200};
		while(1)
		{
			puts("请输入消息内容:");
			fgets(msg.data, sizeof(msg.data), stdin);
			msg.data[strlen(msg.data)-1] = '\0';
	
			if(msgsnd(msgid, &msg, SIZE, 0) == -1)
			{
				perror("msgsnd error");
				return -1;
			}

			if(strcmp(msg.data, "quit") == 0)
			{	
			
				break;
			}
	
		}	
		
		kill(getppid(), 9);

	}
	else
	{
		//从消息队列中取数据
		Msg_ds msg1;
	
		while(1)
		{
			//从消息队列中取数据
			//第一个0表示取消息的类型,每次都是取第一个
			//第二个0表示阻塞方式从消息队列中取数据
			if(msgrcv(msgid, &msg1, SIZE, 100 ,0) == -1)
			{
				perror("msgsnd error");
				return -1;
			}
	 
			if(strcmp(msg1.data, "quit") == 0)
			{	
				break;
			}
 
			printf("rcv: %s\n", msg1.data);
 
		}
		kill(pid, 9);
		wait(NULL);
	}

	msgctl(msgid, IPC_RMID, NULL);

    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值