在面向设备的应用编程当中,各种各样的链路传输功能是非常常用的,并且是完全无关于业务的工具代码,在之前的工作当中,结合自己的经验,同时也参考和借鉴了一些开源库的逻辑,这里值得一提的是QGroundControl,我承认之前在一开始的时候,在项目中我是直接照搬它的逻辑的,但是后来在使用的过程当中,发现了一些问题,便做出了一些修改和完善(PS:当然我会在以后的日子里继续完善),使其可以更加通用于一般的面向设备的应用编程。
项目地址:
huxingqun/HsrComponentsgithub.com有啥
我把该链路模块称之为link,link代码结构上属于HsrCore库中,目前link中只是封装了tcp,udp,serialport三种链路;
特点
- 三种链路都是基于线程实现的异步,起一个链路相当于一个线程,封装成异步的链路主要是为了防止在GUI线程当中发送数据时候,由于IO阻塞导致的界面卡顿情况;
- 底层的网络IO和串口IO使用的都是Qt库,当然有兴趣的话可以使用其他的库代替,目前在终端应用编程当中,Qt的这些IO库是够用的了;
- 参考了QGroundControl的链路实现,但是经过自己在工作和学习当中的修改和完善之后,比QGroundControl的更加精简,灵活,易用;
- 发送数据和接收数据接口底层异步实现;
- 从链路中接收数据可以任意选择信号槽的方式和回调的方式,链路类既提供了信号来分发从IO流中取出的数据,又提供了注册回调函数的方式来分发数据,两者可以同时使用,如果对接收到的数据实时性要求高,使用注册回调会好一些,并且可以在回调函数中进行数据初解析;
简单介绍
#ifndef HSRABSTRACTLINK_H
#define HSRABSTRACTLINK_H
#include <QThread>
#include <memory>
#include <functional>
#include "hsrcore_global.h"
// 链路数据回调函数
typedef std::function<void(const QByteArray& data)> ReceivedDataHandleFunc;
// udp通讯时候,暂时以ip来区分设备
typedef std::function<void(QString ip, QByteArray data)> UdpReceivedDataHandleFunc;
class HsrLinkAbstractConfig;
class HSRCORE_EXPORT HsrAbstractLink : public QObject
{
Q_OBJECT
public:
enum LinkOperateState {
LINK_UNKNOW = 0, //未知
LINK_OPEN_FAILED = 1, //打开失败
LINK_OPEN_SUCCESSED = 2, //打开成功
LINK_CLOSED = 3, //已关闭
};
explicit HsrAbstractLink(QObject* parent = nullptr);
virtual ~HsrAbstractLink();
virtual bool start() = 0;
//! 打开连接
virtual void open(const std::shared_ptr<HsrLinkAbstractConfig>& linkConfigPtr) = 0;
//! 关闭连接
virtual void close() = 0;
//! 注册数据接收回调函数
virtual void registerRecvdDataHandleFunc(ReceivedDataHandleFunc func);
//! 取消注册数据接收回调函数
virtual void unregisterRecvdDataHandleFunc();
//! 是否已经打开
virtual bool isOpen();
//! 发送信息
//! data 发送消息内容 消息长度
virtual bool sendData(const QByteArray& data) = 0;
//! 返回连接配置指针
HsrLinkAbstractConfig* linkConfig();
signals:
//! 准备工作信号,所有的操作都是基于该动作成功发出之后才可以进行
void ready(bool success);
//! 收到串口数据时候发出
void dataReceived(QByteArray data);
//! 打开连接结果
//! opened: true 打开成功; false 打开失败
void openLinkResp(bool opened);
//! 通知连接已关闭
void linkClosed();
protected slots:
//响应打开回应
void _respOpenLink(bool opened);
//! 响应关闭连接
void _respCloseLink();
protected:
std::shared_ptr<HsrLinkAbstractConfig> m_linkconfig_ptr_;
bool m_isopen_; //记录link是否已经打开
ReceivedDataHandleFunc m_recvd_data_handle_func_;
};
#endif
该类是所有链路的虚基类,可以看出,接口方法并不多,从注释上可以看出来就是打开、关闭、发送、注册回调以及一些通知信号,继承该基类的分别有三个派生类HsrTcpSocketLink、HsrUdpSocketLink以及HsrSerialPortLink分别对应三种链路方式;三种链路方式对应了三种配置分别是HsrTcpSocketLinkConfig、HsrUdpSocketLinkConfig以及HsrSerialPortLinkConfig,配置类也有一个共同的基类HsrLinkAbstractConfig,该基类主要有一个纯虚接口,主要是用来区分类型。
如何使用
这里简单拿串口链路来举例,这里举例就直接写用法:
- 打开连接
HsrSerialPortLink* serialLink = new HsrSerialPortLink();
HsrSerialPortLinkConfig* serialCfg = new HsrSerialPortLinkConfig();
// 配置参数
serialCfg->setPortName("COM1");
serialCfg->setParity(QSerialPort::NoParity);
serialCfg->setBaudrate(QSerialPort::Baud115200);
serialCfg->setStopBits(QSerialPort::OneStop);
serialCfg->setFlowControl(QSerialPort::NoFlowControl);
// 打开串口
serialLink->open(std::shared_ptr<HsrLinkAbstractConfig>(serialCfg));
- 发送数据
char data[5] = { 1,2,3,4,5 };
// 这里只是举例,实际使用时候可以序列化任何数据发出去
serialLink->sendData(QByteArray(data, 5));
- 接收数据
方式1:使用信号槽的方式接收数据
// 这里为了演示方便,使用lambda表达式作为槽函数
QObject::connect(serialLink, &HsrSerialPortLink::dataReceived, [=] (QByteArray data){
qDebug() << data.toHex();
});
方式2:使用注册回调函数的方式接收数据,这里为了方便演示代码,使用lambda表达式作为回调函数,实际上这里可以使用全部函数或者任何的成员函数作为回调,只要你喜欢就可以
//同样是为了方便,使用lambda表达式作为回调函数
serialLink->registerRecvdDataHandleFunc([=](const QByteArray& data) {
qDebug() << data.toHex();
});
- 关闭连接
serialLink->close();
结束语
虽然说这并不是什么很牛X的东西,但是自我感觉用起来还可以,并且在实际项目中一直在用,也一直会继续改善。如果对任何人有帮助,欢迎到github上去clone and star吧,同时有任何的批评和建议,欢迎Issue或者留言。