zoukankan      html  css  js  c++  java
  • Qt

    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;
    }
    博客园文作者:Citrusliu 博文地址:https://www.cnblogs.com/citrus
  • 相关阅读:
    记一次proc_open没有开启心得感悟
    Nginx 502 Bad Gateway 的错误的解决方案
    Linux安装redis,启动配置不生效(指定启动加载配置文件)
    设置redis访问密码
    LNMP 多版本PHP同时运行
    ***总结:在linux下连接redis并进行命令行操作(设置redis密码)
    设计模式(一)单例模式:3-静态内部类模式(Holder)
    设计模式(一)单例模式:2-懒汉模式(Lazy)
    设计模式(一)单例模式:1-饿汉模式(Eager)
    设计模式(一)单例模式:概述
  • 原文地址:https://www.cnblogs.com/citrus/p/11819065.html
Copyright © 2011-2022 走看看