【Qt】tcp服务器、tcp多线程服务器、心跳保持、服务端组包

文章目录

    • 背景:
    • 代码实现(服务端):
    • 总结
    • 改进方案:多线程tcp服务器
      • 代码实现(服务端)
        • 心跳保持:
        • 大文件收发

背景:

局域网内,客户端会进行udp广播,服务端会监听udp广播并回复,以此可以让客户端发现服务端。

发现之后,客户端可以发起建立tcp连接的请求,服务端响应请求,建立tcp连接。

然后客户端可以与服务端进行tcp消息的收发(数据以json包格式传输)。

注意:本文仅是服务端代码

代码实现(服务端):

封装了一个networkmanager类来完成网络通讯功能。

networkmanager.h

#pragma once
#include <QtWidgets/QWidget>
#include <QUdpSocket>
#include <QTcpServer>
#include <QTcpSocket>
#include <QDebug>
#include <QByteArray>
#include <QMap>
#include <QString>
#include <QJsonDocument>
#include <QJsonObject>class NetworkManager : public QWidget
{Q_OBJECTpublic:NetworkManager(QWidget* parent = nullptr);void tcpSendCommand(QString ip, QString Command);//tcp发送指令void removeTcpConnect(QString ip);
signals:void signalProjectData(QString ip, QJsonObject jsonObject);void signalPerformanceData(QString ip, QJsonObject jsonObject);void signalStatusData(QString ip, QJsonObject jsonObject);void signalDisconnect(QString ip);private:class Impl;std::unique_ptr<Impl> _impl = nullptr;
};

networkmanager.cpp

