首页 > 编程语言 >QT网络编程(二)——TCP协议工作原理及实战

QT网络编程(二)——TCP协议工作原理及实战

时间:2024-07-26 22:54:41浏览次数:17  
标签:QT 编程 TCP 连接 ui QTcpSocket MainWindow 客户端

目录

引言

一、TCP协议基础知识

1. TCP协议特点

2. TCP连接的三个阶段

3. 三次握手和四次挥手

二、Qt中的TCP编程

1. 引入Qt网络模块

2. QTcpServer 类

常用函数

3. QTcpSocket 类

常用函数

三、TCP网络通信流程

TCP服务器

TCP客户端

四、实战示例

UI界面

核心代码

运行结果

五、总结


引言

Qt 是一个跨平台的 C++ 图形界面开发库,它提供了丰富的模块来支持各种开发需求,其中网络编程是一个重要的部分。TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。在Qt中,通过QTcpSocket和QTcpServer类可以实现TCP通信。本文将详细介绍TCP协议的工作原理以及如何在Qt中进行TCP通信的实战。

一、TCP协议基础知识

1. TCP协议特点

  • 面向连接:TCP在数据传输之前需要建立连接,即进行三次握手。
  • 可靠性:TCP通过确认和重传机制确保数据的准确性和完整性。
  • 顺序性:TCP确保数据按照发送的顺序到达接收方。
  • 流量控制和拥塞控制:TCP通过滑动窗口机制和AIMD(Additive Increase Multiplicative Decrease)算法来控制数据流量和避免网络拥塞。

2. TCP连接的三个阶段

  1. 建立连接:客户端和服务器之间进行三次握手,确认彼此的身份并建立一个可靠的连接。
  2. 数据传输:数据被分成小的数据包,每个数据包都包含序列号,接收方根据序列号对数据包进行排序和重组。
  3. 连接终止:通过四次挥手关闭连接,释放资源。

3. 三次握手和四次挥手

  • 三次握手是TCP协议中用于建立连接的过程。以下是它的详细步骤:
  1. SYN(同步序列编号)发送
    • 客户端发送一个SYN包(SYN=j)到服务器,并进入SYN_SENT状态,等待服务器确认。
  2. SYN-ACK(同步序列编号确认)应答
    • 服务器收到SYN包后,发送一个SYN-ACK包(SYN=k, ACK=j+1)作为应答,表示对SYN包的确认,并发送自己的同步序列号。此时,服务器进入SYN_RCVD状态。
  3. ACK(确认)发送
    • 客户端收到服务器的SYN-ACK包后,发送一个ACK包(ACK=k+1)作为应答,表示对SYN-ACK包的确认。此时,TCP连接建立,客户端和服务器都进入ESTABLISHED状态,可以开始传输数据。
  • 四次挥手是TCP协议中用于关闭连接的过程。以下是它的详细步骤:
  1. FIN(结束)发送
    • 当一方(假设为客户端)想要关闭连接时,它会发送一个FIN包(FIN=M)给对方,并进入FIN_WAIT_1状态,等待对方的确认。
  2. ACK确认
    • 接收方(服务器)收到FIN包后,发送一个ACK包(ACK=M+1)作为确认,表示它已经收到了客户端的关闭请求。此时,客户端进入FIN_WAIT_2状态,等待服务器关闭连接。
  3. FIN发送
    • 服务器在关闭连接之前可能需要完成一些清理工作(如传输缓冲区中的数据),完成后,它也会发送一个FIN包(FIN=N)给客户端,并进入LAST_ACK状态,等待客户端的确认。
  4. ACK确认
    • 客户端收到服务器的FIN包后,发送一个ACK包(ACK=N+1)作为确认,表示它已经收到了服务器的关闭请求。此时,服务器收到ACK包后关闭连接,客户端在发送完ACK包后也会关闭连接。双方都进入了CLOSED状态,连接彻底关闭。

二、Qt中的TCP编程

1. 引入Qt网络模块

在使用Qt进行TCP编程之前,需要在项目的.pro文件中引入网络模块:

