Qt-系统网络UDP回显客户端与服务端(64)

目录

描述

模块设计理念

相关函数

使用

回显服务器

先连接信号槽再绑定端口号

读取请求并解析

把响应写回给客户端

服务器处理逻辑

回显客户端

准备工作

初始化界面布局

定义两个常量

初始化窗口

给发送按钮添加槽函数

连接信号槽

处理服务器返回

测试

单个客户端运行

多个客户端运行

代码

服务器

客户端

关于服务器和客户端能否部署在云服务器上的一些问题

小补充


描述

网络编程,显然任何一个开发工具都绕不开的话题

很遗憾的C++并没有对网络进行封装,使用在C++中对于网络编程是比较麻烦的,但是 Qt 对此进行了修正,有了自己的一套网络编程,所以我们在 Qt 中使用网络编程是比较方便的,Qt 中对操作系统提供的 API 封装成了一个类,我们直接使用就可以了,不过首先我们还得引用 network模块

模块设计理念

Qt 对很多功能都进行了模块化,网络编程也算如此

模块化处理有很多优点,可以减少生成无用代码

Qt 提供了两个版本的网络编程版本

相关函数

主要的类有两个. QUdpSocket 和 QNetworkDatagram

QUdpSocket 表⽰⼀个 UDP 的 socket ⽂件.

|-----------------------------------------|----|-------------------------------------|----------------------|
| 名称 | 类型 | 说明 | 对标原⽣ API |
| bind(const QHostAddress&, quint16) | ⽅法 | 绑定指定的端⼝号. | bind |
| receiveDatagram() | ⽅法 | 返回 QNetworkDatagram . 读取⼀个 UDP 数据报. | recvfrom |
| writeDatagram(const QNetworkDatagram&) | ⽅法 | 发送⼀个 UDP 数据报. | sendto |
| readyRead | 信号 | 在收到数据并准备就绪后触发. | ⽆ (类似于 IO 多路复⽤的通知机制) |

QNetworkDatagram 表⽰⼀个 UDP 数据报.

|---------------------------------------------------------------------|------|---------------------------------------------------------|---------------------|
| 名称 | 类型 | 说明 | 对标原⽣ API |
| QNetworkDatagram(const QByteArray&, const QHostAddress& ,quint16) | 构造函数 | 通过 QByteArray , ⽬标 IP 地址, ⽬标端⼝号 构造⼀个 UDP 数据报.通常⽤于发送数据时. | ⽆ |
| data() | ⽅法 | 获取数据报内部持有的数据.返 QByteArray | ⽆ |
| senderAddress() | ⽅法 | 获取数据报中包含的对端的IP地 址. | ⽆, recvfrom 包含了该功能. |
| senderPort() | ⽅法 | 获取数据报中包含的对端的端⼝号. | ⽆, recvfrom 包含了该功能. |

使用

回显服务器

实现一个带有界面的 UDP 回显服务器,虽然服务器一般不使用图形化界面

使用 Qt 的信号槽机制,可以很好解决堵塞问题

首先我们初始出界面出来

第一步,在.pro文件中引用 network

引入并创建一个 QUdpSocket 对象

先连接信号槽再绑定端口号

绑定失败要有提升,不要让程序继续下去了,这一点 Qt 帮我们封装了,使用 errorString 即可知道绑定失败的原因

读取请求并解析

把响应写回给客户端

服务器处理逻辑

因为还没有客户端,所以我们先得构建出客户端才能进行测试

回显客户端

客户端初始界面

准备工作

在同一个窗口中,我们可以创建多个项目,为了便于演示,这里我们创建在了一起

但是我们需要主要不要混淆了

这里加黑的是主要活动项目,当我们运行的时候,就只会运行该项目

当然了你也可以进行修改主要活动项目

同样的加上 network

初始化界面布局

首先我们创建这样的一个界面

垂直布局5,1

垂直策略

lineEdit

QPushButton

运行如下

定义两个常量

人为规定为两个字节

初始化窗口

给发送按钮添加槽函数

这里填写IP地址的时候需要注意转换

连接信号槽

处理服务器返回

测试

单个客户端运行

先后运行发送消息,正常

多个客户端运行

想要运行多个客户端,要去找exe可执行程序,在Qt内部无法做的

找到对应的build文件夹

debug文件夹

exe可执行程序

如下,测试成功

代码

.pro

加上network

服务器

widget.h

cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QUdpSocket>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();



private:
    Ui::Widget *ui;

    QUdpSocket* socket;

    void processRequest();
    QString process(const QString& request);
};
#endif // WIDGET_H

