知识点涉及:1, Qt多线程编程
2, ZMQ使用发布订阅模式通信
使用python 写zmq 发布订阅, https://www.cnblogs.com/yezl/p/6604680.html点击打开链接
.h
#ifndef ZMQMANAGER_H
#define ZMQMANAGER_H
#include <QObject>
#include "zmq.h"
/************************************************************
*description : 通信管理类,应用zmq 发布订阅模式,zmq 版本:zmq 4.0.4, vs2015
*author : gyh
*date : 2018/5/4 11:24
*last change :
*************************************************************/
class Publisher;
class SubScriber;
class ZMQManager : public QObject
{
Q_OBJECT
public:
ZMQManager(QObject *parent = nullptr);
~ZMQManager();
void sendData(const QByteArray &ba);
signals:
void sigSendData(const QByteArray &ba);
void sigRun();
public slots:
void slotRecv(int cmd, const QString &status);
private:
Publisher * m_pub;
SubScriber * m_sub;
};
/************************************************************
*description : 发布端
*author : gyh
*date : 2018/5/3 17:02
*last change :
*************************************************************/
class Publisher : public QObject
{
Q_OBJECT
public:
Publisher(QObject * parent = nullptr);
~Publisher();
void initZMQ();
// 发送数据
void sendData(const QByteArray &ba);
private:
void * m_context;
void * m_publisher; //发布者
};
/************************************************************
*description : 订阅端
*author : gyh
*date : 2018/5/3 17:02
*last change :
*************************************************************/
class SubScriber : public QObject
{
Q_OBJECT
public:
SubScriber(QObject * parent = nullptr);
~SubScriber();
void initZMQ();
void recv(); //接收
signals:
void sigRecv(int cmd, const QString &status);
private:
void * m_context;
void * m_subscriber; //订阅者
};
#endif // ZMQMANAGER_H
.cpp
#include "zmqmanager.h"
#include "share/lkQDebug.h"
#include <QDebug>
#include <assert.h>
#include <QThread>
ZMQManager::ZMQManager(QObject *parent)
: QObject(parent)
{
QThread * pubThread = new QThread;
m_pub = new Publisher;
m_pub->moveToThread(pubThread);
pubThread->start();
connect(this, &ZMQManager::sigSendData, m_pub, &Publisher::sendData);
QThread * subThread = new QThread;
m_sub = new SubScriber;
m_sub->moveToThread(subThread);
subThread->start();
connect(m_sub, &SubScriber::sigRecv, this, &ZMQManager::slotRecv);
connect(this, &ZMQManager::sigRun, m_sub, &SubScriber::recv);
}
ZMQManager::~ZMQManager()
{
}
void ZMQManager::sendData(const QByteArray &ba)
{
DBG_LOG(11, QStringLiteral("主线程%1 发送数据").arg((int)QThread::currentThreadId()));
sigSendData(ba);
}
void ZMQManager::slotRecv(int cmd, const QString &status)
{
DBG_LOG(11, QStringLiteral("主线程%1 收到数据 %2 %3").arg((int)QThread::currentThreadId()).arg(cmd).arg(status));
}
//
Publisher::Publisher(QObject * parent /*= nullptr*/)
:QObject(parent)
{
initZMQ();
}
Publisher::~Publisher()
{
zmq_close(m_publisher); //关闭发布者
zmq_ctx_destroy(m_context); //释放发布者上下文
}
void Publisher::initZMQ()
{
m_context = zmq_ctx_new(); //创建zmq上下文
m_publisher = zmq_socket(m_context, ZMQ_PUB); //创建发布端套接字,非线程安全
int rc = zmq_bind(m_publisher, "tcp://*:6666"); //绑定端口6666
assert(rc == 0);
}
void Publisher::sendData(const QByteArray &ba)
{
int ret = zmq_send(m_publisher, ba.data(), ba.size(), 0); //发布固定内存数据
DBG_LOG(11, QStringLiteral("发布线程 %1, 发送数据 %2 字节").arg((int)QThread::currentThreadId()).arg(ret));
}
//
SubScriber::SubScriber(QObject * parent /*= nullptr*/)
: QObject(parent)
{
initZMQ();
}
SubScriber::~SubScriber()
{
zmq_close(m_subscriber); //关闭订阅端
zmq_ctx_destroy(m_context); //释放订阅端上下文
}
void SubScriber::initZMQ()
{
m_context = zmq_ctx_new(); //创建订阅端上下文
m_subscriber = zmq_socket(m_context, ZMQ_SUB); //创建订阅端套接字
int rc = zmq_connect(m_subscriber, "tcp://localhost:6666"); //连接端口6666
assert(rc == 0);
if (rc == -1)
{
int err = zmq_errno();
DBG_LOG(11, QString("connect error %1").arg(err));
return;
}
/*zmq_setsockopt()函数会对socket参数指定的socket进行设置,
设置的属性由option_name参数指定,属性值由参数option_value指定。
option_len参数指定属性值的数据存储空间的大小
option: 查看函数翻译文档 https://www.cnblogs.com/fengbohello/p/4398953.html
*/
int ret = zmq_setsockopt(m_subscriber, ZMQ_SUBSCRIBE, NULL, 0);
}
void SubScriber::recv()
{
int size = 0;
int cmdCode;
char buffer[256];
int nbytes = 0;
int offset = 0;
forever
{
// zmq_recv (void *s, void *buf, size_t len, int flags)
// zmq_recv()函数会从socket参数指定的socket上接收一个消息,
//并把这个消息存储在buf参数指定的内存空间中。超过len参数指
//定长度的任何数据都会被删去。如果在socket上没有消息可接收,
//zmq_recv()函数会进行阻塞,直到请求被满足为止。flag参数是由下面的选项组合而成的标志。
//ZMQ_DONTWAIT:此函数在非阻塞模式下执行
//Multi-part:一个ZMQ消息由1个或多个ZMQ消息帧组成
memset(buffer, 0, 256 * sizeof(char));
nbytes = zmq_recv(m_subscriber, &buffer, 256, 0);
if (nbytes == -1) {
qDebug() << "receiver cmd null , continue";;
continue;
}
DBG_LOG(11, QStringLiteral("订阅线程%1 , 收到数据 %2字节").arg((int)QThread::currentThreadId()).arg(nbytes));
memcpy(&cmdCode, buffer + offset, 4); offset += 4;
memcpy(&size, buffer + offset, 4); offset += 4;
switch (cmdCode)
{
case 1001:
{
QString state(buffer+offset);
DBG_LOG(11, QStringLiteral("收到命令%1, 状态%2").arg(cmdCode).arg(state));
sigRecv(cmdCode, state);
}
break;
default:
break;
}
}
}
main
#include "zmqmanager.h"
#include <QtWidgets/QApplication>
#include <QSettings>
#include <QTimer>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
a.setOrganizationDomain("WYX");
a.setApplicationName("ExamTeacher");
//服务器IP
QSettings confSetting(DIR_CONFIG + "conf_teacher.ini", QSettings::IniFormat);
QString address = confSetting.value("SERVER/IP").toString();
int port = confSetting.value("SERVER/Port", 6379).toInt();
#ifdef __TEST__
ZMQManager zmqManager;
zmqManager.sigRun();
QTimer::singleShot(1000, &zmqManager, [&]() {
int cmd = 1001;
int size = 128;
const char * state = "connected";
QByteArray ba;
ba.append((char*)&cmd, sizeof(cmd));
ba.append((char*)&size, sizeof(size));
ba.append(state, 128);
int n = ba.size();
zmqManager.sendData(ba);
});
#endif
#ifdef __TEST__
//w.startRedis();
//w.show();
//#else
ExamLogin * login = new ExamLogin(&w);
login->exec();
#endif
return a.exec();
}