在面向设备的应用编程当中,各种各样的链路传输功能是非常常用的,并且是完全无关于业务的工具代码,在之前的工作当中,结合自己的经验,同时也参考和借鉴了一些开源库的逻辑,这里值得一提的是QGroundControl,我承认之前在一开始的时候,在项目中我是直接照搬它的逻辑的,但是后来在使用的过程当中,发现了一些问题,便做出了一些修改和完善(PS:当然我会在以后的日子里继续完善),使其可以更加通用于一般的面向设备的应用编程。
项目地址:https://github.com/huxingqun/HsrComponents
有啥
我把该链路模块称之为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或者在本网站下方留言。