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

#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

#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

#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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

清风玉骨

爱了!

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

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

打赏作者

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

抵扣说明:

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

余额充值