QTcpClient/QTcpServer示例

前言:Qt的tcp模块基于事件驱动已经比较成熟,但是QTcpScoket的状态检测有一些bug,无法检测到非正常掉线的情况,例如网线拔掉,此时状态仍然是连接,会导致不确定的逻辑错误。所以我们采用心跳包来避免这个问题,心跳包机制:客户端定时发送给服务器,然后服务器返回消息的包,超出心跳限制则判断断开了连接。网络传输数据有时候数据可能很大,所以我们需要序列化数据,然后传输,这样减少数据的大小。只需要两端按照同样的规则发送和接收即可。

数据结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#ifndef SENDSTRUCT_H
#define SENDSTRUCT_H
#include <QString>
#include <QByteArray>
#include <QDataStream>
class sendStruct
{
public:
explicit sendStruct(int Type,QString Description,QByteArray ByteData=QByteArray(0));
int Type;//用于区分发送的不同内容的数据,对应不同的解析方法 本例聊天 1 =文本 2=图片
QString Description;//发送内容的描述
QByteArray ByteData;//具体发送或者接受的内容,可以将所有基本类型int,char,vector,map等或者自定义的结构体通过
//QDataStream序列化到ByteData中,接收端同样的方法从QDataStream中解析出来原数据
sendStruct(){ Type=0; Description=""; ByteData=QByteArray(0);}
int size()
{
int size=0;
size=sizeof(int)+Description.size()*2+4+ByteData.size()+4;
//序列化后QString大小为原有大小乘以2加4,QByteArry序列化后大小为原始大小加4,QString为Unicode编码每个字符占两个字节,
//QString和QByteArry序列化过程中,首先序列化了本身大小的整形数据(qint32)到序列中,然后才是具体数据。
return size;
}
int size() const
{
int size=0;
size=sizeof(int)+Description.size()*2+4+ByteData.size()+4;
return size;
}
sendStruct &operator=(const sendStruct &other)
{
Type=other.Type;
Description=other.Description;
ByteData=QByteArray(other.ByteData);
return *this;
}
#ifndef QT_NO_DATASTREAM
friend QDataStream& operator <<(QDataStream& out,const sendStruct& senstruct)
{
out<<senstruct.Type
<<senstruct.Description
<<senstruct.ByteData;
return out;
}
friend QDataStream& operator >>(QDataStream& in,sendStruct& senstruct)
{
in>>senstruct.Type
>>senstruct.Description
>>senstruct.ByteData;
return in;
}
#endif
};


#endif // SENDSTRUCT_H

服务端

tcpserverconnection.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#ifndef TCPSERVERCONNECT_H
#define TCPSERVERCONNECT_H

#include <QObject>
#include <QtNetwork/QTcpServer>
#include <QtNetwork/QTcpSocket>
#include <QList>
class sendStruct;
class TcpServerConnect : public QObject
{
Q_OBJECT
public:
explicit TcpServerConnect(QObject *parent = nullptr);
private:
QTcpServer *m_server;
// QTcpSocket *m_tcpsocket;
QList<QTcpSocket*> m_clientlist;
bool m_isGetPartData;
int m_requestDataSize;
public slots:
void handleSendOutData(qintptr describe,const sendStruct&);
void handleGetRecieveData();
void handleNewConnection();
void handleDisconnection();
signals:
void CurStatusChanged(QString status);
void UpdateText(QString address,int msgtype,QByteArray data);
};


#endif // TCPSERVERCONNECT_H

tcpserverconnection.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#include "tcpserverconnect.h"
#include "sendstruct.h"
#include <QImage>
#include <QSize>
#include <QBuffer>

TcpServerConnect::TcpServerConnect(QObject *parent) : QObject(parent)
{

m_isGetPartData=false;
m_requestDataSize=0;
m_server=new QTcpServer(this);
connect(m_server,&QTcpServer::newConnection,this,&TcpServerConnect::handleNewConnection);

m_server->listen(QHostAddress::Any,6868);
}

void TcpServerConnect::handleSendOutData(qintptr describe,const sendStruct &data)
{
foreach (QTcpSocket *client, m_clientlist) {
if((!client)||(client->state()!=QAbstractSocket::ConnectedState)
||(client->socketDescriptor()==describe))
continue;
QDataStream out(client);
out<<data.size()<<data;//先发送了数据大小,在发送数据
client->flush();
/*把需要发送的数据封装在结构体里面发送*/
}

}

