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

目录

引言

一、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协议工作原理及实战

  • 69
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

J^T

谢谢帅哥/美女

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值