线程的同步互斥机制
同步互斥基本概念
- 临界资源(共享资源)
- 在多任务并发执行时,访问的同一个资源
- 临界区
- 访问临界资源的代码
- 竞态
- 多个任务共同抢占临界资源的现象叫做竞态
- 互斥
- 指某一个临界资源,同一时刻只允许一个访问者,具有唯一性和排他性
- 同步
- 使用相关手段,将任务有先后顺序的进行
解决竞态的方法
- 互斥锁
- 无名信号量
- 条件变量
互斥锁
- 互斥锁的使用逻辑
- 给临界区上锁,获取到锁的线程可以使用临界资源
- 释放锁,当一个线程释放锁资源后,其他线程对该锁可以继续抢占
- 互斥锁被一个任务抢占后,其他任务就不能获取到该锁资源了
互斥锁的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)
相关概念
- 线程间通信,可以使用全局变量来完成,原因是多个线程共享进程的资源
- 由于进程间的通信时,每个进程的用户空间是独立的,所以不能使用全局变量来完成进程间的通信
- 可以使用文件来完成两个进程间通信,一个进程向文件中写数据,另一个进程从文件中读数据,但是,又因为进程的调度是时间片轮询,上下文切换,所以,不确定先执行哪一个进程,文件操作有些行不通,但是不完全行不通
- 由于多个进程用户空间是独立的,但是共享内核空间
进程间通信的方式
- 内核提供的传统通信方式
- 无名管道
- 有名管道
- 信号
system V
提供的三种
4. 消息队列
5. 共享内存
6. 有名信号量(信号灯集)- 用于跨主机传输的通信机制
7. socket套接字通信
管道通信
- 原理
- 在3-4G内核空间中,创建一个特殊的文件(管道文件),管道文件直接保存在内存中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3xWeAY8U-1692791464650)(C:\Users\无实的谎言花\Desktop\上海\Linux系统编程\图片\管道文件.png)]
管道的特性
- 管道可以看成特殊的文件,一般文件存储在磁盘文件中,而管道文件可以存在内存中
- 管道文件遵循的规则是先进先出,被读取的文件不会在管道文件中存留
- 对管道的操作是一次性的
- 管道通信的方式是一种半双工的通信方式
- 在对管道操作时,只能使用文件IO进行操作,因为打开管道时会提供读端和写端的两个文件描述符
- 当管道文件所有的读端和写端都被关闭后,管道文件会自动关闭
- 管道文件的大小为64K = 65536Byte
- 管道文件可以自己与自己进行通信
- 读写管道文件时,不能使用lseek移动光标
- 读写的特点
- 如果读端存在,写管道,有多少写多少,直到写满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应用层的信号,其实是模拟底层中的中断,当一个进程正在运行时,如果有信号产生,则会执行该信号对应的操作
- 信号的处理方式
- 捕获:当捕获一个信号后,可以进行处理信号处理函数
- 忽略:当信号发射后,不做任何处理
- 默认:一般都是杀死进程
信号的种类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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
提供的三种- 消息队列
- 共享内存
- 有名信号量(信号灯集)
相关的指令
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值的组成:
-
返回值
- 成功:返回创建的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
:消息队列idint 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
:共享内存段IDconst 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
:共享内存idint cmd
:控制方式- IPC_STAT:队列的状态
- 如果参数2位该值,则参数三就是当前队列的状态,使用一个结构体进行接收
- IPC_SET:设置消息队列的属性
- 如果参数2位该值,更改当前消息队列的信息,需要传入一个结构体变量,在主 调函数中初始化后,进行传递,结构体类型如下:
- IPC_STAT:队列的状态
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;
}