void TcpServerConnect::handleGetRecieveData()
{
QTcpSocket *m_tcpsocket=static_cast<QTcpSocket *> (sender());
if((!m_tcpsocket)||m_tcpsocket->state()!=QAbstractSocket::ConnectedState)
return;
if(m_isGetPartData==false){
if(m_tcpsocket->bytesAvailable()<sizeof(int))//先要得到数据的大小
return;
else
{
QDataStream in(m_tcpsocket);
in>>m_requestDataSize;//数据大小写入这个变量中
m_isGetPartData=true;//只获得了数据的大小,数据内容还未获得
}
}
if(m_isGetPartData==true){
if(m_tcpsocket->bytesAvailable()<m_requestDataSize)//判断是否数据接收完整了,不完整就返回等待下一次判断
return;
else
{
QDataStream in(m_tcpsocket);
sendStruct receiveData;
in>>receiveData;//接收到了发送端的数据
m_requestDataSize=0;//清空大小
m_isGetPartData=false;//清空标志
/*
数据接收成功,放置在receiveData中,可以做其他处理
doSomething(receiveData);
*/

if(receiveData.Type==1 ||receiveData.Type==2 ||receiveData.Type==3)


emit UpdateText( m_tcpsocket->localAddress().toString(),receiveData.Type,receiveData.ByteData);
handleSendOutData(m_tcpsocket->socketDescriptor(),receiveData);


}else if(receiveData.Type==100){ //心跳包
sendStruct sendtextData;
sendtextData.Type=100;
sendtextData.Description="This is a message of heartbeat from server.";
QDataStream stream(&sendtextData.ByteData,QIODevice::WriteOnly);
stream<<"";

QDataStream out(m_tcpsocket);
out<<sendtextData.size()<<sendtextData;//先发送了数据大小,在发送数据
m_tcpsocket->flush();

}
if(m_tcpsocket->bytesAvailable())//如果缓存区还存在数据,继续执行
handleGetRecieveData();
}
}
}
void TcpServerConnect::handleNewConnection()
{
QTcpServer *server=static_cast<QTcpServer*>(sender());
QTcpSocket *m_tcpsocket=server->nextPendingConnection();
m_clientlist.append(m_tcpsocket);
emit CurStatusChanged(QString::number(m_clientlist.count()));
if(m_tcpsocket){

connect(m_tcpsocket,&QTcpSocket::readyRead,this,&TcpServerConnect::handleGetRecieveData);
connect(m_tcpsocket,&QTcpSocket::disconnected,this,&TcpServerConnect::handleDisconnection);
}

}
void TcpServerConnect::handleDisconnection()
{
QTcpSocket * socket=static_cast<QTcpSocket*>(sender());
foreach (QTcpSocket *tempsocket, m_clientlist) {
if(tempsocket->socketDescriptor()==socket->socketDescriptor()){
m_clientlist.removeOne(tempsocket);
break;
}
}
emit CurStatusChanged(QString::number(m_clientlist.count()));
}

客户端

tcpclientconnection.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#ifndef TCPCLIENTCONNECT_H
#define TCPCLIENTCONNECT_H


#include <QObject>
#include <QtNetwork/QTcpSocket>
#include "sendstruct.h"
#include <QImage>
#include <QDateTime>
#include <QTimer>
const int MAX_HEARTBEAT_COUNT=100;
class sendStruct;
class TcpClientConnect : public QObject
{
Q_OBJECT
public:
explicit TcpClientConnect(QObject *parent = nullptr);

private:
QTimer *m_pHeartBeat; //心跳定时器
qint32 m_heartBeatCount; //心跳次数

QTcpSocket *m_tcpsocket;
bool m_isGetPartData;
qint32 m_requestDataSize;

public:
void sendText(QString msg);
void sendImage(const QImage& img);
void sendVideoImage(const QImage& img);


bool connectToServer();
bool disconnectFromServer();
public slots:
void handleSendOutData(const sendStruct&);
void handleGetRecieveData();
void handleSocketConnected();
void handleSocketDisconnected();
void handleSendHeartbeatMsg();

signals:
void GetStrMsg(QString str);
void GetImageMsg(QImage image);
void GetVideoImageMsg(QImage image);
};


#endif // TCPCLIENTCONNECT_H

tcpclientconnection.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
#include "tcpclientconnect.h"
#include <QHostAddress>
#include <QDataStream>
#include <QSize>
#include <QBuffer>
TcpClientConnect::TcpClientConnect(QObject *parent) : QObject(parent)
{
m_tcpsocket=new QTcpSocket(this);
m_tcpsocket->setSocketOption(QTcpSocket::LowDelayOption,0); //0关闭低延时
m_tcpsocket->setSocketOption(QTcpSocket::KeepAliveOption,0);//0关闭自带的心跳机制,然后实现自己的心跳机制
m_isGetPartData=false;
m_requestDataSize=0;
connect(m_tcpsocket,&QTcpSocket::readyRead,this,&TcpClientConnect::handleGetRecieveData);
connect(m_tcpsocket,&QTcpSocket::connected,this,&TcpClientConnect::handleSocketConnected);
connect(m_tcpsocket,&QTcpSocket::disconnected,this,&TcpClientConnect::handleSocketDisconnected);

m_pHeartBeat=new QTimer;
connect(m_pHeartBeat,&QTimer::timeout,this,&TcpClientConnect::handleSendHeartbeatMsg);

}

void TcpClientConnect::handleSendOutData(const sendStruct &data)
{
if((!m_tcpsocket)||m_tcpsocket->state()!=QAbstractSocket::ConnectedState)
return;
QDataStream out(m_tcpsocket);
out<<qint32(data.size())<<data;//先发送数据大小,在发送数据本身
m_tcpsocket->flush();
/*把需要发送的数据封装在结构体里面发送*/
}