#include "networkmanager.h"
class NetworkManager::Impl : public QObject
{Q_OBJECT
public:explicit Impl(NetworkManager* obj);~Impl() final;NetworkManager* _self = nullptr;QUdpSocket* _udpSocket = nullptr;QTcpServer* _tcpServer = nullptr;QMap<QString, QTcpSocket*> _tcpSocketList;//tcp连接列表
public:void udpStartListening();//udp开始监听void tcpStartListening();// tcp开始监听
public slots:void slotUdpReadData();void slotTcpProcessConnection();void slotReadSocket();void slotDisconnectSocket();
};
NetworkManager::Impl::Impl(NetworkManager* obj) :_self(obj)
{}
NetworkManager::Impl::~Impl() = default;
NetworkManager::NetworkManager(QWidget* parent): QWidget(parent), _impl(std::make_unique<Impl>(this))
{_impl->_udpSocket = new QUdpSocket(this);_impl->_tcpServer = new QTcpServer(this);_impl->tcpStartListening();_impl->udpStartListening();
}
void NetworkManager::Impl::tcpStartListening()
{// 有新的连接 绑定连接处理函数connect(_tcpServer, &QTcpServer::newConnection, this, &Impl::slotTcpProcessConnection);if (_tcpServer->listen(QHostAddress::Any, 5556)) { // 端口号qDebug() << "tcp Listening on port 5555";}else {qDebug() << "TCP Error" << "Failed to Listen to port 5555";}
}
void NetworkManager::Impl::udpStartListening()// udp监听
{connect(_udpSocket, &QUdpSocket::readyRead, this, &Impl::slotUdpReadData);if (_udpSocket->bind(QHostAddress::Any, 5555)) { // 端口号qDebug() << "udp Listening on port 5555";}else {qDebug()<< "UDP Error"<< "Failed to bind to port 5555";}}
void NetworkManager::Impl::slotUdpReadData() 
{while (_udpSocket->hasPendingDatagrams()) {QByteArray datagram;datagram.resize(_udpSocket->pendingDatagramSize());QHostAddress sender;quint16 senderPort;// 读取数据包_udpSocket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);// 打印接收到的数据qDebug() << "Received datagram from" << sender.toString() << ":" << senderPort;// 发送响应消息QByteArray response = "udp responsed" ;_udpSocket->writeDatagram(response, QHostAddress(sender), senderPort);}
}
void NetworkManager::Impl::slotTcpProcessConnection()// tcp 连接处理
{// 通过TcpServer拿到一个socket对象clientsocket,用它来和客户端通信;QTcpSocket* newTcpSocket = _tcpServer->nextPendingConnection();QString ip = newTcpSocket->peerAddress().toString();QString ipv4 = QHostAddress(ip.mid(7)).toString();// 获取ip地址qDebug() << ipv4;// 将连接存到map中_tcpSocketList.insert(ipv4, newTcpSocket);connect(newTcpSocket, &QTcpSocket::readyRead, this, &Impl::slotReadSocket);connect(newTcpSocket, &QTcpSocket::disconnected, this, &Impl::slotDisconnectSocket);
}
void NetworkManager::Impl::slotReadSocket()
{qDebug() << "tcp read data";QTcpSocket* tempSocket = qobject_cast<QTcpSocket*>(sender());if (tempSocket){//先获取发送消息方的ip地址QString ip = tempSocket->peerAddress().toString();QString ipv4 = QHostAddress(ip.mid(7)).toString();if (_tcpSocketList.contains(ipv4)){//解析QByteArray jsonData = _tcpSocketList[ipv4]->readAll();// 读取数据QJsonDocument document = QJsonDocument::fromJson(jsonData);if (document.isNull()) {// 处理JSON解析错误qDebug()<<"document is Null";}else {if (document.isObject()) {QJsonObject jsonObject = document.object();// 解析JSON文件 根据type 做不同处理QString type = jsonObject["Type"].toString();if (type == "Project"){emit _self->signalProjectData(ipv4, jsonObject);}else if (type == "Performance"){emit _self->signalPerformanceData(ipv4, jsonObject);} else if (type == "Status"){emit _self->signalStatusData(ipv4, jsonObject);}}}}}
}
// 处理断连
void NetworkManager::Impl::slotDisconnectSocket()
{QTcpSocket* tempSocket = qobject_cast<QTcpSocket*>(sender());QString ip = tempSocket->peerAddress().toString();QString ipv4 = QHostAddress(ip.mid(7)).toString();qDebug() << "tcp disconnect:" << ipv4;_tcpSocketList[ipv4]->close();_tcpSocketList.remove(ipv4);//移除列表// 发出信号  提示断连emit _self->signalDisconnect(ipv4);
}
// 通信 发送指令
void NetworkManager::tcpSendCommand(QString ip, QString Command)
{qDebug() << Command<<"   "<<ip;if (_impl->_tcpSocketList.contains(ip)){// 发送指令qDebug() << "send command:"<< Command;_impl->_tcpSocketList[ip]->write(Command.toUtf8());}
}
void NetworkManager::removeTcpConnect(QString ip)
{// 移除该tcp连接_impl->_tcpSocketList.remove(ip);
}
#include "networkmanager.moc"

总结

这个服务端的网络通信代码,虽然实现了功能,但是还有以下不足:

1、收发消息是串行的。如果多台客户端连接,会将每个客户端的ip以及socket存入map,收发消息都是主线程在做,如果多个同时来消息,其实是串行处理的。

2、没有做心跳保持。如果客户端断线,服务器并不知道。

改进方案:多线程tcp服务器

将通信交给子线程去做,实现并行消息收发,并且在子线程内加上心跳保持。

实现方案:

封装TcpServer类,重写TcpServerincomingConnection方法,每当有新的连接进来,会自动调用这个函数,我们重写这个函数,在这个函数中进行子线程的创建,使用子线程进行通信。
将子线程与通信的标识符,存在一个map中,方便对多个客户端通信。

重写Thread,封装为TcpThread,重写run函数,在里面初始化该线程的socket,并连接相应的收发消息信号和槽函数。

重写TcpSocket类,主要是在内部实现,收发消息信号的转发和处理。

在这里插入图片描述

代码实现(服务端)

networkmanager.h

#include "mytcpsocket.h"
#include "tcpthread.h"
#include <QtWidgets/QWidget>
#include <QUdpSocket>
#include <QTcpServer>
#include <QTcpSocket>
#include <QDebug>
#include <QByteArray>
#include <QMap>
#include <QString>
#include <QJsonDocument>
#include <QJsonObject>
class NetworkManager : public QWidget
{Q_OBJECT
public:NetworkManager(QWidget* parent = nullptr);void tcpSendCommand(QString ip, QString Command);//tcp发送指令void removeTcpConnect(QString ip);
signals:void signalProjectData(QString ip, const QJsonObject& jsonObject);void signalPerformanceData(QString ip, const QJsonObject& jsonObject);void signalStatusData(QString ip, const QJsonObject& jsonObject);void signalDisconnect(QString ip);
private:class Impl;std::unique_ptr<Impl> _impl = nullptr;
};

networkmanager.cpp

#include "networkmanager.h"
class NetworkManager::Impl : public QObject
{Q_OBJECT
public:explicit Impl(NetworkManager* obj);~Impl() final;void udpStartListening();//udp开始监听void tcpStartListening();// tcp开始监听
public:NetworkManager* _self = nullptr;QUdpSocket* _udpSocket = nullptr;MyTcpServer* _tcpServer = nullptr;QMap<QString, qintptr> _tcpList;//tcp连接列表  ip和描述符
public slots:void slotUdpReadData(); void slotTcpProcessConnection(QString ip, qintptr sockDesc);void slotReadSocket(QString ip, qintptr sockDesc, const QByteArray& msg);void slotDisconnectSocket(QString ip, qintptr sockDesc);
};
NetworkManager::Impl::Impl(NetworkManager* obj) :_self(obj)
{}
NetworkManager::Impl::~Impl() = default;
NetworkManager::NetworkManager(QWidget* parent): QWidget(parent), _impl(std::make_unique<Impl>(this))
{_impl->_udpSocket = new QUdpSocket(this);_impl->_tcpServer = new MyTcpServer(this);// 将网络管理类设置为父对象_impl->tcpStartListening();_impl->udpStartListening();}
// tcp 开始监听
void NetworkManager::Impl::tcpStartListening()
{// 有新的连接 绑定连接处理函数connect(_tcpServer, &MyTcpServer::signalNewTcpConnect, this, &Impl::slotTcpProcessConnection);// 收到新的消息connect(_tcpServer, &MyTcpServer::signalTcpGetMsg, this, &Impl::slotReadSocket);// 客户端断连消息connect(_tcpServer, &MyTcpServer::signalTcpDisconnect, this, &Impl::slotDisconnectSocket);if (_tcpServer->listen(QHostAddress::Any, 5556)) { // 端口号qDebug() << "tcp Listening on port 5556";}else {qDebug() << "TCP Error" << "Failed to Listen to port 5556";}
}
// udp开始监听
void NetworkManager::Impl::udpStartListening()
{// 收到udp消息connect(_udpSocket, &QUdpSocket::readyRead, this, &Impl::slotUdpReadData);if (_udpSocket->bind(QHostAddress::Any, 5555)) { // 端口号qDebug() << "udp Listening on port 5555";}else {qDebug()<< "UDP Error"<< "Failed to bind to port 5555";}
}
// udp消息读取槽函数
void NetworkManager::Impl::slotUdpReadData() 
{while (_udpSocket->hasPendingDatagrams()) {QByteArray datagram;datagram.resize(_udpSocket->pendingDatagramSize());QHostAddress sender;quint16 senderPort;// 读取数据包_udpSocket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);// 回复响应消息QByteArray response = "udp responsed" ;_udpSocket->writeDatagram(response, QHostAddress(sender), senderPort);}
}
// tcp 连接处理
void NetworkManager::Impl::slotTcpProcessConnection(QString ip, qintptr sockDesc)
{qDebug() << "new connect:"<<ip;_tcpList.insert(ip, sockDesc);// 将ip地址和描述符存起来// ps: 此处业务逻辑。新的连接建立 主界面并不做响应  建立连接后客户端会发送消息 收到消息后主界面才做响应
}
// tcp读取消息槽函数
void NetworkManager::Impl::slotReadSocket(QString ip, qintptr sockDesc, const QByteArray& msg)
{QJsonDocument document = QJsonDocument::fromJson(msg);if (document.isNull()) {// 处理JSON解析错误qDebug() << "JSON is NULL";}else {if (document.isObject()) {QJsonObject jsonObject = document.object();// 根据key获取valueQString type = jsonObject["Type"].toString();if (type == "Project"){emit _self->signalProjectData(ip, jsonObject);}else if (type == "Performance"){emit _self->signalPerformanceData(ip, jsonObject);} else if (type == "Status"){emit _self->signalStatusData(ip, jsonObject);}}}
}
// 处理断开连接
void NetworkManager::Impl::slotDisconnectSocket(QString ip, qintptr sockDesc)
{qDebug() << "tcp disconnect:" << ip;// 发出信号  提示断连emit _self->signalDisconnect(ip);
}
// 通信 发送指令
void NetworkManager::tcpSendCommand(QString ip, QString Command)
{if (_impl->_tcpList.contains(ip)){// 发送指令qDebug() << "send command:" << Command << " to " << ip;_impl->_tcpServer->sendData(_impl->_tcpList[ip], Command.toUtf8());}else{qDebug() << "The ip address is not in the _tcpList. Unable to send message";}
}
void NetworkManager::removeTcpConnect(QString ip)
{// 移除该tcp连接_impl->_tcpList.remove(ip);
}
#include "networkmanager.moc"

mytcpserver.h

#pragma once
#include "mytcpsocket.h"
#include "tcpthread.h"
#include "networkmanager.h"
#include <QObject>
#include <QMap>
#include <QTcpServer>
class MyTcpServer :public QTcpServer
{Q_OBJECT
public:explicit MyTcpServer(QObject* parent = nullptr);void sendData(qintptr sockDesc, const QByteArray& msg);void slotTcpDisconnect(QString ip, qintptr sockDesc);
signals:void signalNewTcpConnect(QString ip, qintptr sockDesc);// 有新的tcp连接void signalTcpGetMsg(QString ip, qintptr Desc, const QByteArray& msg);// 收到新的消息void signalTcpDisconnect(QString ip, qintptr sockDesc);// tcp断连 传给网络管理类
private:void incomingConnection(qintptr sockDesc) override;QMap<qintptr, TcpThread*> _socketMap;//用来保存每一个客户端的通信线程
};

mytcpserver.cpp

重写了incomingConnection方法

#include "mytcpserver.h"
MyTcpServer::MyTcpServer(QObject* parent) : QTcpServer(parent)
{}
//当有新的连接进来的时候会自动调用这个函数,不需要你去绑定信号槽
void MyTcpServer::incomingConnection(qintptr sockDesc)
{// 用于注册一个类型,使其可以被Qt的元系统识别(不写这个,线程通信会报错)qRegisterMetaType<qintptr>("qintptr");//产生线程用于通信TcpThread* tcpthread = new TcpThread(sockDesc);// 转移tcpthread->moveToThread(tcpthread);//将标识符和对应的线程 存在map中_socketMap.insert(sockDesc, tcpthread);//将新的tcp连接的ip发送给网络管理类connect(tcpthread, &TcpThread::signalSendIp, this, &MyTcpServer::signalNewTcpConnect);// 底层socket收到消息时触发readyread,通过signalGetMsg将消息传给线程// 线程通过signalThreadGetMsg将消息转发给tcpserver// tcpserver通过signalTcpGetMsg再将消息转发给网络管理类connect(tcpthread, &TcpThread::signalThreadGetMsg, this, &MyTcpServer::signalTcpGetMsg);//线程中发出断开tcp连接,传给网络管理类,tcp断开连接connect(tcpthread, &TcpThread::signalTcpDisconnect, this, &MyTcpServer::slotTcpDisconnect);// 开启线程tcpthread->start();
}
// 发送消息
void MyTcpServer::sendData(qintptr sockDesc, const QByteArray& msg)
{if(_socketMap.contains(sockDesc)){// 触发对应线程发送消息emit _socketMap[sockDesc]->signalSendData(sockDesc, msg);}
}
void MyTcpServer::slotTcpDisconnect(QString ip, qintptr sockDesc)
{_socketMap.remove(sockDesc); // 移除emit signalTcpDisconnect(ip, sockDesc); // 发信号告诉网络管理类 tcp断连
}

tcpthread.h

#pragma once
#include "mytcpsocket.h"
#include <QThread>
#include <QTimer>
#include <QJsonDocument>
#include <QJsonObject>
#include <QHostAddress>
class TcpThread :public QThread
{Q_OBJECT
public://构造函数初始化套接字标识符explicit TcpThread(qintptr sockDesc, QObject* parent = nullptr);~TcpThread();void run() override;
signals:void signalTcpDisconnect(QString ip, qintptr sockDesc);// 线程给tcpserver发信号 tcp断连 void signalSendData(qintptr Desc, const QByteArray& msg); // 线程发送消息void signalThreadGetMsg(QString ip, qintptr Desc, const QByteArray& msg); // 线程收到消息void signalSendIp(QString ip, qintptr Desc); // 线程给tcpserver发信号 有新的连接(主要是发ip)public  slots:void slotSendData(qintptr Desc, const QByteArray& msg);void slotReceiveData(qintptr Desc, const QByteArray& msg);void slotDisconnect();void slotSendHeartbeat();
private:qintptr _socketDesc;QString _ip;QByteArray _imgData;// 用于大文件组包MyTcpSocket* _socket = nullptr;QTimer* _heartbeatTimer;// 心跳包发送间隔int _checkTime = 5;// 控制心跳断联时间
};

tcpthread.cpp
ps:这个里面的实现逻辑需要注意以下,在run中,主要是四个操作:收消息、发消息、断连处理、心跳保持
由于我这里收到的消息都是json包(和客户端约定好的格式),所以我拿收到的消息解析一下json,判断一下字段,即可知道是不是心跳包,如果是心跳包,我就直接对我的心跳计数更新,如果不是,那就说明是数据包,我就转发给主线程。
这个tcpthread类其实相当于mytcpsocket和mytcpserver的中间层,底层的mytcpsocket是真正完成收发消息的,tcpthread主要是负责管理这个socket的线程,它主要做的就是将收到的消息转发出去,以及接收外界的发消息指令,将指令转给socekt。
说明两个实现思路:

心跳保持:

我定义了一个计时器,每隔两秒我会发送一条心跳消息emit _socket->signalSocketSendMsg(this->_socketDesc, "heartbeat");
客户端接收到心跳消息后,也会回我一个心跳回复,是个json,里面type字段是heartbeat。我收到这个消息,这就完成了心跳保持。
但是代码里有个_checkTime是什么意思呢?
如果我没收到心跳回复,那么并不能立刻说明客户端掉线了,有的时候可能网络波动,或者别的什么原因。所以我们想给心跳保持加一个容错时间。
我设置了一个变量:_checkTime初始化=5(可根据情况自己定义)
当我发送一次心跳包,我就_checkTime--
我收到一次心跳包,我就_checkTime++
这样只要双方正常收发心跳,那么_checkTime就一直是5,如果没有收到客户端的回复,那么_checkTime就会一直减少。
而我在每次发送心跳包之前都会检查一下_checkTime,如果小于0了,那么说明客户端掉线了。也就是说,我给客户端留的冗余时间是10秒
容错时间=_checkTime*心跳包发送频率

大文件收发

我们的业务中,收发的都是json包,并且其中有的时候可能json包中有图片(编码后的图片)
这个图片比较大,可能64kb-170kb
tcpsocketreadyRead是有接收限制的(好像是最大可以接收64kb的数据,记不清了,可以去搜索一下)
这样的话图片一次就发不完,客户端需要多次发送。
一般来讲,这种大文件传输的情况正确的处理方法是:
客户端拆包,服务器组包。并且客户端发送之前需要先发送如文件名,文件大小等信息,客户端接收到后,再向客户端请求文件数据。
客户端接收到请求后,再发送文件数据,并且双方约定好每次传输的字节大小,接收完了之后,服务端以此为依据完成组包。
具体我就不赘述了,可以搜索别的去看。

我这里用的不是正常的处理方法,我这里取了个巧。只适用于我的业务场景。
由于我这里收发都是json包,所以我利用了json包的解析来完成了一个简易的组包。
具体实现思路:客户端不需要管,文件大不大,直接发就行,文件小的话一次就发了,文件大的话socket内部会自动帮你分为几次发送的。
而服务端接收到数据以后,拿去解析json,如果小文件,直接解析成功了。如果这次是大文件,一次没发完,那么就会解析失败。ok,关键就是这里,解析失败我就把这次的数据存起来,然后下次数据来了,由于不完整自然还会解析失败,我就累加上去(这也就相当于组包了),每次解析失败我都累加,并且累加完了我再次解析,如果解析成功了,那就说明发完了。那就正常转发消息即可,注意要清空用来组包的变量。
由此就可以完成一个简易的组包,这只适用于特殊情况下。

#include "tcpthread.h"
TcpThread::TcpThread(qintptr sockDesc, QObject* parent) : QThread(parent)
{this->_socketDesc = sockDesc;
}
void TcpThread::run()
{// 打印线程id  测试用/*qDebug() << "run:";qDebug() << "Current thread ID:" << QThread::currentThreadId();*/_socket = new MyTcpSocket(this->_socketDesc);//绑定套接字标识符绑定给自定义套接字对象_socket->setSocketDescriptor(this->_socketDesc);// 拿到ip地址QString ipHostAddress = _socket->peerAddress().toString();_ip = QHostAddress(ipHostAddress.mid(7)).toString();// 告诉tcpserver 有新的连接建立 并将ip传递出去emit signalSendIp(_ip, this->_socketDesc);// 收到消息// socket发出有消息的信号,然后触发线程中发出有消息的信号connect(_socket, &MyTcpSocket::signalSocketGetMsg, this, &TcpThread::slotReceiveData);// 发送消息// 当线程收到sendData信号时候,通知socket发送消息connect(this, &TcpThread::signalSendData, _socket, &MyTcpSocket::slotSendData);// 断开连接// 当套接字断开时,发送底层的disconnected信号connect(_socket, &MyTcpSocket::disconnected, this, &TcpThread::slotDisconnect);// 心跳连接_heartbeatTimer = new QTimer(this);connect(_heartbeatTimer, &QTimer::timeout, this, &TcpThread::slotSendHeartbeat);_heartbeatTimer->start(2*1000); // 2秒发送一次心跳this->exec();//在Qt中启动消息机制
}
TcpThread::~TcpThread()
{delete  _socket;
}
// 发送消息
void TcpThread::slotSendData(qintptr Desc, const QByteArray& msg)
{// 触发socket发送消息的信号 将消息传递进去if (_socket){emit _socket->signalSocketSendMsg(Desc, msg);}
}
// 收到消息
void TcpThread::slotReceiveData(qintptr Desc, const QByteArray& msg)
{// 解析一下 QJsonDocument document = QJsonDocument::fromJson(msg);if (document.isNull()) { // 如果是大数据(如图片) 可能一次发不完 需要进行合包// 处理JSON解析错误qDebug() << "JSON is NULL(Thread)";_imgData += msg;// 累加合包QJsonDocument imgDocument = QJsonDocument::fromJson(_imgData);if (!imgDocument.isNull()) {if (imgDocument.isObject()) {emit signalThreadGetMsg(_ip, Desc, _imgData);// 转发出去qDebug() << "QByteArray:img data size" << _imgData.size();_imgData.clear();// 清除}}}else { //不是大文件 一次可以接收完if (document.isObject()) {QJsonObject jsonObject = document.object();QString type = jsonObject["Type"].toString();if (type == "Heartbeat") // 如果是心跳包 直接拦截 {_checkTime++; // 是心跳回复 计数加1}else{// 如果不是心跳包 说明是数据包 转发出去emit signalThreadGetMsg(_ip, Desc, msg);}}}
}
// 断开连接
void TcpThread::slotDisconnect()
{// 告诉tcpserver tcp断联emit signalTcpDisconnect(_ip, this->_socketDesc);//让该线程中的套接字断开连接_socket->disconnectFromHost();//断开连接//线程退出qDebug() << "thread quit";this->quit();
}
// 心跳保持
void TcpThread::slotSendHeartbeat()
{qDebug() << "heartbeat-----------"<<_checkTime;if(_checkTime <= 0) // 检查心跳计时{// 客户端掉线qDebug() << "heartbeat:client disconnect";_socket->disconnectFromHost();//断开连接  执行这个会触发socket的disconnect//线程退出qDebug() << "thread quit";this->quit();}else{// 发送心跳包emit _socket->signalSocketSendMsg(this->_socketDesc, "heartbeat");_checkTime--;}
}      

mytcpsocket.h

#pragma once
#include <QObject>
#include <QThread>
#include <QTcpSocket>
class MyTcpSocket :public QTcpSocket
{Q_OBJECT
public:explicit MyTcpSocket(qintptr socketDesc, QObject* parent = nullptr);
public slots:void slotSendData(qintptr socketDesc, const QByteArray& data);
signals:void signalSocketSendMsg(qintptr socketDesc, const QByteArray& msg);// 发送消息void signalSocketGetMsg(qintptr socketDesc, const QByteArray& msg); // 接收消息
private:qintptr _sockDesc;
};

mytcpsocket.cpp

#include "mytcpsocket.h"
MyTcpSocket::MyTcpSocket(qintptr socketDesc, QObject* parent) : QTcpSocket(parent)
{this->_sockDesc = socketDesc;// 线程内触发socket的发送消息信号 则执行对应发送消息的槽函数connect(this, &MyTcpSocket::signalSocketSendMsg, this, &MyTcpSocket::slotSendData);// 收到消息  传递出去connect(this, &MyTcpSocket::readyRead, this, [=] {QByteArray msg = readAll();emit signalSocketGetMsg(_sockDesc, msg);});
}
// 发送消息
void MyTcpSocket::slotSendData(qintptr socketDesc, const QByteArray& msg)
{if (socketDesc == _sockDesc && !msg.isEmpty()) {this->write(msg);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/414793.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

书法图片自动扣字的批处理

本程序会根据原文字图片&#xff0c;自动扣字并生成黑字、红字2个透明的png图片&#xff0c;原图片黑字或白字均可。运行的话需要先安装好 ImageMagick-7.1.1-37 用法与生成效果举例&#xff1a; a.jpg 白字 转 黑、红扣字png: b.jpg 黑字 转 黑、红扣字png: 分享脚本如下: …

Spring MVC 八股文

目录 重点 SpringMVC的工作原理 Spring MVC 拦截器 Spring MVC 的拦截器和 Filter 过滤器有什么差别&#xff1f; 基础 什么是SpringMVC SpringMVC的优点 Spring MVC的核心组件 Spring MVC的常用注解由有哪些 Controller 注解有什么用 重点 SpringMVC的工作原理 1、客…

OLED显示屏详解(IIC协议0.96寸 STM32)

目录 一、介绍 二、模块原理 1.原理图 2.工作原理&#xff1a;SSD1306显存与命令 三、程序设计 main.c文件 oled.h文件 oled.c文件 四、实验效果 五、资料获取 项目分享 一、介绍 OLED是有机发光二极管&#xff0c;又称为有机电激光显示&#xff08;Organic Electrol…

U-Mail垃圾邮件网关:一站式邮件安全防护方案

在当今的数字化时代&#xff0c;电子邮件已成为企业日常运营中不可或缺的通讯工具。然而&#xff0c;随着电子邮件的广泛应用&#xff0c;垃圾邮件也日益成为困扰企业的一大难题。如何有效防止垃圾邮件入侵&#xff0c;确保企业邮件系统的安全稳定运行&#xff0c;已成为众多企…

Python进阶08-爬虫

零、文章目录 Python进阶08-爬虫 1、爬虫介绍 &#xff08;1&#xff09;爬虫是什么 **网络爬虫:**又被称为网页蜘蛛&#xff0c;网络机器人&#xff0c;是一种按照一定的规则&#xff0c;自动地抓取网络信息的程序或者脚本&#xff0c;另外一些不常使用的名字还有蚂蚁、自…

挂载磁盘时有多个文件系统

mount: /opt/storage/data1/: more filesystems detected on /dev/md5; use -t or wipefs(8). 1、解决方法一 mount -t ext4 /dev/md5 /opt/data2、解决方法二 #返回磁盘有那些文件系统和格式 wipefs /dev/md5 #清除文件系统和元数据 wipefs -a -f /dev/md5 #再次查看将没有任…

算法导论 总结索引 | 第五部分 第二十三章:最小生成树

需要将多个组件的针脚 连接在一起。要连接n个针脚&#xff0c;可以使用 n-1 根连线&#xff0c;每根连线连接两个针脚。很显然&#xff0c;希望所使用的连线长度最短 用一个连通无向图 G (V, E) 来予以表示&#xff0c;这里的 V 是针脚的集合&#xff0c;E 是针脚之间的可能连…

华为网络工程师证书等级有哪些?怎么备考?

华为网络工程师是由华为技术厂商推出的一系列网络工程师认证&#xff0c;其主要目的就是为了培养了验证网络工程师在华为技术以及解决方案方面的拥有一定的专业知识及技能&#xff0c;该证书分为多个等级&#xff0c;涵盖了不同网络领域及技术&#xff0c;也为众多的网络工程师…

spring security 自定义图形验证码(web/前后端分离)

一、准备工作 1.1 导入pom 所需依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.3</version><!-- <version>2.7.18</version>-->&l…

微信小程序知识点(一)

1.条件判断&#xff1a; wx:if&#xff0c;wx:elif&#xff0c;wx:else 和Hidden的区别 wx:if等是动态实现组件的&#xff0c;符合条件&#xff0c;页面上就新增一个组件&#xff0c;不符合&#xff0c;就会在也页面上加载&#xff0c;而Hidden只是控制页面的组件的显示与否&…

CUDA 计算点集与点集之间的距离

文章目录 一、简介二、实现代码三、实现效果参考资料 一、简介 这里使用CUDA实现一种计算计算点集与点集之间的距离的方法&#xff0c;其思路很简单&#xff0c;就是计算每个点到另一个点集之间的最小距离&#xff0c;最终保存结果到一个数组中&#xff0c;通过这种方式可以快速…

海力士A-DIE颗粒内存条震撼发布:毁灭者星际战舰DDR5内存条登场

**海力士A-DIE颗粒内存条震撼发布&#xff1a;毁灭者星际战舰内存条登场** 近日&#xff0c;海力士正式发布了全新一代A-DIE颗粒内存条——毁灭者星际战舰DDR5 7200RGB电竞内存条。这款内存条凭借其卓越的性能和先进的技术&#xff0c;成为数码爱好者关注的焦点。 导语&#xf…

分类预测|基于麻雀优化核极限学习机的数据分类预测Matlab程序SSA-KELM 多特征输入多类别输出 含基础KELM

分类预测|基于麻雀优化核极限学习机的数据分类预测Matlab程序SSA-KELM 多特征输入多类别输出 含基础KELM 文章目录 前言分类预测|基于麻雀优化核极限学习机的数据分类预测Matlab程序SSA-KELM 多特征输入多类别输出 含基础KELM 一、SSA-KELM模型SSA-KELM 分类预测的详细原理和流…

剑侠情缘c#版(游戏源码+资源+工具+程序),百度云盘下载,大小1.68G

剑侠情缘c#版&#xff08;游戏源码资源工具程序&#xff09;&#xff0c;c#开发的&#xff0c;喜欢研究游戏的可以下载看看。亲测可进游戏。 剑侠情缘c#版&#xff08;游戏源码资源工具程序&#xff09;下载地址&#xff1a; 通过网盘分享的文件&#xff1a;【游戏】剑侠情缘c#…

U-Mail垃圾邮件过滤网关‍是如何过滤垃圾邮件的?

随着互联网的普及&#xff0c;垃圾邮件已经成为计算机网络安全的又一个公害。因此&#xff0c;反垃圾邮件已经成为互联网应用研究中一个重要课题。为了防止垃圾邮件首先要学会保护自己的邮件地址&#xff0c;避免在网上随意登记和使用邮件地址&#xff0c;预防垃圾邮件骚扰。其…

Mysql——高可用集群部署

目录 一、源码编译mysql 二、mysql的主从复制 2.1、主从复制 2.2、延迟复制 2.3、慢查询日志 2.4、MySQL的并行复制 三、MySQL半同步模式 四、mysql高可用组复制 五、mysql-router 六、mysql高可用MHA 七、为MHA添加VIP功能 一、源码编译mysql 1、安装依赖 [rootm…

Pyqt5高级技巧:多线程任务、窗体交互、常用控件介绍(含基础Demo)

目录 一、多线程任务和多窗体交互 二、增删改查Demo 三、UI设计 【css效果代码对照表】 【实现效果】 【实现代码】 【常见问题】 Q1&#xff1a;工具栏怎么加&#xff0c;资源图片怎么加 Q2&#xff1a;控件被背景染色怎么办&#xff1f; Q3&#xff1a;QTdesigner有…

磐石云语音识别引擎

磐石云发布了V1.2.2版本语音识别引擎。 经过严格客观的测试识别效果和阿里云、讯飞、火山进行了对比几乎无差。&#xff08;欢迎对比测试&#xff09; 上图是CPU下的流式识别效果 RTF0.1~0.14,也就是一并发一个小时大约处理7~10小时&#xff0c;这取决于硬件的配置&#xff0…

MathType常见问题汇总

文章目录 MathType常见问题汇总一、如何将MathType内嵌到WPS工具栏中&#xff1f;二、在word中&#xff0c;如何批量修改所有MathType公式的字体以及大小格式&#xff1f;三、如何解决插入MathType公式后的行间距发生改变&#xff1f;参考 MathType常见问题汇总 一、如何将Mat…

Altium designer设计经验谈——常用规则的使用(二)

文章目录 前言三、规则设置介绍——走线规则1、Routing——>Width 线宽2、Routing——>Topology 拓扑 四、规则设置介绍——平面层规则1、Plane——>电源层连接样式 Power Plane Connect Style2、Plane——>电源层间距距离 Power Plane Clearance3、Plane——>多…