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++是不能够的,这一点有些遗憾

相关推荐
Code哈哈笑6 分钟前
【Java 学习】深度剖析Java多态:从向上转型到向下转型,解锁动态绑定的奥秘,让代码更优雅灵活
java·开发语言·学习
程序猿进阶9 分钟前
深入解析 Spring WebFlux:原理与应用
java·开发语言·后端·spring·面试·架构·springboot
qq_4336184411 分钟前
shell 编程(二)
开发语言·bash·shell
charlie11451419126 分钟前
C++ STL CookBook
开发语言·c++·stl·c++20
袁袁袁袁满26 分钟前
100天精通Python(爬虫篇)——第113天:‌爬虫基础模块之urllib详细教程大全
开发语言·爬虫·python·网络爬虫·爬虫实战·urllib·urllib模块教程
ELI_He99932 分钟前
PHP中替换某个包或某个类
开发语言·php
m0_7482361140 分钟前
Calcite Web 项目常见问题解决方案
开发语言·前端·rust
倔强的石头1061 小时前
【C++指南】类和对象(九):内部类
开发语言·c++
Watermelo6171 小时前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
半盏茶香2 小时前
在21世纪的我用C语言探寻世界本质 ——编译和链接(编译环境和运行环境)
c语言·开发语言·c++·算法