Qt之TCP编程

Qt之TCP编程

介绍

TCP(传输控制协议)和套接字(socket)是网络通信中两个相关但不同的概念。下面是对它们的简要解释:

TCP(传输控制协议)

  1. 定义

    • TCP 是一种面向连接的、可靠的传输协议。它在计算机网络中负责在两个主机之间建立和维护一个可靠的连接,并保证数据的完整性和顺序。
    • TCP 提供错误检测、流量控制、拥塞控制等机制,以确保数据在传输过程中的可靠性。
  2. 特点

    • 连接导向:在发送数据之前,必须先建立连接。
    • 可靠性:确保数据的正确传输,重新传输丢失的数据。
    • 顺序性:确保数据按发送顺序到达目标。
    • 流量控制:防止发送方过快发送数据导致接收方处理不过来。
    • 拥塞控制:防止网络过载。
  3. 使用场景

    • 适用于需要可靠数据传输的应用,如网页浏览、电子邮件和文件传输等。

套接字(Socket)

  1. 定义

    • 套接字是网络通信中的一种抽象概念,它是程序与网络之间的接口。通过套接字,应用程序可以发送和接收数据。
    • 套接字提供了与网络连接的方式,可以是 TCP、UDP 等不同的协议。
  2. 类型

    • 流式套接字(Stream Socket):基于 TCP 协议,提供面向连接的可靠数据流。
    • 数据报套接字(Datagram Socket):基于 UDP 协议,提供无连接的、不可靠的数据报服务。
  3. 操作

    • 创建:使用系统调用或库函数创建一个套接字。
    • 绑定:将套接字绑定到特定的地址和端口。
    • 监听:在服务端,监听客户端的连接请求。
    • 连接:在客户端,连接到服务端的套接字。
    • 发送/接收:通过套接字发送和接收数据。
    • 关闭:关闭套接字,释放资源。

总结

  • TCP 是一种传输协议,定义了在网络中如何可靠地传输数据。
  • 套接字 是一种网络编程接口,它允许应用程序通过特定的协议(如 TCP)进行网络通信。

在实际编程中,您可能会使用套接字来实现 TCP 连接或其他类型的网络连接。例如,在 Qt 框架中,QTcpSocket 类是一个套接字类,它提供了对 TCP 连接的支持。

QTcpServer

QTcpServer 是 Qt 框架中用于创建和管理 TCP 服务器的类。它允许你在指定的端口上监听来自客户端的连接,并在有客户端连接时处理它们。下面是 QTcpServer 相关功能的详细说明,包括构造函数和常用 API:

构造函数

QTcpServer::QTcpServer(QObject *parent = nullptr);
  • 参数parent 是父对象指针,通常设置为 nullptr
  • 功能:创建一个新的 QTcpServer 实例。

listen 方法

bool QTcpServer::listen(const QHostAddress &address, quint16 port);
  • 参数
    • address:监听的地址,可以是本地地址、IP 地址或 QHostAddress::Any
    • port:监听的端口号。
  • 功能:开始在指定的地址和端口上监听传入的连接请求。
  • 返回值:如果成功开始监听,返回 true;否则返回 false

newConnection 信号

signals:
    void newConnection();
  • 功能:当有新的客户端连接请求到达时发出。可以通过 connect 函数连接到一个槽,以处理新的连接。

nextPendingConnection 方法

QTcpSocket* QTcpServer::nextPendingConnection();
  • 返回值:返回一个新的 QTcpSocket 实例,代表与新客户端的连接。
  • 功能:获取下一个待处理的客户端连接。通常在接收到 newConnection 信号时调用。

QTcpSocket::readyRead 信号

signals:
    void readyRead();
  • 功能:当有数据从客户端读取到时发出。可以连接到一个槽来处理接收到的数据。

clientConnection->state() 方法

QAbstractSocket::SocketState QTcpSocket::state() const;
  • 返回值:返回当前套接字的状态,可以是以下之一:
    • QAbstractSocket::UnconnectedState:未连接状态
    • QAbstractSocket::HostLookupState:正在查找主机
    • QAbstractSocket::ConnectingState:正在连接
    • QAbstractSocket::ConnectedState:已连接
    • QAbstractSocket::BoundState:已绑定
    • QAbstractSocket::ClosingState:正在关闭
    • QAbstractSocket::ListeningState:正在监听(对于服务器端)

