Qt - 基于TCP的网络编程
TCP(传输控制协议 Transmission Control Protocol)
可靠、面向数据流、面向连接 的传输协议。(许多应用层协议都是以它为基础:HTTP、FTP)
使用情况:
相比UDP无连接,TCP是面向连接
相比UDP不可靠,TCP是可靠传输
相比UDP不提供流量控制,TCP是提供
相比UDP适用少量数据传输,TCP是大量数据
相比UDP速度快,TCP是慢
适用:对可靠性要求高的数据通信系统。
工作原理:
TCP客户端与服务器在正式发送数据前,双方需要经过三次握手后建立连接。
参考:https://blog.csdn.net/c571121319/article/details/82663170
TCP服务器编程 6 步: socket、bind、listen、accept、recv/send、close
1、创建socket套接字
2、设置socket属性
3、绑定socket相关信息(IP、地址)
4、监听来自客户端的连接请求
5、循环接受消息、发送消息(响应)
6、关闭socket套接字
TCP客户端编程 4 步: socket、connect、send/recv、close
1、创建socket套接字
2、向服务器发送连接请求
3、向服务器发送消息、接受消息(请求)
4、关闭socket套接字
工具:
使用Qt提供的网络模块QtNetwork(pro文件里面加network);
使用Qt提供的类QTcpSocket、QTcpServer。
代码:
tcpcommon.h(共同的头文件)
/*************************************************** * 作者:citrus * 日期:2020.05.22 * 描述:定义网络通讯中用到的 常量、数据结构(协议部分) ***************************************************/ #ifndef TCPCOMMON_H #define TCPCOMMON_H #include <QByteArray> #include <QIODevice> #include <QDataStream> //tcp连接参数 #define TCPCOMMON_SERVER_PORT 5566 //TCP服务器监听“起始”端口号 #define TCPCOMMON_SERVER_PORT_SPACE 10 //尝试监听、绑定、广播端口号间隔 #define TCPCOMMON_SOCKET_TIMEOUT 10*1000 //多长时间没有收到客户端信息说明已经断开 #define TCPCOMMON_SHAKE_HAND_ENABLED false //packet结构 //包头(1) Head + 包长度(1) Len + 包数据类型(1) DataType + 包数据 Data + 包尾(1) End #define TCPCOMMON_PACKET_LEN_BYTE 1 //包长度占几个字节 #define TCPCOMMON_PACKET_DATATYPE_BYTE 1 //包数据类型占几个字节 #define TCPCOMMON_PACKET_HEAD_BYTE 1 //包头占几个字节 #define TCPCOMMON_PACKET_END_BYTE 1 //包尾占几个字节 //包校验 #define TCPCOMMON_PACKET_HEAD 0xAA //包头校验值 #define TCPCOMMON_PACKET_END 0xFF //包尾校验值 //包中数据类型 #define TCPCOMMON_DATATYPE_CAN52 0x52 //DataType为 0x52 #define TCPCOMMON_DATATYPE_CAN53 0x53 //DataType为 0x53 /************************************************** * 备注:以下为具体数据---后期可分离到protocol类中 ***************************************************/ //用于存储接收到的包 class CacheData { public: long len; //单条数据长度 QByteArray dataType;//单条数据类型 QByteArray data; //总数据+单条数据 CacheData() { len = -1; } void clear() { len = -1; dataType.clear(); data.clear(); } }; //DataType为 0x52 class Can52 { public: quint16 m_steeringWheelAngle; //方向盘角度 quint8 m_steeringWheelAngleSpeed; //方向盘转角加减速度 quint16 m_acceleratorPedal; //油门踏板开度 quint16 m_gear; //挡位信息 //写-指针 void write(QByteArray *data) { QDataStream streamWriter(data, QIODevice::WriteOnly); streamWriter.setVersion(QDataStream::Qt_5_6); streamWriter << m_steeringWheelAngle; streamWriter << m_steeringWheelAngleSpeed; streamWriter << m_acceleratorPedal; streamWriter << m_gear; } //读-引用 void read(QByteArray &data) { QDataStream streamReader(&data, QIODevice::ReadOnly); streamReader.setVersion(QDataStream::Qt_5_6); streamReader >> m_steeringWheelAngle; streamReader >> m_steeringWheelAngleSpeed; streamReader >> m_acceleratorPedal; streamReader >> m_gear; } }; //DataType为 0x53 class Can53 { public: quint16 m_speed; //车速度 quint16 m_angle; //转角信号 quint8 m_angleSpeed; //转向角速度信号 //写-指针 void write(QByteArray *data) { QDataStream streamWriter(data, QIODevice::WriteOnly); streamWriter.setVersion(QDataStream::Qt_5_6); streamWriter << m_speed; streamWriter << m_angle; streamWriter << m_angleSpeed; } //读-引用 void read(QByteArray &data) { QDataStream streamReader(&data, QIODevice::ReadOnly); streamReader.setVersion(QDataStream::Qt_5_6); streamReader >> m_speed; streamReader >> m_angle; streamReader >> m_angleSpeed; } }; class NetText { public: QString m_text; //写数据到类数据成员中 void write(QByteArray *data) { QDataStream streamWriter(data, QIODevice::WriteOnly); streamWriter.setVersion(QDataStream::Qt_5_6); streamWriter << m_text; } //读数据到类数据成员中 void read(QByteArray &data) { QDataStream streamReader(&data, QIODevice::ReadOnly); streamReader.setVersion(QDataStream::Qt_5_6); streamReader >> m_text; } }; #endif // TCPCOMMON_H
tcpserver.h(服务器头文件)
#ifndef TCPSERVER_H #define TCPSERVER_H #include <QObject> #include <QTimer> #include <QTcpServer> //监听套接字 #include <QTcpSocket> //通信套接字 #include "tcpcommon.h" class TcpServer : public QObject { Q_OBJECT public: ~TcpServer(); static TcpServer *getInstance(); bool readCacheData(CacheData &cacheData); protected: explicit TcpServer(QObject *parent = nullptr); signals: void parseDataSignal(QByteArray dataType, QByteArray data); public slots: void getNewConnection(); void disconnectClient(); void readData(); void writeData(QByteArray dataType, QByteArray data); private: QTcpServer *m_server; //监听套接字 QTcpSocket *m_socket; //通信套接字 //缓存未处理的数据,处理粘包 CacheData m_cacheData; //接收到握手信号在一定间隔时间再判断是否还能接收到 QTimer m_timerDiagnose; }; #endif // TCPSERVER_H
tcpserver.cpp(服务器源文件)
#include "tcpserver.h" TcpServer::TcpServer(QObject *parent) : QObject(parent) { m_server = nullptr; m_socket = nullptr; //监听套接字 m_server = new QTcpServer(this); //指定父对象,自动回收指针 //绑定端口和监听 if(m_server->listen(QHostAddress::Any, 5566)) { qDebug() << "TcpServer port=" << 5566; //建立连接 connect(m_server, SIGNAL(newConnection()), this, SLOT(getNewConnection())); } } TcpServer::~TcpServer() { //主动和客户端断开连接 m_socket->disconnectFromHost(); //关闭socket m_socket->close(); m_server->close(); } //使用单例模式 TcpServer *TcpServer::getInstance() { static TcpServer instance; return &instance; } //处理一个新的客户端连接 void TcpServer::getNewConnection() { //取出与客户端连接的socket m_socket = m_server->nextPendingConnection(); //获取客户端的 addr地址 port端口 QString clientAddr = m_socket->peerAddress().toString(); quint16 clientPort = m_socket->peerPort(); qDebug() << "TcpServer::getNewConnection: " << clientAddr << ":" << clientPort; connect(m_socket, SIGNAL(disconnected()), this, SLOT(disconnectClient())); connect(m_socket, SIGNAL(readyRead()), this, SLOT(readData())); //NOTE 测试 connect(this, SIGNAL(parseDataSignal(QByteArray, QByteArray)), this, SLOT(writeData(QByteArray, QByteArray))); } void TcpServer::disconnectClient() { qDebug()<<"TcpServer::disconnectClient client is disconnect."; if(m_socket != nullptr) { m_socket->abort(); m_socket = nullptr; } } //发送数据 void TcpServer::writeData(QByteArray dataType, QByteArray data) { qDebug() << "TcpServer Receive dataType:" << dataType; qDebug() << "TcpServer Receive data:" << data; //包类型+包数据+包尾 QByteArray sendMessage = dataType + data + QByteArray::fromHex(QByteArray::number(TCPCOMMON_PACKET_END, 16)); int size = sendMessage.size(); QByteArray len = QByteArray::fromHex(QByteArray::number(size, 16)); qDebug() << "TcpServer len:" << len; sendMessage.insert(0, QByteArray::fromHex(QByteArray::number(TCPCOMMON_PACKET_HEAD, 16)) + len); qDebug() << "TcpServer sendMessage:" << sendMessage; qint64 res = m_socket->write(sendMessage); if (res <= 0) { qDebug() << "TcpServer Write m_socket error."; } } //接收数据 void TcpServer::readData() { qDebug() << "TcpServer::readData"; if(m_socket->bytesAvailable() <= 0) { return; } m_cacheData.data += this->m_socket->readAll(); bool flag = false; do { flag = readCacheData(m_cacheData); } while (flag); } //处理TCP粘包 bool TcpServer::readCacheData(CacheData &cacheData) { //校验包头 AA0801111111111111FF int pIndexStart = cacheData.data.indexOf(TCPCOMMON_PACKET_HEAD); if(pIndexStart < 0) { return false; } //截取从包头index_start到末尾的数据 cacheData.data = cacheData.data.mid(pIndexStart); CacheData tmpCacheData; tmpCacheData.data = cacheData.data; //删除包头 tmpCacheData.data.remove(0, TCPCOMMON_PACKET_HEAD_BYTE); qDebug() << tmpCacheData.data; //解析包长度 if(tmpCacheData.data.count() < TCPCOMMON_PACKET_LEN_BYTE) { return false; } bool flag; tmpCacheData.len = tmpCacheData.data.mid(0, TCPCOMMON_PACKET_LEN_BYTE).toHex().toLong(&flag); qDebug() << tmpCacheData.len; if(flag == false) { //删除包头 cacheData.data.remove(0, TCPCOMMON_PACKET_HEAD_BYTE); if(cacheData.data.indexOf(TCPCOMMON_PACKET_HEAD) >= 0) { //有可能出现粘包的情况,继续解析后面数据 return true; } else { return false; } } //数据达到一个完整包长度 tmpCacheData.data.remove(0, TCPCOMMON_PACKET_LEN_BYTE); if(tmpCacheData.len > tmpCacheData.data.count()) { return false; } //重置data长度,删除多余数据 tmpCacheData.data.resize(tmpCacheData.len); //校验包尾 if(tmpCacheData.data.endsWith(TCPCOMMON_PACKET_END) == false) { //删除包头 cacheData.data.remove(0, TCPCOMMON_PACKET_HEAD_BYTE); if(cacheData.data.indexOf(TCPCOMMON_PACKET_HEAD) >= 0) { //有可能出现粘包的情况,继续解析后面数据 return true; } else { return false; } } //删除包尾 tmpCacheData.data.resize(tmpCacheData.len - TCPCOMMON_PACKET_END_BYTE); //解析包数据类型 if(tmpCacheData.data.count() < TCPCOMMON_PACKET_DATATYPE_BYTE) { return false; } tmpCacheData.dataType = tmpCacheData.data.left(TCPCOMMON_PACKET_DATATYPE_BYTE); //删除包数据类型 tmpCacheData.data.remove(0, TCPCOMMON_PACKET_DATATYPE_BYTE); //解析出一条去头去尾的包数据 qDebug() << "TcpServer::readCacheData parseDataSignal:" << tmpCacheData.data; emit parseDataSignal(tmpCacheData.dataType, tmpCacheData.data); //删除当前包数据 cacheData.data.remove(0, TCPCOMMON_PACKET_HEAD_BYTE + TCPCOMMON_PACKET_LEN_BYTE + tmpCacheData.len); return true; }
tcpclient.h(客户端头文件)
#ifndef TCPCLIENT_H #define TCPCLIENT_H #include <QObject> #include <QTcpSocket> #include <QVariant> #include <QTimer> #include "tcpcommon.h" class TcpClient : public QObject { Q_OBJECT public: ~TcpClient(); static TcpClient* getInstance(QString addr, QString port); //使用单例模式 void start(); bool readCacheData(CacheData &cacheData); protected: //构造函数是保护型,此类使用单例模式 TcpClient(QString address, QString port); signals: void parseDataSignal(QByteArray dataType, QByteArray data); public slots: void connectServer(); void disconnectServer(); void reconnectServer(); void readData(); void writeData(QByteArray dataType, QByteArray data); private: QString m_hostAddr; //socket IP地址 QString m_hostPort; //socket 端口 QTcpSocket m_socket; //socket 收发数据 bool m_isConnected = false; QTimer m_timerReconnected; QTimer m_timerDiagnose; //TODO 接收到握手信号,过一段时间再判断是否还接收到 QAbstractSocket::SocketState m_socketState; //socket状态 //缓存未处理的数据,处理粘包 CacheData m_cacheData; }; #endif // TCPCLIENT_H
tcpclient.cpp(客户端源文件)
#include "tcpclient.h" TcpClient::TcpClient(QString addr, QString port) { this->m_hostAddr = addr; this->m_hostPort = port; qDebug() << "TcpClient address=" << m_hostAddr; qDebug() << "TcpClient port=" << m_hostPort; connect(&m_socket, SIGNAL(connected()), this, SLOT(connectServer())); connect(&m_socket, SIGNAL(disconnected()), this, SLOT(disconnectServer())); connect(&m_timerReconnected, SIGNAL(timeout()), this, SLOT(reconnectServer())); m_timerReconnected.start(2000); } TcpClient::~TcpClient() { m_socket.close(); } //使用单例模式 TcpClient* TcpClient::getInstance(QString addr, QString port) { static TcpClient instance(addr, port); return &instance; } void TcpClient::start() { qDebug() << "TcpClient try to connect server."; this->m_socket.connectToHost(this->m_hostAddr, this->m_hostPort.toUInt(), QTcpSocket::ReadWrite); this->m_socket.waitForConnected(); } //断网处理,判断socket连接状态 void TcpClient::reconnectServer() { switch (m_socket.state()) { case QAbstractSocket::UnconnectedState: //未连接状态 start(); break; case QAbstractSocket::ConnectedState: //连接状态 break; default: break; } } void TcpClient::connectServer() { qDebug() << "TcpClient is connected."; this->m_isConnected = true; connect(&m_socket, SIGNAL(readyRead()), this, SLOT(readData())); //NOTE 测试 connect(this, SIGNAL(parseDataSignal(QByteArray, QByteArray)), this, SLOT(writeData(QByteArray, QByteArray))); writeData(QByteArray::fromHex(QByteArray("01")), QByteArray::fromHex(QByteArray("111111111111"))); } void TcpClient::disconnectServer() { qDebug() << "TcpClient is disconnected."; this->m_isConnected = false; m_socket.close(); } //WARNING 发送数据:是否要转HEX void TcpClient::writeData(QByteArray dataType, QByteArray data) { qDebug() << "TcpClient Receive dataType:" << dataType; qDebug() << "TcpClient Receive data:" << data; //包类型+包数据+包尾 QByteArray sendMessage = dataType + data + QByteArray::fromHex(QByteArray::number(TCPCOMMON_PACKET_END, 16)); int size = sendMessage.size(); QByteArray len = QByteArray::fromHex(QByteArray::number(size, 16)); qDebug() << "TcpClient len:" << len; sendMessage.insert(0, QByteArray::fromHex(QByteArray::number(TCPCOMMON_PACKET_HEAD, 16)) + len); qDebug() << "TcpClient sendMessage:" << sendMessage; qint64 res = m_socket.write(sendMessage); if (res <= 0) { qDebug() << "TcpClient Write m_socket error."; } } //WARNING 接收数据:是否要转HEX void TcpClient::readData() { qDebug() << "TcpClient::readData"; if(m_socket.bytesAvailable() <= 0) { return; } m_cacheData.data += this->m_socket.readAll(); bool flag = false; do { flag = readCacheData(m_cacheData); } while (flag); } //处理TCP粘包 bool TcpClient::readCacheData(CacheData &cacheData) { //校验包头 AA0801111111111111FF int pIndexStart = cacheData.data.indexOf(TCPCOMMON_PACKET_HEAD); if(pIndexStart < 0) { return false; } //截取从包头index_start到末尾的数据 cacheData.data = cacheData.data.mid(pIndexStart); CacheData tmpCacheData; tmpCacheData.data = cacheData.data; //删除包头 tmpCacheData.data.remove(0, TCPCOMMON_PACKET_HEAD_BYTE); qDebug() << tmpCacheData.data; //解析包长度 if(tmpCacheData.data.count() < TCPCOMMON_PACKET_LEN_BYTE) { return false; } bool flag; tmpCacheData.len = tmpCacheData.data.mid(0, TCPCOMMON_PACKET_LEN_BYTE).toHex().toLong(&flag); qDebug() << tmpCacheData.len; if(flag == false) { //删除包头 cacheData.data.remove(0, TCPCOMMON_PACKET_HEAD_BYTE); if(cacheData.data.indexOf(TCPCOMMON_PACKET_HEAD) >= 0) { //有可能出现粘包的情况,继续解析后面数据 return true; } else { return false; } } //数据达到一个完整包长度 tmpCacheData.data.remove(0, TCPCOMMON_PACKET_LEN_BYTE); if(tmpCacheData.len > tmpCacheData.data.count()) { return false; } //重置data长度,删除多余数据 tmpCacheData.data.resize(tmpCacheData.len); //校验包尾 if(tmpCacheData.data.endsWith(TCPCOMMON_PACKET_END) == false) { //删除包头 cacheData.data.remove(0, TCPCOMMON_PACKET_HEAD_BYTE); if(cacheData.data.indexOf(TCPCOMMON_PACKET_HEAD) >= 0) { //有可能出现粘包的情况,继续解析后面数据 return true; } else { return false; } } //删除包尾 tmpCacheData.data.resize(tmpCacheData.len - TCPCOMMON_PACKET_END_BYTE); //解析包数据类型 if(tmpCacheData.data.count() < TCPCOMMON_PACKET_DATATYPE_BYTE) { return false; } tmpCacheData.dataType = tmpCacheData.data.left(TCPCOMMON_PACKET_DATATYPE_BYTE); tmpCacheData.data.remove(0, TCPCOMMON_PACKET_DATATYPE_BYTE); //解析出一条去头去尾的包数据 qDebug() << "TcpClient::readCacheData parseDataSignal:" << tmpCacheData.data; emit parseDataSignal(tmpCacheData.dataType, tmpCacheData.data); //删除当前包数据 cacheData.data.remove(0, TCPCOMMON_PACKET_HEAD_BYTE + TCPCOMMON_PACKET_LEN_BYTE + tmpCacheData.len); return true; }