QT实现TCP客户端(断线重连,自动连接)与服务端的实现

1,918 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

TCP原理

TCP是一种面向连接的、可靠的、基于字节流的通信协议。(相比于UDP的尽最大努力交付来说,TCP相对较为可靠。此处不得不提的一点是,UDP是报文传输,而TCP是字节流传输,在实际的使用过程中注意,如果TCP协议发送过快,可能会出现粘包现象,本人就遇到过,当时需求是20ms发送一次,导致了严重的粘包现象,处理方式不难,此处只是简单举例,关于粘包问题在本博客中不详细说明,有需要了解的可以在评论中提出。)所谓面向连接,就是数据在传输前要建立连接,然后进行数据数据传输,传输完毕后还要断开连接。

  客户端在收发数据前要使用 connect() 函数和服务器建立连接。建立连接的目的是保证IP地址、端口、物理链路等正确无误,为数据的传输开辟通道。套接字十一全双工方式工作的,也就是说,它可以双向传递数据。因此收发数据前需要做一些准备。下面废话不多说,直接上代码。

先说一下客户端的代码,客户端实现相对较为繁琐一点,我在客户端代码实现了心跳包、重连机制,也就是丢失服务器之后自动重连。 项目结构如下

1.PNG 首先在.pro文件中需要加入network,其它的不需要改动

QT       += core gui network

下面是.h文件

#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QTcpSocket>
#include <QHostAddress>
#include <QString>
#include <QDebug>
#include <QTimer>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    void iniSocket();
    void connetcTcp();
private:
    Ui::Widget *ui;
    // 心跳定时器
    QTimer *timer_heart;
    // 重连定时器
    QTimer *timer_reConn;
    // 初始化连接定时器
    QTimer *timer_iniconn;
    // TCP对象
    QTcpSocket *TSocket;
    // 服务器IP
    QString ip="127.0.0.1";
    // 服务器端口
    quint16 port=3080;
    // 设置重连次数
    int count=1;
private slots:
    // 心跳数据包
    void doHeart();
    // 是否处于连接状态
    void isConnection();
    // 是否处于未连接状态
    void isDisConnection();
    // 失去心跳包重连
    void reConnection();
    void on_sendMes_clicked();
};
#endif // WIDGET_H

接下来就是客户端的cpp代码

#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    iniSocket();
    connect(TSocket,&QTcpSocket::readyRead,[=](){
        //从通信套接字中取出内容
        QByteArray array = TSocket->readAll();
        // 显示到文本框内
        ui->recerived->append(array);
    });
}
Widget::~Widget()
{
    delete ui;
}
void Widget::iniSocket()
{
    qDebug()<<"初始化客户端";
    timer_heart=new QTimer(this);
    timer_reConn=new QTimer(this);
    timer_iniconn=new QTimer(this);
    TSocket=new QTcpSocket(this);
    timer_heart->setInterval(15000);//设置定时器间隔15秒发送心跳包
    connect(timer_heart,SIGNAL(timeout()),this,SLOT(doHeart()));
    connect(TSocket,SIGNAL(connected()),this,SLOT(isConnection()));//连接
    connect(TSocket,SIGNAL(disconnected()),this,SLOT(isDisConnection()));//断线
    connect(timer_iniconn,&QTimer::timeout,this,&Widget::connetcTcp);//初始化客户端未获得连接时,每两秒自动连接一次
    timer_iniconn->start(2000);
}
// 连接到TCP
void Widget::connetcTcp()
{
    // 未连接时,每隔两秒自动向指定服务器发送连接请求
    if(TSocket->state() == QAbstractSocket::UnconnectedState)
    {
        qDebug()<<"connect state"<<TSocket->state();
        TSocket->abort();
        TSocket->connectToHost(ip, port);
    }
    // 连接成功后关闭初始化连接定时器,后面的断线重连与此无关
    else if(TSocket->state() == QAbstractSocket::ConnectedState)
    {
        timer_iniconn->stop();
    }
}
// 发送心跳报文
void Widget::doHeart()
{
    qDebug()<<"时间到,发送心跳包";
    // 心跳包的格式报文
    QString heart="heartPackage$";
    QByteArray ba=heart.toLocal8Bit();
    TSocket->write(ba);
}