clientConnection->write 方法

qint64 QTcpSocket::write(const QByteArray &data);
  • 参数data 要发送的数据。
  • 返回值:返回实际写入的字节数;如果失败,则返回 -1。
  • 功能:将数据发送到连接的对端。

clientConnection->readAll 方法

QByteArray QTcpSocket::readAll();
  • 返回值:返回从套接字读取的所有数据。
  • 功能:读取并返回套接字中的所有可用数据。

clientConnection->disconnectFromHost 方法

void QTcpSocket::disconnectFromHost();
  • 功能:断开当前与主机的连接。它会发送断开连接的请求,并关闭套接字。

clientConnection->deleteLater 方法

void QObject::deleteLater();
  • 功能:标记对象在事件循环的适当时机删除,确保在对象被销毁时不会造成不稳定的状态。

示例用法

以下是一个简单的使用示例:

// 创建服务器
QTcpServer *server = new QTcpServer(this);

// 开始监听端口
if (server->listen(QHostAddress::Any, 4545)) {
    qDebug() << "服务器启动成功!";
} else {
    qDebug() << "服务器启动失败:" << server->errorString();
}

// 处理新连接
connect(server, &QTcpServer::newConnection, [=]() {
    QTcpSocket *clientConnection = server->nextPendingConnection();
    
    // 处理客户端数据
    connect(clientConnection, &QTcpSocket::readyRead, [=]() {
        QByteArray data = clientConnection->readAll();
        qDebug() << "接收到数据:" << data;
    });

    // 发送数据
    clientConnection->write("Hello, Client!");

    // 断开连接
    connect(clientConnection, &QTcpSocket::disconnected, [=]() {
        clientConnection->deleteLater();
    });
});

这个示例创建了一个 TCP 服务器,监听端口 4545。它处理新的客户端连接、读取客户端发送的数据、向客户端发送数据,并在连接断开时清理资源。

QTcpSocket

QTcpSocket 是 Qt 框架中的一个类,用于实现 TCP 客户端。它允许你通过网络连接到远程主机,并进行数据传输。QTcpSocket 提供了诸如连接、断开连接、发送数据、接收数据等基本的 TCP 网络操作功能,并且支持异步操作,使得在 GUI 应用中不会阻塞用户界面。

QTcpSocket 构造函数

QTcpSocket::QTcpSocket(QObject *parent = nullptr);
  • 参数parent 是父对象指针,通常设置为 nullptr
  • 功能:创建一个新的 QTcpSocket 实例,用于客户端 TCP 连接。

QTcpSocket::connected 信号

signals:
    void connected();
  • 功能:当与远程主机成功建立连接时发出。可以连接到槽函数以处理连接建立后的逻辑。

QTcpSocket::readyRead 信号

signals:
    void readyRead();
  • 功能:当有数据可供读取时发出。通常在此信号的槽函数中调用 readAll() 方法来获取数据。

QTcpSocket::connectToHost 方法

void QTcpSocket::connectToHost(const QHostAddress &address, quint16 port);
  • 参数
    • address:要连接的主机地址。
    • port:要连接的端口号。
  • 功能:发起到指定地址和端口的连接。连接成功后会发出 connected 信号。

QTcpSocket::disconnectFromHost 方法

void QTcpSocket::disconnectFromHost();
  • 功能:断开与当前主机的连接。可以用于关闭连接并释放资源。

QTcpSocket::state 方法

QAbstractSocket::SocketState QTcpSocket::state() const;
  • 返回值:返回当前套接字的状态,如 QAbstractSocket::ConnectedState 表示已连接,QAbstractSocket::UnconnectedState 表示未连接等。
  • 功能:用于检查套接字的当前状态。

QTcpSocket::write 方法

qint64 QTcpSocket::write(const QByteArray &data);
  • 参数data 要发送的数据。
  • 返回值:返回实际写入的字节数;如果失败,则返回 -1。
  • 功能:将数据写入套接字,并发送到远程主机。

QTcpSocket::readAll 方法