widget.cpp

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
#include <QNetworkDatagram>

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

    // 创建出这个对象
    socket = new QUdpSocket(this);

    // 设置窗口标题
    this->setWindowTitle("服务器");

    // 连接信号槽
    connect(socket, &QUdpSocket::readyRead, this, &Widget::processRequest);

    // 绑定端口号
    bool ret = socket->bind(QHostAddress::Any, 9090);
    if(!ret){
        // 绑定失败
        QMessageBox::critical(this, "服务器启动出错", socket->errorString());
        return;
    }
}

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

void Widget::processRequest()
{
    // 1.读取请求并解析
    const QNetworkDatagram& requestDatagram = socket->receiveDatagram();
    QString request = requestDatagram.data();
    // 2.根据请求计算响应(由于是回显服务器,响应不需要计算,就是请求本身)
    const QString& response = process(request);
    // 3.把响应写回给客户端
    QNetworkDatagram responseDatagram(response.toUtf8(), requestDatagram.senderAddress(), requestDatagram.senderPort());
    socket->writeDatagram(responseDatagram);
    QString log = "[" + requestDatagram.senderAddress().toString() + ":" + QString::number(requestDatagram.senderPort())
                + "] req: " + request + ", resp: " + response;
    ui->listWidget->addItem(log);
}

QString Widget::process(const QString &request)
{
    // 由于当前是一个回显服务器,响应就是和请求完全一样的
    // 对于一个成熟的商业服务器,这里的请求->响应的计算可能非常复杂(业务逻辑)
    return request;
}

客户端

widget.h

cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QUdpSocket>

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_pushButton_clicked();

private:
    Ui::Widget *ui;

    QUdpSocket* socket;

    void processResponse();
};
#endif // WIDGET_H

widget.cpp

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"

#include <QNetworkDatagram>

// 定义两个常量,描述服务器的 地址 和 端口
const QString& SERVER_IP = "127.0.0.1"; //本地回环
const quint16 SERVER_PORT = 9090;

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

    socket = new QUdpSocket(this);

    // 修改窗口标题,方便区别这个是客户端程序
    this->setWindowTitle("客户端");

    // 通过信号槽,来处理返回的数据,来处理服务器返回的数据
    connect(socket, &QUdpSocket::readyRead, this, &Widget::processResponse);
}

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


void Widget::on_pushButton_clicked()
{
    // 1.获取到输入框的内容
    const QString& text = ui->lineEdit->text();
    // 2.构建 UDP 的请求资源
    QNetworkDatagram requestDatagram(text.toUtf8(), QHostAddress(SERVER_IP), SERVER_PORT);
    // 3.发送请求数据
    socket->writeDatagram(requestDatagram);
    // 4.把发送的请求也添加到列表中
    ui->listWidget->addItem("客户端说: " + text);
    // 5.把输入框的内容也清空一下
    ui->lineEdit->setText("");
}

void Widget::processResponse()
{
    // 通过这个函数来处理收到的数据
    // 1.读取到响应数据
    const QNetworkDatagram& responseDatagram = socket->receiveDatagram();
    QString response = responseDatagram.data();
    // 2.把响应的数据显示到界面上
    ui->listWidget->addItem("服务器说: " + response);
}

关于服务器和客户端能否部署在云服务器上的一些问题

小补充

在服务器中,我们看到的消息中会多了一些消息,这是 IPV6 的本地回环,在绑定端口号的时候,我们的服务器指定了全部的地址

关于引用的认知

这一点也是很常见的一类问题了

自动添加头文件

在一些开发工具中,是支持自动添加头文件的,但是显然C++是不能够的,这一点有些遗憾

相关推荐
----云烟----1 小时前
QT中QString类的各种使用
开发语言·qt
lsx2024061 小时前
SQL SELECT 语句:基础与进阶应用
开发语言
开心工作室_kaic2 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
向宇it2 小时前
【unity小技巧】unity 什么是反射?反射的作用?反射的使用场景?反射的缺点?常用的反射操作?反射常见示例
开发语言·游戏·unity·c#·游戏引擎
武子康2 小时前
Java-06 深入浅出 MyBatis - 一对一模型 SqlMapConfig 与 Mapper 详细讲解测试
java·开发语言·数据仓库·sql·mybatis·springboot·springcloud
转世成为计算机大神2 小时前
易考八股文之Java中的设计模式?
java·开发语言·设计模式
宅小海3 小时前
scala String
大数据·开发语言·scala
qq_327342733 小时前
Java实现离线身份证号码OCR识别
java·开发语言
锅包肉的九珍3 小时前
Scala的Array数组
开发语言·后端·scala
心仪悦悦3 小时前
Scala的Array(2)
开发语言·后端·scala