QT += network

2. QTcpServer 类

QTcpServer 类用于在服务器端监听端口,等待客户端的连接请求。当客户端连接时,QTcpServer 会创建一个新的 QTcpSocket 实例来处理这个连接。

常用函数
  • listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0): 在指定的地址和端口上开始监听连接。
  • waitForNewConnection(int msec = 0, bool *timedOut = nullptr): 等待新的连接到达,可设置超时时间。
  • nextPendingConnection(): 获取下一个已接受的连接的套接字(QTcpSocket)。

3. QTcpSocket 类

QTcpSocket 类用于在客户端发起连接,以及在连接建立后发送和接收数据。

常用函数
  • connectToHost(const QString &hostName, quint16 port, OpenMode openMode = ReadWrite): 连接到指定的主机和端口。
  • write(const char *data, qint64 maxSize): 向套接字写入数据。
  • readAll(): 读取所有可用的数据并返回一个 QByteArray 对象。
  • disconnectFromHost(): 断开与服务器的连接。

三、TCP网络通信流程

TCP服务器

  1. 创建QTcpServer对象。
    #include <QTcpServer>  
      
    QTcpServer *server = new QTcpServer(this);
  2. 调用listen()方法监听指定端口。
    bool ok = server->listen(QHostAddress::Any, 1234);  
    if (!ok) {  
        qDebug() << "无法启动服务器:" << server->errorString();  
    } else {  
        qDebug() << "服务器正在监听端口 1234...";  
    }
  3. 等待newConnection()信号的到来,表明有新的连接请求。
    //  通常,您会在类的构造函数或某个初始化函数中连接这个信号到槽函数。
    
    connect(server, &QTcpServer::newConnection, this, &YourClass::handleNewConnection);  
      
    // 槽函数  
    void YourClass::handleNewConnection() {  
        QTcpSocket *socket = server->nextPendingConnection();  
        connect(socket, &QTcpSocket::readyRead, this, &YourClass::readData);  
        // 可以在这里添加更多的socket连接处理,如断开连接处理  
        connect(socket, &QTcpSocket::disconnected, socket, &QTcpSocket::deleteLater);  
    }
  4. 调用nextPendingConnection()获取新的连接对象QTcpSocket。这一步已在上一步handleNewConnection槽函数中展示。
  5. 通过QTcpSocket对象与客户端进行通信。
    // 这通常包括读取数据、发送数据等。
    void YourClass::readData() {  
        QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());  
        if (socket) {  
            QByteArray data = socket->readAll();  
            qDebug() << "收到数据:" << data;  
            // 可以在这里处理数据,然后发送响应等  
        }  
    }  
      
    // 发送数据示例  
    void YourClass::sendData(QTcpSocket *socket, const QByteArray &data) {  
        if (socket->state() == QAbstractSocket::ConnectedState) {  
            socket->write(data);  
        }  
    }

TCP客户端

  1. 创建QTcpSocket对象。
    QTcpSocket *socket = new QTcpSocket(this);
  2. 调用connectToHost()方法连接到服务器。
    socket->connectToHost("127.0.0.1", 1234);  
      
    // 可以选择性地检查连接状态  
    if (!socket->waitForConnected()) {  
        qDebug() << "无法连接到服务器:" << socket->errorString();  
        socket->deleteLater();  
    } else {  
        qDebug() << "连接到服务器成功";  
    }
  3. 等待readyRead()信号的到来,表明有新的数据可读。
    connect(socket, &QTcpSocket::readyRead, this, &YourClass::readDataFromServer);  
      
    // 槽函数  
    void YourClass::readDataFromServer() {  
        QByteArray data = socket->readAll();  
        qDebug() << "从服务器收到数据:" << data;  
    }
  4. 调用readAll()方法读取数据。这一步已在readDataFromServer槽函数中展示。
  5. 通过QTcpSocket对象与服务器进行通信。
    // 客户端发送数据示例  
    void YourClass::sendDataToServer(const QByteArray &data) {  
        if (socket->state() == QAbstractSocket::ConnectedState) {  
            socket->write(data);  
        }  
    }