QByteArray QTcpSocket::readAll();
  • 返回值:返回一个 QByteArray 对象,包含套接字中所有可用的数据。
  • 功能:从套接字中读取所有可用的数据。通常在 readyRead 信号的槽函数中使用。

示例用法

#include <QCoreApplication>
#include <QTcpSocket>
#include <QHostAddress>
#include <QDebug>

class TcpClient : public QObject
{
    Q_OBJECT

public:
    TcpClient() {
        // 创建 QTcpSocket 实例
        socket = new QTcpSocket(this);

        // 连接信号与槽
        connect(socket, &QTcpSocket::connected, this, &TcpClient::onConnected);
        connect(socket, &QTcpSocket::readyRead, this, &TcpClient::onReadyRead);
        connect(socket, &QTcpSocket::disconnected, this, &TcpClient::onDisconnected);

        // 连接到主机
        socket->connectToHost(QHostAddress("127.0.0.1"), 1234);
    }

private slots:
    void onConnected() {
        qDebug() << "Connected to host";
        // 发送数据
        socket->write("Hello, Server!");
    }

    void onReadyRead() {
        QByteArray data = socket->readAll();
        qDebug() << "Received data:" << data;
    }

    void onDisconnected() {
        qDebug() << "Disconnected from host";
    }

private:
    QTcpSocket *socket;
};

TCP编程

整体设计如图所示:

在这里插入图片描述

创建一个项目,它们都有各自的窗口类和对应的ui文件,目录如图。

在这里插入图片描述

接下来分别对服务端和客服端使用TCP编程。

服务端

服务端只需要引入QTcpServer的指针以及QTcpSocket指针作为类的成员变量,一个用于监听客户端,一个用于获取客服端的连接。然后调用相应的api来完成简单的设计。

界面设计

界面的ui如图所示。

在这里插入图片描述

比较简单,就两个消息框和两个按钮。

代码设计

tcpserver.h

#ifndef TCPSERVER_H
#define TCPSERVER_H

#include <QWidget>
#include <QTcpServer>
namespace Ui {
class TcpServer;
}

class TcpServer : public QWidget
{
    Q_OBJECT

public:
    explicit TcpServer(QWidget *parent = nullptr);
    ~TcpServer();

private:
    Ui::TcpServer *ui;
    QTcpServer *server;  // QTcpServer指针
    QTcpSocket *clientConnection; // QTcpSocket指针
};

#endif // TCPSERVER_H

tcpserver.cpp

#include "tcpserver.h"
#include "ui_tcpserver.h"
#include <QTcpSocket>

TcpServer::TcpServer(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::TcpServer)
{
    ui->setupUi(this);
    this->resize(400, 500);
    this->setWindowTitle("服务端");

    // 创建一个 QTcpServer 实例
    server = new QTcpServer(this);

    // 让服务器在本地地址的4545端口上监听连接请求
    bool succeed = server->listen(QHostAddress::LocalHost, 4545);
    if (!succeed) {
        qDebug() << "无法启动服务器:" << server->errorString();
        return;
    }

    // 当有新的客户端连接时,触发 newConnection 信号
    connect(server, &QTcpServer::newConnection, this, [=] {
        ui->textEdit->setText("端口监听成功!");

        // 获取新的客户端连接
        clientConnection = server->nextPendingConnection();

        // 接收数据
        connect(clientConnection, &QTcpSocket::readyRead, this, [=] {
            QByteArray data = clientConnection->readAll();
            if (data.isEmpty()) {
                ui->textEdit->append("客户端发送空数据!");
            } else {
                ui->textEdit->append("客户端:" + QString::fromUtf8(data));
            }
        });

        // 发送数据
        connect(ui->pushButton_3, &QPushButton::clicked, this, [=] {
            QString data = ui->textEdit_2->toPlainText();
            if(clientConnection && clientConnection->state() == QAbstractSocket::ConnectedState) {
                QByteArray byteArray = data.toUtf8();
                clientConnection->write(byteArray);
                ui->textEdit->append("服务端:" + data);
            } else {
                ui->textEdit->append("未连接到客户端!");
            }
        });
    });

    // 断开连接
    connect(ui->pushButton_2, &QPushButton::clicked, this, [=] {
        if (clientConnection) {
            clientConnection->disconnectFromHost();
            clientConnection->deleteLater();
            clientConnection = nullptr;
            ui->textEdit->append("连接已断开!");
        }
    });
}

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