void TcpClientConnect::handleGetRecieveData()
{
if((!m_tcpsocket)||m_tcpsocket->state()!=QAbstractSocket::ConnectedState)
return;
if(m_isGetPartData==false){
if(m_tcpsocket->bytesAvailable()<sizeof(int))//先接收数据的大小
return;
else
{
QDataStream in(m_tcpsocket);
in>>m_requestDataSize;//数据大小写入变量
m_isGetPartData=true;//设置标志,只接收到了数据大小,没接收到数据全部
}
}
if(m_isGetPartData==true){
if(m_tcpsocket->bytesAvailable()<m_requestDataSize)//判断是否接收到了完整的数据
return;
else
{
QDataStream in(m_tcpsocket);
sendStruct receiveData;
in>>receiveData;//接收到了数据
m_requestDataSize=0;//清空大小
m_isGetPartData=false;//清空标志
/*
数据接收成功,放置在receiveData中,可以做其他处理
doSomething(receiveData);
*/
// qDebug()<<"receiveData type"<<receiveData.Type;
// qDebug()<<"receiveData Description"<<receiveData.Description;
// qDebug()<<"receiveData ByteData"<<receiveData.ByteData;
if(receiveData.Type==1){
QString msg;
QDataStream textStream(&(receiveData.ByteData),QIODevice::ReadOnly);
textStream>>msg;
emit GetStrMsg(msg);

}
else if(receiveData.Type==2){
QImage image;
QBuffer buffer(&(receiveData.ByteData));
buffer.open(QIODevice::ReadOnly);
image.load(&buffer,"JPG");
// qDebug()<<image;
emit GetImageMsg(image);

}
else if(receiveData.Type==3){
QImage image;
QBuffer buffer(&(receiveData.ByteData));
buffer.open(QIODevice::ReadOnly);
image.load(&buffer,"JPG");
// qDebug()<<image;
emit GetVideoImageMsg(image);

}else if(receiveData.Type==100){ //接收到心跳包
qDebug()<<receiveData.Description;
m_heartBeatCount=0;
}
if(m_tcpsocket->bytesAvailable())//如果缓存区还存在数据,递归执行
handleGetRecieveData();
}
}
}

void TcpClientConnect::handleSocketConnected()
{

m_pHeartBeat->start(3*1000);
m_heartBeatCount=0;

}
void TcpClientConnect::handleSocketDisconnected()
{
m_pHeartBeat->stop();
m_heartBeatCount=MAX_HEARTBEAT_COUNT+1;
}

void TcpClientConnect::handleSendHeartbeatMsg()
{
if(m_heartBeatCount<MAX_HEARTBEAT_COUNT){ //没有超出心跳最大次数
sendStruct sendtextData;
sendtextData.Type=100;
sendtextData.Description="This is a heartbeat msg to server.";
QDataStream stream(&sendtextData.ByteData,QIODevice::WriteOnly);
stream<<QString("");
handleSendOutData(sendtextData);
m_heartBeatCount++;
}else{ //超出心跳最大此处,重连
disconnectFromServer();
connectToServer();
}

}

void TcpClientConnect::sendText(QString msg)
{
//发送文字
sendStruct sendtextData;
sendtextData.Type=1;
sendtextData.Description="this is text";
QDataStream pointStream(&sendtextData.ByteData,QIODevice::WriteOnly);
pointStream<<msg;
handleSendOutData(sendtextData);
}
void TcpClientConnect::sendImage(const QImage& img)
{
// 发送图片
sendStruct sendImageData;
sendImageData.Type=2;
sendImageData.Description=QString("this is image");
// QImage image(QSize(640,480),QImage::Format_RGB888);
// image.fill(Qt::red);
QBuffer buffur(&(sendImageData.ByteData));
buffur.open(QIODevice::ReadWrite);

img.save(&buffur,"JPG");
handleSendOutData(sendImageData);
}
void TcpClientConnect::sendVideoImage(const QImage& img)
{
// 发送图片
sendStruct sendImageData;
sendImageData.Type=3;
sendImageData.Description=QString("this is video image");
// QImage image(QSize(640,480),QImage::Format_RGB888);
// image.fill(Qt::red);
QBuffer buffur(&(sendImageData.ByteData));
buffur.open(QIODevice::ReadWrite);

img.save(&buffur,"JPG");
handleSendOutData(sendImageData);
}

bool TcpClientConnect::connectToServer()
{
m_tcpsocket->connectToHost(QHostAddress("192.168.16.200"),6868);
if(m_tcpsocket->state()!=QTcpSocket::ConnectedState){
m_tcpsocket->waitForConnected(1500);
}
if(m_tcpsocket->state()==QTcpSocket::ConnectedState){
return true;
}else{
return false;
}

}

bool TcpClientConnect::disconnectFromServer()
{
m_tcpsocket->disconnectFromHost();
if(m_tcpsocket->state()==QTcpSocket::UnconnectedState){
return true;
}else{
return false;
}
}

QTcpClient/QTcpServer示例
http://yoursite.com/2019/03/25/QTcpClient-QTcpServer示例/
作者
还在输入
发布于
2019年3月25日
许可协议