四、实战示例

  • UI界面

服务端:

客户端:

  • 核心代码

服务端:

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QString strip = GetLocalIpAddress();
    ui->comboBoxIp->addItem(strip);

    tcpserver = new QTcpServer(this);
    connect(tcpserver, SIGNAL(newConnection()), this, SLOT(onnewconnect()));
}

MainWindow::~MainWindow()
{
    delete ui;
}


void MainWindow::on_pushButton_Start_clicked()
{
    QString ip = ui->comboBoxIp->currentText();
    quint16 port = ui->spinBoxPort->value();

    QHostAddress address(ip);
    tcpserver->listen(address, port);

    ui->plainTextEdit_DispMsg->appendPlainText("~~~~~~~~正在监听~~~~~~~~");
    ui->plainTextEdit_DispMsg->appendPlainText("服务器地址:" +
                                               tcpserver->serverAddress().toString());
    ui->plainTextEdit_DispMsg->appendPlainText("服务器端口:" +
                                               QString::number(tcpserver->serverPort()));

    ui->pushButton_Start->setEnabled(false);
    ui->pushButton_Stop->setEnabled(true);
}

void MainWindow::on_pushButton_Stop_clicked()
{
    if(tcpserver->isListening())
    {
        tcpserver->close();
        ui->pushButton_Stop->setEnabled(false);
        ui->pushButton_Start->setEnabled(true);
    }

}

void MainWindow::on_pushButton_Send_clicked()
{
    QString strmsg = ui->lineEdit_InpuMsg->text();
    ui->plainTextEdit_DispMsg->appendPlainText("[J.]:" + strmsg);
    ui->lineEdit_InpuMsg->clear();

    QByteArray str = strmsg.toUtf8();
    str.append("\n");
    tcpsocket->write(str);
}

QString MainWindow::GetLocalIpAddress()    // 获取本地IP地址
{
    QString hostname = QHostInfo::localHostName();
    QHostInfo hostinfo = QHostInfo::fromName(hostname);

    QString localip = "";
    QList<QHostAddress> addresslist = hostinfo.addresses();

    if(!addresslist.isEmpty())
    {
        for(int i = 0; i < addresslist.count(); i++)
        {
            QHostAddress hostaddress = addresslist.at(i);
            if(hostaddress.protocol() == QAbstractSocket::IPv4Protocol)
            {
                localip = hostaddress.toString();
                break;
            }
        }
    }
    return localip;
}

void MainWindow::clientconnect()       // 客户端连接
{
    ui->plainTextEdit_DispMsg->appendPlainText("****客户端Socket连接****");
    ui->plainTextEdit_DispMsg->appendPlainText("peer address:"+tcpsocket->peerAddress().toString());
    ui->plainTextEdit_DispMsg->appendPlainText("peer port:"+QString::number(tcpsocket->peerPort()) + "\n");
}

void MainWindow::clientdisconnect()    // 不连接
{
    ui->plainTextEdit_DispMsg->appendPlainText("****客户端Socket断开连接****\n");
    tcpsocket->deleteLater();
}

void MainWindow::socketreaddata()      // 读数据
{
    while (tcpsocket->canReadLine()) {
        ui->plainTextEdit_DispMsg->appendPlainText("[T.]:" + tcpsocket->readLine());
    }
}
/*
 * 当有客户端接入时,tcpServer会发射newConnection信号,触发槽函数
 * nextPendingConnection函数获取与接入连接进行通信的QTcpSocket.
 * connected():客户端socket 连接建立时发射此信号;
 * disconnected():客户端socket 连接断开时发射此信号;
 * readyRead():本程序的socket的读取缓冲区有新数据时发射此信号。
*/

void MainWindow::onnewconnect()          // 新连接
{
    tcpsocket = tcpserver->nextPendingConnection();

    connect(tcpsocket, SIGNAL(connected()), this, SLOT(clientconnect())); // 连接
    clientconnect();

    connect(tcpsocket, SIGNAL(disconnected()), this, SLOT(clientdisconnect())); // 不连接

    connect(tcpsocket, SIGNAL(readyRead()), this, SLOT(socketreaddata())); // 读数据


}

