一、前言
有时程序已经基本完善,但是发现需要加入一个TCP常连接去连接服务器获取所需数据和推送数据和日志,此时比较推荐的是新建一个线程去连接服务器,本篇文章将推荐一个方法,安全管理该线程以及线程所需资源,并且保证程序不中断的情况下,可以对服务器IP进行纠正并自动重连。
二、Qt 子线程TCP客户端建立方法
直接使用Qt自带的QTcpSocket来创建客户端,直接在项目中添加模块然后在源文件添加头文件即可直接调用:
C++
//.pro文件中添加
QT += network
//头文件
#include <QTcpSocket>
//调用时,例如此时服务器地址为172.0.0.1:12345:
QTcpSocket socket;
QString ip = "172.0.0.1";
int port = 12345;
socket.connectToHost(ip, port);//发起连接
socket.write("ping");//写数据
socket.readAll();//读取数据
socket.state();//连接状态
socket.waitForBytesWritten(1000);//写超时ms
socket.waitForReadyRead(1000);//读超时ms
子线程使用QThread建立,我们使用创建工作管理类移入线程的方式来让线程工作,这样可以更安全的管理线程资源,避免资源泄漏问题:
C++
//.pro文件
QT += core
//头文件
#include <QThread>
//调用时
QThread *mTcpThread;
TcpWorker *mWorker;//TcpWorker 需要继承 QObject
mTcpThread = new QThread(this);
mWorker = new TcpWorker;
mWorker->moveToThread(mTcpThread);//将工作移入到线程内部
connect(mTcpThread, &QThread::started, mWorker, &mWorker::process);//process为工作函数,循环或非循环执行任务
connect(mTcpThread, &QThread::finished, mWorker, &QObject::deleteLater);//回收绑定,线程结束自动回收资源
mTcpThread->start();//开始工作,开始执行前面绑定的函数process(该函数里来建立TCP连接)
//析构函数中添加回收
mWorker->stop();//循环执行任务时需要设置退出标志来结束循环,封装stop来设置
mTcpThread->quit();
mTcpThread->wait();
三、实际使用实例
前面已经给了线程的创建了,我这里直接给出TcpWorker 的代码,调用时结合前面子线程的代码。
tcpworker.h:
C++
#pragma once
#include <QObject>
#include <QTcpSocket>
#include <atomic>
class TcpWorker : public QObject
{
Q_OBJECT
public:
explicit TcpWorker(QObject *parent = nullptr);
public slots:
void process(); // 线程主函数
void stop();
private:
std::atomic_bool m_exit{false};//循环结束标志,bool类型原子变量比线程锁更方便
};
tcpworker.cpp:
C++
#include "tcpworker.h"
#include "constant.h"
#include <QThread>
#include <QDebug>
#include <QSettings>
QStringList mPushData;//将需要推送给服务器的内容定义为公共资源
QMutex mPushMutex;//对应的线程锁保证安全
TcpWorker::TcpWorker(QObject *parent)
: QObject(parent)
{
}
void TcpWorker::process()
{
//外层:连接-重连循环
while (!m_exit.load()) {
QString ip;
int port;
{//读取本地配置 放在大循环里面 离线后配置文件更新即可同步更新
QSettings ip_port("./netty.ini", QSettings::IniFormat);
if (!ip_port.contains("ip") || !ip_port.contains("port")) {
ip_port.setValue("ip", "127.0.0.1");
ip_port.setValue("port", 24689);
ip_port.sync();
qWarning() << "netty.ini not found, set default.";
}
ip = ip_port.value("ip").toString();
port = ip_port.value("port").toInt();
}
qDebug() << "baiyi_gps TCP target =>" << ip << ":" << port;
QTcpSocket socket;
bool socketConnectStatus = true;
socket.connectToHost(ip, port);
//初步判断是否连接上了
if (!socket.waitForConnected(10000)) {
socketConnectStatus = false;
}
//测试是否是假连接/半连接 -- 未完成
if(socketConnectStatus){
for (int i = 0; i < 11 && !m_exit.load(); ++i) {
QThread::sleep(1);
}
quint64 ret = socket.write("ping");
socket.waitForBytesWritten(1000);
socket.flush();
//解决写缓存不发送,关闭程序才一次性发送问题
if (socket.waitForReadyRead(10)) {
socket.readAll(); // 清空接收缓冲
}
QThread::sleep(1);
qDebug() << socket.state() << ret;
if (socket.state() != QAbstractSocket::ConnectedState) {//判断是否失败
socketConnectStatus = false;
}
}
//确认是否是真连接
if(!socketConnectStatus){
qWarning() << "baiyi_gps TCP connect failed, retry after 60s";
//放到休眠前面=>休眠结束重连成功发送积攒的报文=>减少报文丢失率
{//断线后 每5分钟检测超过300条数据就清理 防止缓存过多
QMutexLocker locker(&mPushMutex);
qDebug() << "mPushData size :" << mPushData.size();
if (mPushData.size() > 300) {
mPushData.clear();
qWarning() << "mPushData size > 300, cleaned.";
}
}
// 连接失败,暂时休眠
for (int i = 0; i < 60 * 5 && !m_exit.load(); ++i) {
QThread::sleep(1);
}
continue;
}
qDebug() << "baiyi_gps TCP connected";
//发送循环
while (!m_exit.load() && socket.state() == QAbstractSocket::ConnectedState) {
QStringList dataToSend;
int sendCount = 0;
{
QMutexLocker locker(&mPushMutex);
//不直接清空,因为socket.state()需要write()/read()后才会更新
dataToSend = mPushData;
}
if (!dataToSend.isEmpty()) {
for (const QString &one : dataToSend) {
if (m_exit.load() || socket.state() != QAbstractSocket::ConnectedState) {
break;
}
// QByteArray payload = one.toUtf8();//字符串
QByteArray payload = QByteArray::fromHex(one.toLatin1());//HEX
socket.write(payload);
socket.flush();
//解决写缓存不发送,关闭程序才一次性发送问题
if (socket.waitForReadyRead(10)) {
socket.readAll(); // 清空接收缓冲
}
// 每条之间间隔 100ms(可被退出打断)
for (int i = 0; i < 100 && !m_exit.load(); ++i) {
QThread::msleep(1);
}
if (socket.state() != QAbstractSocket::ConnectedState) {//判断是否失败
qWarning() << "TCP write failed, treat as disconnected";
break;
}
sendCount++;
}
qDebug() << "baiyi_gps TCP send :" << sendCount;
{
QMutexLocker locker(&mPushMutex);
if (sendCount > 0 && sendCount <= mPushData.size()) {
mPushData.erase(mPushData.begin(), mPushData.begin() + sendCount);
} else if (sendCount > mPushData.size()) {
// 如果n超过列表长度,直接清空列表
mPushData.clear();
}
}
}
QThread::sleep(1);
}
qWarning() << "baiyi_gps TCP disconnected, reconnect...";
socket.disconnectFromHost();
socket.waitForDisconnected(1000);
}
qDebug() << "baiyi_gps TCP thread exit";
}
void TcpWorker::stop()
{
m_exit.store(true);
}
四、其他
如果是需要推送大量数据,为节省开销增强性能可以选择使用QQueue来作为传递公共资源调用时:
C++
QQueue<QByteArray> g_sendQueue;
QMutex g_sendMutex;
QWaitCondition g_sendCond;
//添加数据
void pushData(const QString &hex)
{
QByteArray data = QByteArray::fromHex(hex.toLatin1());
QMutexLocker locker(&g_sendMutex);
g_sendQueue.enqueue(data);
g_sendCond.wakeOne(); // 唤醒发送线程,在此之前线程是睡眠的状态
}
//取出数据
{
QMutexLocker locker(&g_sendMutex);
if (g_sendQueue.isEmpty()) {// 没数据就等待休眠,不会占 CPU
g_sendCond.wait(&g_sendMutex, 1000); // 最多等 1 秒
continue;
}
data = g_sendQueue.dequeue();// 醒一次只取一条
}
//缓存限流:
{
QMutexLocker locker(&g_sendMutex);
if (g_sendQueue.size() > 300) {
g_sendQueue.clear();
qWarning() << "send queue overflow, cleared";
}
}