目录
[System V消息队列](#System V消息队列)
System V消息队列
消息队列的基本原理
消息队列实际上就是在系统当中创建了一个队列,队列当中的每个成员都是一个数据块,这些数据块都由类型和信息两部分构成,如果消息队列中有很多不同类型的数据块,那么对应的进程只需要通过自己想要得到的类型来获取对应的数据块即可,同种类型的数据块遵循先进先出的规则,两个互相通信的进程通过某种方式看到同一个消息队列。

消息队列的数据结构:
cpp
struct msqid_ds {
struct ipc_perm msg_perm;
struct msg *msg_first; /* first message on queue,unused */
struct msg *msg_last; /* last message in queue,unused */
__kernel_time_t msg_stime; /* last msgsnd time */
__kernel_time_t msg_rtime; /* last msgrcv time */
__kernel_time_t msg_ctime; /* last change time */
unsigned long msg_lcbytes; /* Reuse junk fields for 32 bit */
unsigned long msg_lqbytes; /* ditto */
unsigned short msg_cbytes; /* current number of bytes on queue */
unsigned short msg_qnum; /* number of messages in queue */
unsigned short msg_qbytes; /* max number of bytes on queue */
__kernel_ipc_pid_t msg_lspid; /* pid of last msgsnd */
__kernel_ipc_pid_t msg_lrpid; /* last receive pid */
};
msqid_ds的全称为Message Queue Identifier Data Structure。
值得注意的是:不管是信号量、消息队列、共享内存他们都共有这个struct ipc_perm类型的结构体:
cpp
struct ipc_perm{
__kernel_key_t key;
__kernel_uid_t uid;
__kernel_gid_t gid;
__kernel_uid_t cuid;
__kernel_gid_t cgid;
__kernel_mode_t mode;
unsigned short seq;
};
需要特别注意的是System V IPC资源的生命周期是随操作系统内核的,因此创建后必须调用对应的系统调用来销毁IPC资源;或者可以使用Linux指令进行销毁:
ipcs -q:查看内核中存在的消息队列。
ipcrm -q msgid:销毁用户层id标识为msgid的消息队列。
类似的还有共享内存、信号量的指令:
ipcs -m和ipcrm -m shmid:共享内存相关。
ipcs -s和ipcrm -s semid:信号量相关。
消息队列的创建
cpp
int msgget(key_t key, int msgflg);
第一个参数key通过ftok获取:
cpp
key_t ftok(const char *pathname, int proj_id);
- ftok函数的作用就是,将一个已存在的路径名pathname和一个整数标识符proj_id转换成一个key值,称为IPC键值,即消息队列的内核层标识符在使用shmget函数获取共享内存时,这个key值会被填充进维护共享内存的数据结构(msqid_ds中的ipc_perm中)当中。
第二个参数msgflg:
- IPC_CREAT:如果内核中不存在键值与key相等的共享内存,则新建一个共享内存并返回该共享内存的句柄;如果存在这样的共享内存,则直接返回该共享内存的句柄
- IPC_CREAT | IPC_EXCL:如果内核中不存在键值与key相等的共享内存,则新建一个共享内存并返回该共享内存的句柄;如果存在这样的共享内存,则出错返回
返回值:
- 消息队列创建成功时,msgget函数返回的一个有效的消息队列标识符(消息队列的用户层标识符)。
消息队列的销毁和带出消息队列内核结构中的数据
cpp
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
第二个参数cmd:
|----------|------------------------------------------|
| PC_STAT | 获取共享内存的当前关联值,此时参数buf作为输出型参数 |
| IPC_SET | 在进程有足够权限的前提下,将共享内存的当前关联值设置为buf所指的数据结构中的值 |
| IPC_RMID | 删除共享内存段 |
返回值:
- 调用成功返回0;调用失败返回-1
向消息队列中发送数据
cpp
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
第二个参数msgp:
- msgp指向一个struct msgbuf类型的结构体,这个类型结构体需要我们自己定义出来!如下:
cpp
struct msgbuf
{
long mtype;
char mtext[default_size];
};
第三个参数msgsz:
- 必须是定义出来的struct msgbuf类型对象中mtext数组的大小,而不是整个结构体的大小!
第四个参数msgflg:
- 一般为0
返回值:
- 调用成功,返回0。
- 调用失败,返回-1。
从消息队列中获取数据
cpp
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
第四个参数msgtyp:
- 用于获取从消息队列中获取指定类型的数据块。从消息队列中将数据块类型为msgtyp的数据块放到定义的struct msgbuf类型结构体中的mtext成员数组中。
返回值:
- 调用成功,返回实际获取到mtext数组中的字节数。
- 调用失败,返回-1。
使用消息队列实现进程间通信
srever是读端,client是写端。
cpp
//MsgQueue.hpp
#ifndef MSGQUEUE_HPP
#define MSGQUEUE_HPP
#include <iostream>
#include <cstring>
#include <string>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
const int default_msgid = -1;
const int default_size = 1024;
#define PATHNAME "/tmp"
#define PROJID 0x123
#define GET_MSGQUEUE (IPC_CREAT)
#define CREATE_MSGQUEUE (IPC_CREAT | IPC_EXCEL)
enum Status
{
FTOK_ERROR = 1,
MSGGET_ERROR = 2,
MSGCTL_ERROR = 3
};
class MsgQueue
{
public:
std::string getHex(int num)
{
char arr[64] = {0};
snprintf(arr, "0x%x", num);
return std::string(arr);
}
struct msgbuf
{
long mtype;
char mtext[default_size];
};
MsgQueue()
,_masgid(default_msgid)
{}
void GetAttr()
{
struct msqid_ds outbuffer;
int n = ::msgctl(_msgid, IPC_STAT, &outbuffer);
if(n < 0){
std::cerr << "msgctl IPC_STAT failed!" << std::endl;
exit(MSGCTL_ERROR);
}
std::cout << "outbuffer.msg_perm.__key: " << outbuffer.msg_perm.__key << std::endl;
}
void Create(int flag, mode_t mode)
{
key_t key = ftok(PATHNAME, PROJID);
if(key == -1){
std::cout << "ftok error!" << std::endl;
exit(FTOK_ERROR);
}
int msgid = -1;
if(flag == CREATE_MSGQUEUE){
msgid = ::msgget(key, CREATE_MSGQUEUE | flag);
}
else{
msgid = ::msgget(key, GET_MSGQUEUE);
}
if(msgid == -1){
std::cout << "msgget error!" << std::endl;
exit(MSGGET_ERROR);
}
_msgid = msgid;
std::cout << "msgqueue created: " << _msgid << std::endl;
}
void Send(int type, const string& text)
{
struct msgbuf msg;
memset(&msg, 0, sizeof(msg));
msg.mtype = type;
strcpy(msg.mtext, text.c_str());
int n = ::msgsnd(_msgid, &msg, sizeof(msg.mtext), 0);
if(n == -1){
std::cerr << "msgsnd error!" << std::endl;
return;
}
}
int Recv(int type, string& text)
{
struct msgbuf msg;
memset(&msg, 0, sizeof(msg));
int n = ::msgrcv(_msgid, &msg, sizeof(msg.mtext), type, 0);
if(n == -1){
std::cerr << "msgrcv error!" << std::endl;
return n;
}
msg.mtext[n] = '\0';
text = msg.mtext;
return n;
}
void Destroy()
{
int n = ::msgctl(_msgid, IPC_RMID, nullptr);
if(n == -1){
std::cout << "msgctl error!" << std::endl;
exit(MSGCTL_ERROR);
}
std::cout << "msgqueue destroyed!" << std::endl;
}
~MsgQueue() = default;
private:
int _msgid;
};
#define MSG_TYPE_CLIENT 1
#define MSG_TYPE_SERVER 2
class Client : public MsgQueue
{
public:
Client()
{
MsgQueue::Create(CREATE_MSGQUEUE);
}
~Client()
{
MsgQueue::Destroy();
}
};
class Server : public MsgQueue
{
public:
Server()
{
MsgQueue::Create(GET_MSGQUEUE);
}
~Server() = default;
};
#endif
cpp
//Client.cpp
//发送消息方
#include "MsgQueue.hpp"
int main()
{
Client client;
string str = "";
while(true)
{
std::cout << "please input message: ";
std::getline(std::cin, str);
client.Send(MSG_TYPE_CLIENT, str);
if(str == "exit"){
break;
}
}
return 0;
}
cpp
//Server.cpp
//接收消息方
#include "MsgQueue.hpp"
#include "ChainOfResponsibility.hpp"
int main()
{
Server server;
std::string text = "";
while(true)
{
int n = server.Recv(MSG_TYPE_CLIENT, text);
if(n > 0){
std::cout << "recv message: " << text << std::endl;
if(text == "exit"){
break;
}
}
}
return 0;
}
让责任链类对接收到的数据进行特定处理
需求:
1.给srever收到的内容拼接上时间和进程的pid。
2.server收到的内容持久化的保存到文件中。
3.文件的内容如果过大,就要进行切片保存并在指定的目录下打包(安静文件打包成.tar)保存。
责任链模式
一种行为设计模式,它允许你将请求沿着处理者链进行传递,每个处理者都对请求进行检查,以决定是否处理它。如果处理者能够处理这个请求,它就会处理这个请求;否则,这个处理者将请求传递给链中的下一个处理者。这个设计模式是的多个处理者对象都有机会处理请求。
cpp
//ChainOfResponsibility.hpp
//#pragma once
#ifndef CHAIN_OF_RESPONSIBILITY_HPP
#define CHAIN_OF_RESPONSIBILITY_HPP 1
#include <iostream>
#include <string>
#include <memory>
#include <ctime>
#include <sstream>
#include <filesystem> //C++17
#include <fstream>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
//需求:
//1.client发送给server的内容,拼接上时间和进程的pid
//2.server收到的内容持久化的保存到文件中
//3.文件的内容如果过大,就要进行切片保存并在指定的目录下打包保存,命令自定义
class HandlerText
{
public:
HandlerText() = default;
virtual ~HandlerText() = default;
virtual void Excute(const std::string& text) = 0;
void SetNext(std::shared_ptr<HandlerText> next)
{
_next = next;
}
void Enable()
{
_enable = true;
}
void Disable()
{
_enable = false;
}
protected:
std::shared_ptr<HandlerText> _next;
bool _enable = true;
};
//对文本进行格式化处理
class HandlerTextFormat : public HandlerText
{
public:
void Excute(const std::string& text) override
{
std::string format_result = "";
if(_enable){
std::stringstream ss;
ss << time(nullptr) << "-" << getpid() << "-" << text << "\n";
format_result = ss.str();
}
if(_next){
_next->Excute(format_result);
}
else{
std::cout << "已到达责任链末端。" << std::endl;
}
}
};
const std::string default_filepath = "./tmp/";
const std::string default_filename = "test.log";
//将文件数据持久保存到文件中
class HandlerTextSaveFile : public HandlerText
{
public:
HandlerTextSaveFile(const std::string& filepath = default_filepath,
const std::string& filename = default_filename)
:_filepath(filepath)
,_filename(filename)
{
if(std::filesystem::exists(_filepath)){
return;
}
try{
std::filesystem::create_directories(_filepath);
}
catch(const std::filesystem::filesystem_error& e){
std::cerr << e.what() << std::endl;
}
}
void Excute(const std::string& text) override
{
if(_enable){
std::string file = _filepath + _filename;
std::ofstream ofs(file, std::ios::app);
if (!ofs.is_open()){
std::cerr << "open file error: " << file << std::endl;
return;
}
ofs << text;
ofs.close();
}
if(_next){
_next->Excute(text);
}
else{
std::cout << "已到达责任链末端。" << std::endl;
}
}
private:
std::string _filepath;
std::string _filename;
};
const int default_max_fileline = 5;
//文件中的数据行超过一定数量,则将该文件打包成一个.tar文件,删除打包后的文件,然后下一次接收到新数据重新在
//类中设置的_filepath路径下生成文件名为_filename的文件,需要先检查路径中的目录是否存在,不存在则创建
class HandlerTextBackup : public HandlerText
{
public:
HandlerTextBackup(const std::string& filepath = default_filepath,
const std::string& filename = default_filename,
const int& maxline = default_max_fileline)
:_filepath(filepath)
,_filename(filename)
,_maxline(maxline)
{
}
void Excute(const std::string& text) override
{
std::string file = _filepath + _filename;
if(IsOutOfRange(file)){ //如果超范围了进行对文件进行切片备份
Backup(file);
}
}
private:
void Backup(const std::string& file)
{
std::string suffix = std::to_string(time(nullptr)); //带路径的
std::string backup_filename = file + "." + suffix; //带路径的
std::string src_filename = _filename + "." + suffix; //不带路径
std::string tar_filename = _filename + suffix + ".tar"; //不带路径
//1.先对文件进行备份即对文件进行重命名
pid_t pid = fork();
if(pid < 0){
perror("fork");
}
if(pid == 0){
std::filesystem::rename(file, backup_filename);
std::cout << "将文件: " << file << " 备份为: " << backup_filename << std::endl;
std::filesystem::current_path(_filepath); //Linux下的chdir也可以改变当前进程坐在的目录
execlp("tar", "-zcf", src_filename.c_str(), tar_filename.c_str());
exit(1);
}
int status;
int n = waitpid(pid, &status, 0);
if(n == pid){
if(WIFEXITED(status) && WEXITSTATUS(status)){
//子进程成功进行程序替换执行tar指令将文件打包成功,接下来删除已经被打包的文件
std::filesystem::remove(backup_filename); //Linux下的unlink也可以删除文件
std::cout << "删除已经打包的文件: " << backup_filename << std::endl;
}
}
}
bool IsOutOfRange(const std::string& file)
{
int line = 0;
std::ifstream ifs(file);
if(!ifs.is_open()){
std::cerr << "open file error: " << file << std::endl;
return false;
}
std::string s = "";
while(std::getline(ifs, s)){
++line;
}
ifs.close();
return line > _maxline ? true : false;
}
private:
std::string _filepath;
std::string _filename;
int _maxline;
};
//责任链入口类
class HandlerEntry
{
public:
HandlerEntry(const std::string& filepath = default_filepath,
const std::string& filename = default_filename,
const int& maxline = default_max_fileline)
{
_format = std::make_shared<HandlerTextFormat>();
_savefile = std::make_shared<HandlerTextSaveFile>(filepath, filename);
_backup = std::make_shared<HandlerTextBackup>(filepath, filename, maxline);
_format->SetNext(_savefile);
_savefile->SetNext(_backup);
}
~HandlerEntry() = default;
void Run(const std::string& text)
{
_format->Excute(text);
}
void EnableHandler(bool isformat, bool issave, bool isbackup)
{
isformat ? _format->Enable() : _format->Disable();
issave ? _savefile->Enable() : _savefile->Disable();
isbackup ? _backup->Enable() : _backup->Enable();
}
private:
std::shared_ptr<HandlerText> _format;
std::shared_ptr<HandlerText> _savefile;
std::shared_ptr<HandlerText> _backup;
};
#endif
Server端的接入责任链:
cpp
#include "MsgQueue.hpp"
#include "ChainOfResponsibility.hpp"
int main()
{
Server server;
auto u = std::make_unique<HandlerEntry>(); //构建责任链入口类
u->EnableHandler(true,true,true); //设置责任链中各个处理者是否可用
std::string text = "";
while(true)
{
int n = server.Recv(MSG_TYPE_CLIENT, text);
if(n > 0){
std::cout << "recv message: " << text << std::endl;
if(text == "exit"){
break;
}
}
u->Run(text); //责任链除处理从消息队列中拿到的数据
}
return 0;
}