void MainWindow::closeEvent(QCloseEvent *event) // 关闭事件
{
    if(tcpserver->isListening())
        tcpserver->close();
    event->accept();
}

客户端:

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QHostInfo>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    tcpclient = new QTcpSocket(this);
    QString strip = GetLocalIpAddress();
    ui->comboBoxIp->addItem(strip);

    connect(tcpclient, SIGNAL(connected()), this, SLOT(connectFunc())); // 连接

    connect(tcpclient, SIGNAL(disconnected()), this, SLOT(disconnectFunc())); // 不连接

    connect(tcpclient, SIGNAL(readyRead()), this, SLOT(socketreaddata())); // 读数据
}

MainWindow::~MainWindow()
{
    delete ui;
}

QString MainWindow::GetLocalIpAddress()    // 获取本地IP地址
{
    QString hostname = QHostInfo::localHostName();
    QHostInfo hostinfo = QHostInfo::fromName(hostname);

    QString localip = "";
    QList<QHostAddress> addresslist = hostinfo.addresses();

    if(!addresslist.isEmpty())
    {
        for(int i = 0; i < addresslist.count(); i++)
        {
            QHostAddress hostaddress = addresslist.at(i);
            if(hostaddress.protocol() == QAbstractSocket::IPv4Protocol)
            {
                localip = hostaddress.toString();
                break;
            }
        }
    }
    return localip;
}

void MainWindow::connectFunc()       // 连接服务器
{
    ui->plainTextEdit_DispMsg->appendPlainText("****成功连接服务器****");
    ui->plainTextEdit_DispMsg->appendPlainText("peer address:"+tcpclient->peerAddress().toString());
    ui->plainTextEdit_DispMsg->appendPlainText("peer port:"+QString::number(tcpclient->peerPort()) + "\n");

    ui->pushButton_Connect->setEnabled(false);
    ui->pushButton_Stop->setEnabled(true);
}

void MainWindow::disconnectFunc()    // 不连接
{
    ui->plainTextEdit_DispMsg->appendPlainText("***已断开服务器连接***\n");

    ui->pushButton_Connect->setEnabled(true);
    ui->pushButton_Stop->setEnabled(false);
}

void MainWindow::socketreaddata()      // 读数据
{
    while (tcpclient->canReadLine()) {
        ui->plainTextEdit_DispMsg->appendPlainText("[J.]:" + tcpclient->readLine());
    }
}

void MainWindow::on_pushButton_Connect_clicked()
{
    QString ip = ui->comboBoxIp->currentText();
    quint16 port = ui->spinBoxPort->value();

    //QHostAddress address(ip);
    tcpclient->connectToHost(ip, port);

}

void MainWindow::on_pushButton_Stop_clicked()
{
    if(tcpclient->state() == QAbstractSocket::ConnectedState)
        tcpclient->disconnectFromHost();
}

void MainWindow::on_pushButton_Send_clicked()
{
    QString strmsg = ui->lineEdit_InpuMsg->text();
    ui->plainTextEdit_DispMsg->appendPlainText("[T.]:" + strmsg);
    ui->lineEdit_InpuMsg->clear();

    QByteArray str = strmsg.toUtf8();
    str.append("\n");
    tcpclient->write(str);
}

void MainWindow::closeEvent(QCloseEvent *event)
{
    if(tcpclient->state() == QAbstractSocket::ConnectedState)
        tcpclient->disconnectFromHost();
    event->accept();
}
  • 运行结果

五、总结

通过本篇文章的学习,通过QTcpSocket和QTcpServer类可以实现TCP通信。下一篇会介绍UDP协议的工作原理及实战!

传送门1:QT网络编程(一)——基础知识体系

传送门2:QT网络编程(三)——UDP协议工作原理及实战

标签:QT,编程,TCP,连接,ui,QTcpSocket,MainWindow,客户端
From: https://blog.csdn.net/weixin_62621696/article/details/140668150