// 处于连接状态
void Widget::isConnection()
{
    qDebug()<<"connect sucess";
    count=1;
    timer_heart->start();//开始心跳
}
// 处于断开状态
void Widget::isDisConnection()
{
    count=1;
    qDebug()<<"*******断开*******";
    timer_heart->stop();//断开连接后心跳停止
    connect(timer_reConn,SIGNAL(timeout()),this,SLOT(reConnection()));
    timer_reConn->start(5000);//每5秒钟自动重连
}
// 断线重连
void Widget::reConnection()
{
    if(TSocket->state() == QAbstractSocket::UnconnectedState)
    {
        TSocket->abort();
        qDebug()<<"connect tring...("<<count++<<")";
        TSocket->connectToHost(ip,port);
    }
    else if(TSocket->state() == QAbstractSocket::ConnectedState)
    {
        timer_reConn->stop();//
    }
}
// 发送数据函数
void Widget::on_sendMes_clicked()
{
    // 读取文本框内的内容
    QString content=ui->send->toPlainText();
    // 转换编码格式
    QByteArray cont=content.toLocal8Bit();
    // 发送出去
    TSocket->write(cont);
}

然后是main文件

#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}

然后最后是客户端项目的UI界面

2.PNG

下面就是服务器端的,单独的两个项目,项目结构先发一下

3.PNG

然后是.h文件

#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QTcpServer>
#include <QTcpSocket>
#include <QDebug>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
private slots:
    void on_buttonSend_clicked();
private:
    Ui::Widget *ui;
    QTcpServer  *tcpServer;//监听套接字
    QTcpSocket  *tcpSocket;//通信套接字
};
#endif // WIDGET_H

接下来就是.cpp文件

#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    tcpServer = NULL;
    tcpSocket = NULL;
    //监听套接字   指定父对象的目的,让其自动回收空间
    tcpServer = new QTcpServer(this);
    // 监听端口号
    tcpServer->listen(QHostAddress::Any,3080);
    // 设置窗口显示标题
    setWindowTitle("服务器:3080");
    connect(tcpServer,&QTcpServer::newConnection,[=](){
        //取出建立好连接的的套接字
        tcpSocket = tcpServer->nextPendingConnection();
        //获取对方的IP和端口
        QString ip = tcpSocket->peerAddress().toString();
        qint16  port = tcpSocket->peerPort();
        QString temp = QString("[%1:%2]:成功连接").arg(ip).arg(port);
        ui->textEditRead->setText(temp);
        connect(tcpSocket,&QTcpSocket::readyRead,[=](){
            //从通信套接字中取出内容
            QByteArray array = tcpSocket->readAll();
            ui->textEditRead->append(array);
        });
    });
}
// 析构函数
Widget::~Widget()
{
    delete ui;
}
// 发送函数
void Widget::on_buttonSend_clicked()
{
    if(NULL == tcpSocket)
    {
        return ;
    }
    //获取编辑区的内容
    QString str = ui->textEditWrite->toPlainText();
    tcpSocket->write(str.toUtf8().data());
}

然后就是main

#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}

最后附上服务器的UI

4.PNG

在博客的最后,附上客户端与服务器的交互截图 首先,第一个是客户端开启,服务端未打开的时候,一直在自动连接服务器,结果如下

5.PNG 然后是,服务器开启,并到15秒时,发送心跳包的结果截图

6.PNG 接下来是双方通信,发送消息的结果截图。

7.PNG 最后是服务器端断开连接,客户端显示服务器断开并开始自动重连的结果,并在服务器重新开启后连接成功。

8.PNG 到这里就结束了,是不是没有想象的那么难!代码已经全部附上,直接粘贴即可运行,如有问题,评论留言,我看到会给大家回复。