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;
}

 

posted @ 2019-11-08 11:31  Citrusliu  阅读(529)  评论(0编辑  收藏  举报