相关文章

  • 零基础STM32单片机编程入门(二十二) ESP8266 WIFI模块实战含源码
    文章目录一.概要二.ESP8266WIFI模块主要性能参数三.ESP8266WIFI模块芯片内部框图四.ESP8266WIFI模块原理图五.ESP8266WIFI模块与单片机通讯方法1.硬件连接2.ESP8266模块AT指令介绍六.STM32单片机与ESP8266WIFI模块通讯实验1.硬件准备2.软件工程3.软件主要代码4.实验......
  • Mistral新旗舰决战Llama 3.1,最强开源Large 2 123B,扛鼎多语言编程全能王
    【新智元导读】紧跟着Meta的重磅发布,MistralLarge2也带着权重一起上新了,而且参数量仅为Llama3.1405B的三分之一。不仅在编码、数学和多语言等专业领域可与SOTA模型直接竞争,还支持单节点部署。昨天正式发布的Llama3.1模型,让AI社区着实为之兴奋。但是仔细一想就能发现......
  • vs2022 QT Opencv用到的一些代码
     MyFirstQT.cpp#include"MyFirstQT.h"#include"ui_MyFirstQT.h"#include<QFileDialog>#include<QMessageBox>#include<QPixmap>#include<opencv2/opencv.hpp>#include<QDebug>#include<opencv2/imgp......
  • 2024“钉耙编程”中国大学生算法设计超级联赛(3)
    Preface徐神是我们的红太阳,最后2min切了一道极难的string使得在这场前期爆炸的局面最后没有崩得太难看这场前期的开题顺序有点问题导致前5题出的很慢,中后期开始三人一人写一题,然后经典三开三卡好在最后我在WA了五发后写对拍把B过了,徐神又压线过了string,但比较可惜的......
  • Qt自定义控件
    开发系统:ubuntu22.04IDE:clion构建工具:cmakeQt自定义控件之插件形式插件形式是指将自定义控件按照一定的规则,生成动态库,放到Qtdesigner插件加载目录/usr/lib/x86_64-linux-gnu/qt5/plugins/designer下,Qtdesigner启动时加载,自定义控件就像内置控件一样可以直接拖拽。下面......
  • C# TCP通信
    一、服务端usingSystem;usingSystem.Collections.Generic;usingSystem.Net;usingSystem.Net.Sockets;usingSystem.Text;usingSystem.Threading;usingSystem.Windows.Forms;namespaceWindowsFormsApp7{publicpartialclassForm2:Form{/......
  • Shell脚本编程(二)
    目录一、Shell程序中使用变量1、变量赋值2、访问变量值3、键盘输入变量值4、实例15、实例2二、表达式的比较1、字符串比较1.1、实例11.2、实例22、数字比较2.1、实例13、逻辑操作3.1、实例14、文件操作4.1、实例14.2、实例2一、Shell程序中使用变量1、变量......
  • shell脚本编程
    一、shell基础1、shell概念shell英文翻译过来是外壳的意思,作为计算机语言来理解可以认为它是操作系统的外壳。可以通过shell命令来操作和控制操作系统,比如Linux中的shell命令就包括ls、cd、pwd等等。shell在内核的基础上编写的一个应用程序,它连接了用户和Linux内......
  • 模块3 面向对象编程高级 --- 第八章:实现继承
    第八章实现继承主要知识点1、继承的概念2、继承的实现3、用this和super关键字实现继承4、抽象类的实现学习目标掌握继承和抽象类的定义和实现方法。借助集成,可以扩展原有的代码,应用到其他程序中,而不必要重新编写这些代码。扩展声明的新类称为子类,原有......
  • Python 中的面向对象编程
    一.介绍在本文中,我们将使用Python中的类和对象来探索基本的OOP概念。面向对象编程(OOP)是一种强大的方法,可帮助开发人员组织代码,使其易于理解、重用和维护。Python是一种灵活的语言,可以很好地支持OOP概念。1.类和对象类是创建对象的蓝图。它定义了该类的对象将......