1.头文件
#ifndef TCPTOOL_H
#define TCPTOOL_H
#include <QObject>
#include <QTcpSocket>
#include <QHostAddress>
class TCPTool : public QObject
{
Q_OBJECT
//单例模式
private:
TCPTool();
public:
static TCPTool * GetInstance()
{
static TCPTool instance;
return &instance;
}
~TCPTool();
//变量
private:
bool m_exitThread;
QTcpSocket * m_tcpClient;
bool m_isConnected;
//函数
public:
bool SendLargeData(QByteArray & block);
private:
void ProcessThread();
bool Connect(const QString & address,int port);
signals:
void TryConnectSignal();
public slots:
void Disconnected();
void ReadMessage();
void TryConnectSlot();
};
#endif // TCPTOOL_H
2.源代码
#include "tcptool.h"
#include <thread>
#include "setting.h"//设置头文件
TCPTool::TCPTool()
{
m_tcpClient = new QTcpSocket(this);
m_tcpClient->abort();//取消原有连接
connect(this,SIGNAL(TryConnectSignal()),this,SLOT(TryConnectSlot()));
connect(m_tcpClient,SIGNAL(readyRead()),this,SLOT(ReadMessage()));
connect(m_tcpClient,SIGNAL(disconnected()),this,SLOT(Disconnected()));
m_isConnected = false;
m_exitThread = false;
std::thread sendThread(&TCPTool::ProcessThread,this);
sendThread.detach();
}
TCPTool::~TCPTool()
{
Disconnected();
m_exitThread = true;
}
bool TCPTool::SendLargeData(QByteArray & block)
{
if(!m_isConnected)
return false;
//分包发送
const int PayloadSize = 64*1024;//一个帧数据包大小
int totalSize = block.size();
int bytesWritten = 0;
int bytesToWrite = totalSize;
while(bytesWritten<totalSize)
{
int startIdx = bytesWritten;
int length = std::min(PayloadSize,bytesToWrite);
if(startIdx+length>totalSize)
return false;
QByteArray smallBlock = block.mid(startIdx,length);
qint64 written = m_tcpClient->write(smallBlock);
bool success = m_tcpClient->waitForBytesWritten();
if(!success)//发送失败包时,停止发送
return false;
bytesWritten+=written;
bytesToWrite-=written;
}
m_tcpClient->flush();
return true;
}
void TCPTool::ProcessThread()
{
while(m_exitThread==false)
{
//重连尝试
if(!m_isConnected)
{
emit TryConnectSignal();
usleep(200000);//等待连接尝试
if(!m_isConnected)
{
usleep(5000000);//等待5秒重连
continue;
}
}
//执行发送(未展开,思路:大块数据从队列读出,然后在线程中执行同步发送)
//SendLargeData(data);
usleep(1000000);//等待1s
}
}
bool TCPTool::Connect(const QString & address, int port)
{
if(!m_tcpClient)
{
m_isConnected = false;
return false;
}
//直接读取状态,如果连接正常,则直接返回
if(m_tcpClient->state()== QAbstractSocket::ConnectedState)
{
if(m_tcpClient->isValid())
{
m_isConnected = true;
return true;
}
else
{
m_isConnected = false;
return false;
}
}
//尝试连接
m_tcpClient->abort();//取消原有连接
m_tcpClient->connectToHost(address,port);
if(m_tcpClient->waitForConnected(1000))
{
m_isConnected = true;
}
else
m_isConnected = false;
return m_isConnected;
}
void TCPTool::Disconnected()
{
m_isConnected = false;
if(!m_tcpClient)
return;
if(m_tcpClient->state()== QAbstractSocket::UnconnectedState||m_tcpClient->waitForDisconnected(1000))
return;
}
void TCPTool::ReadMessage()
{
QByteArray buf = m_tcpClient->readAll();//读取数据
}
void TCPTool::TryConnectSlot()
{
Connect(Setting_Server_IPAddress,Setting_Server_IPPort);
}
3.说明
3.1.自动重连
使用waitForConnected
时会有等待时间,如果放在主线程中,会造成卡顿。定时器也不能执行等待(一般情况定时器运行在主线程),因此选择在线程中执行重复连接。
注意:如果在线程中执行Connect
函数时,会引起:
QObject: Cannot create children for a parent that is in a different thread. (Parent is QTcpSocket(0x142f1860), parent’s thread is QThread(0xbbac10), current thread is QThread(0x7fff18001040)
QObject::startTimer: Timers can only be used with threads started with QThread QObject: Cannot create children for a parent that is in a different thread. (Parent is QTcpSocket(0x142f1860), parent’s thread is QThread(0xbbac10), current thread is QThread(0x7fff18001040)
经测试,在线程中调用connectToHost
会引起如上问题。使得发送、接收信号都停留在子线程中,只有当子线程exec()之后才释放信号,从而引起接收不到信息(未触发readyRead)和发送不出去信息(write后没有立即发送出去)
。
因此代码中使用信号、槽
执行重新连接尝试。发送信号后,等待片刻读取执行结果。详见代码中示例。
3.2.发送大数据包
执行write()
时,只能发送一个小的数据包(与系统相关),大的数据包需要进行拆分才能进行发送。本文中在线程中使用同步方式发送大数据包,在线程中write()
和waitForBytesWritten()
配合即可完成同步方式发送。
以上代码经测试基本功能完善,仅供参考。