注意,断开连接的部分不要放在newConnection的信号对应的槽中,防止意外情况。

客服端

只需要引入QTcpSocket套接字作为类的成员指针。

界面设计

界面设计,如图所示。

在这里插入图片描述

代码设计

tcpclient.h

#ifndef TCPCLIENT_H
#define TCPCLIENT_H

#include <QWidget>
#include <QTcpSocket>
namespace Ui {
class TcpClient;
}

class TcpClient : public QWidget
{
    Q_OBJECT

public:
    explicit TcpClient(QWidget *parent = nullptr);
    ~TcpClient();

public slots:
    void  connectServer();
    void  disconnectFromServer();
    //发送数据
    void sendData();
    // 接收数据
    void receiveData();
private:
    Ui::TcpClient *ui;
    // 套接字指针
    QTcpSocket *socket;
    // ip地址和端口号
    QString hostAddress;
    quint16 port;

};

#endif // TCPCLIENT_H

tcpclient.cpp

#include "tcpclient.h"
#include "ui_tcpclient.h"

TcpClient::TcpClient(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::TcpClient)
{
    ui->setupUi(this);
    this->resize(450,550);
    this->setWindowTitle("客服端");

    socket = new QTcpSocket(this);

    // 回显默认端口和ip地址
    ui->lineEdit->setText("4545");
    ui->lineEdit_2->setText("127.0.0.1");

    // 从输入框读取端口号和ip地址
    bool ok;
    port = ui->lineEdit->text().toShort(&ok);
    hostAddress =  ui->lineEdit_2->text();


    // 当连接到服务器成功时执行以下操作
    connect(socket, &QTcpSocket::connected, this, [=] {//1
        ui->textEdit->setText("服务器连接成功!");

        // 发送数据
        connect(ui->pushButton_3, &QPushButton::clicked, this, &TcpClient::sendData);

        // 接收数据
        connect(socket, &QTcpSocket::readyRead, this, &TcpClient::receiveData);

        // 断开连接
        connect(ui->pushButton_2, &QPushButton::clicked, this, &TcpClient::disconnectFromServer);
    });


    // 连接到客服端
    connect(ui->pushButton, &QPushButton::clicked, this, &TcpClient::connectServer);//2


}

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

// 连接到服务器
void TcpClient::connectServer() {
    socket->connectToHost(hostAddress, port); // 连接到指定的主机和端口
}

// 断开与服务器的连接
void TcpClient::disconnectFromServer() {
    ui->textEdit->setText("连接已断开!");
    socket->disconnectFromHost();
    if (socket->state() == QAbstractSocket::UnconnectedState) {
        ui->textEdit->append("已断开连接,不能再发送数据。");
    }
}


// 发送数据到服务器
void TcpClient::sendData() {
    if (socket->state() == QAbstractSocket::ConnectedState) {
        QString text = ui->textEdit_2->toPlainText();
        QString formattedText = QString("<div style='color:blue;'>客服端:%1</div>").arg(text);
        ui->textEdit->append(formattedText);
        socket->write(text.toUtf8());
    } else {
        ui->textEdit->append("连接已断开,无法发送数据。");
    }
}

// 接收服务器发来的数据
void TcpClient::receiveData() {
    QByteArray data = socket->readAll();
    QString text = QString("<div style='color:purple;'>%1</div>").arg(QString::fromUtf8(data));
    ui->textEdit->append("服务端:"+text);
}

// 2和1的顺序应该不要交换

不同于服务端的地方在于,把槽函数从lambda中抽离出来。

验证

在项目启动之前,一定要保证,服务端在客服端之前启动,如下面的main函数所示:

#include "widget.h"
#include "tcpclient.h"
#include "tcpserver.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();

    TcpServer ts;// 先启动服务端
    ts.show();
    TcpClient t;
    t.show();

    return a.exec();
}

在客户端界面点击连接服务器,进行验证。

连接之后分别发送数据。

在这里插入图片描述

从客服端断开。

在这里插入图片描述

重新连接,然后从服务端断开。

在这里插入图片描述

参考:
Qt学习(十四)—— 网络通信之TCP

基于TCP的Qt网络通信

  • 15
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值