QT 学习笔记(十五)

由于每次代码都是在原有程序上修改,因此除了新建项目,不然一般会在学完后统一展示代码。
提示:具体项目创建流程和注意事项见QT 学习笔记(一)
提示:具体项目准备工作和细节讲解见QT 学习笔记(二)

一、UDP 通信过程

1. Linux 下的 UDP 通信过程

在这里插入图片描述

2. QT 下的 UDP 通信过程

在这里插入图片描述

  • 在过程当中,使用 QT 提供的 QUdpSocket 进行 UDP 通信。
  • 在 UDP 方式下,客户端并不与服务器建立连接,它只负责调用发送函数向服务器发送数据。类似的服务器也不从客户端接收连接,只负责调用接收函数,等待来自客户端的数据的到达。

3. 在 QT 中实现 UDP 通信的流程

  • 在 UDP 通信中,服务器端和客户端的概念已经显得有些淡化,两部分做的工作都大致相同:
  • (1) 创建套接字。
  • (2) 绑定套接字(包括下面两部分):
  • 第一部分:在 UDP 中如果需要接收数据则需要对套接字进行绑定,只发送数据则不需要对套接字进行绑定。
  • 第二部分:通过调用 bind() 函数将套接字绑定到指定端口上。
  • (3) 接收或者发送数据
  • 接收数据介绍如下:使用 readDatagram() 接收数据,函数声明如下:
qint64	readDatagram(char * data, qint64 maxSize, 
QHostAddress * address = 0, quint16 * port = 0)
  • 该函数声明的参数介绍如下:
参数含义
data接收数据的缓存地址
maxSize:缓存接收的最大字节数
address:数据发送方的地址(一般使用提供的默认值)
port:数据发送方的端口号(一般使用提供的默认值)
  • 使用 pendingDatagramSize() 可以获取到将要接收的数据的大小,根据该函数返回值来准备对应大小的内存空间存放将要接收的数据。
  • 发送数据介绍如下: 使用 writeDatagram() 函数发送数据,函数声明如下:
qint64	writeDatagram(const QByteArray & datagram, 
const QHostAddress & host, quint16 port)
  • 该函数声明的参数介绍如下:
参数含义
datagram要发送的字符串
host数据接收方的地址
port数据接收方的端口号

4. TCP/IP 和 UDP的区别

TCP/IPUDP
是否连接面向连接无连接
传输方式基于流基于数据报
传输可靠性可靠不可靠
传输效率效率低效率高
能否广播不能

二、UDP 文本发送

  • 生成一个新的项目,具体步骤过程见提示。

1. UDP 文本发送实例演示

  • 在 UDP 通信的过程当中,没有服务器端和客户端之分,二者集成为一个东西。
  • 在生成新项目的过程中,我们选择基类为 QWidget ,这是因为 QWidget 当中比较干净,不存在别的东西,而 QMainWindow 虽然也可以实现,但其中存在工具栏,菜单栏,核心控件等东西,比较复杂。
  • 首先,我们在 ui 界面布置出所需要的窗口界面,包含两个标签(分别是对方的 IP 和对方的端口)以及对应的行编辑区,两个按钮(发分别是送 send 和 关闭 close)和一个文本编辑区(用来输入和显示),具体界面布局如下图所示。

在这里插入图片描述

  • 随后,我们在 UDP.pro 当中加入 QT += network 代码,以便后续工作可以顺利开展。
  • 这里使用传统的 connect 函数,通过编写槽函数实现,不选择使用 Lambda 表达式,这里需要注意的是由于信号没有参数,所以槽函数也没有参数。
  • 先进行接收信息槽函数的代码编写,具体实现代码如下。
//接收数据的槽函数
void Widget::dealMsg()
{
    //先读取对方发送的内容
    char buf[1024] = {0};
    QHostAddress cliAddr;    //对方地址
    quint16 port;    //对方端口
    qint64 len = udpsocket->readDatagram(buf,sizeof(buf),&cliAddr,&port);
    if(len > 0)
    {
        //格式化 [192.68.2.2;8888]aaaa
        QString str = QString("[%1:%2] %3")
                .arg(cliAddr.toString())
                .arg(port)
                .arg(buf);
        //给编辑区设置内容
        ui->textEdit->setText(str);
    }
}
  • 然后,在 ui 界面通过按钮 send 转到槽函数操作,进行发送信息的代码编写,具体实现代码如下。
//发送按钮
void Widget::on_pushButtonsend_clicked()
{
    //先获取对方的IP和端口
    QString ip = ui->lineEditIP->text();
    qint16 port = ui->lineEditport->text().toInt();

    //获取编辑区内容
    QString str = ui->textEdit->toPlainText();

    //给指定的IP发送数据
    udpsocket->writeDatagram(str.toUtf8(),QHostAddress(ip),port);
}
  • 在完成代码编写后,我们就会得到一个 UDP 通信界面,通过输入别人的 IP 和端口(为自己设定),便可以进行 UDP 通信。

在这里插入图片描述

  • 在这里只有我自己的一个 IP 和端口,没有别人的进行通信,故具体现象无法展示。

2. UDP 广播

  • 使用 UDP 广播发送消息的时候会发给所有用户。
  • 在使用 QUdpSocket 类的 writeDatagram() 函数发送数据的时候,其中第二个参数 host 应该指定为广播地址:QHostAddress::Broadcast 此设置相当于 QHostAddress(“255.255.255.255”)。
  • 使用 UDP 广播的的特点:
  • (1) 使用 UDP 进行广播,局域网内的其他的 UDP 用户全部可以收到广播的消息。
  • (2) UDP 广播只能在局域网范围内使用。

3. UDP 组播

  • 我们再使用广播发送消息的时候会发送给所有用户,但是有些用户是不想接受消息的,这时候我们就应该使用组播,接收方只有先注册到组播地址中才能收到组播消息,否则则接受不到消息。
  • 另外组播是可以在 Internet 中使用的。
  • 在使用 QUdpSocket 类的 writeDatagram() 函数发送数据的时候,其中第二个参数 host 应该指定为组播地址,关于组播地址的分类介绍如下。
地址含义
224.0.0.0~224.0.0.255为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其它地址供路由协议使用。
224.0.1.0~224.0.1.255是公用组播地址,可以用于Internet。
224.0.2.0~238.255.255.255为用户可用的组播地址(临时组地址),全网范围内有效。
239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效。
  • 注册加入到组播地址需要使用 QUdpSocket 类的成员函数:
bool joinMulticastGroup(const QHostAddress & groupAddress)
  • 但是如果我们直接加入某个组播时,会出现如下情况,虽然可以编译通过,但仍存在错误。

在这里插入图片描述

  • 对此,我们按照错误描述进行修改,用 QHostAddress::AnyIPv4 代替最开始绑定的地址即可。
//绑定
//udpsocket->bind(8888);
udpsocket->bind(QHostAddress::AnyIPv4,8888);

三、UDP 文本发送实现代码

1. 主窗口头文件 widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QUdpSocket>//UDP套接字

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

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

    //槽函数,处理对方发过来的数据(因为信号没有参数,所以槽函数也没有参数)
    void dealMsg();

private slots:
    void on_pushButtonsend_clicked();

private:
    Ui::Widget *ui;

    //UDP套接字
    QUdpSocket *udpsocket;
};

#endif // WIDGET_H

2. 主窗口源文件 widget.cpp

#include "widget.h"
#include "ui_widget.h"
#include <QHostAddress>

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

    //分配空间,指定父对象(便于回收空间)
    udpsocket = new QUdpSocket(this);

    //绑定
    //udpsocket->bind(8888);
    udpsocket->bind(QHostAddress::AnyIPv4,8888);

    //加入某个组播
    //组播地址是D类地址
    udpsocket->joinMulticastGroup(QHostAddress("224.0.0.2"));
    //退出组播
    //udpsocket->leaveMulticastGroup();

    //标题
    setWindowTitle("服务器端口为:8888");

    //当对方成功发送数据过来
    //自动触发 readyRead()
    connect(udpsocket,&QUdpSocket::readyRead,this,&Widget::dealMsg);

}

//接收数据的槽函数
void Widget::dealMsg()
{
    //先读取对方发送的内容
    char buf[1024] = {0};
    QHostAddress cliAddr;    //对方地址
    quint16 port;    //对方端口
    qint64 len = udpsocket->readDatagram(buf,sizeof(buf),&cliAddr,&port);
    if(len > 0)
    {
        //格式化 [192.68.2.2;8888]aaaa
        QString str = QString("[%1:%2] %3")
                .arg(cliAddr.toString())
                .arg(port)
                .arg(buf);
        //给编辑区设置内容
        ui->textEdit->setText(str);
    }
}

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

//发送按钮
void Widget::on_pushButtonsend_clicked()
{
    //先获取对方的IP和端口
    QString ip = ui->lineEditIP->text();
    qint16 port = ui->lineEditport->text().toInt();

    //获取编辑区内容
    QString str = ui->textEdit->toPlainText();

    //给指定的IP发送数据
    udpsocket->writeDatagram(str.toUtf8(),QHostAddress(ip),port);
}

四、QTimer 定时器的使用

  • 生成一个新的项目,具体步骤过程见提示。

1. QTimer 定时器使用实例演示

  • 这里不需要在 ui 界面进行布局操作,可以直接对 QTimer 定时器进行使用。
  • QTimer 定时器需要位于 ui 界面的 Display Widgets 当中的 LCD Number 和两个按钮(分别是 start 和 stop),具体界面如下图所示。

在这里插入图片描述

  • 首先,我们需要创建一个定时器对象。
  • 随后,当我们按下按钮 start 开始计时,每过设定的时间间隔自动触发 timeout() ,按下按钮 stop 结束计时(这中间使用 Lambda 表达式,需要提前在 QTimer.pro 当中加入 CONFIG += C++11 代码)。
  • 对两个按钮分别进行转到槽函数的操作,进行代码的编写。
  • 当我们按下按钮 start 时,QTimer 定时器的开始启动,其具体现象如下图所示。

在这里插入图片描述

  • 当我们按下按钮 stop 时,QTimer 定时器的停止工作,这里再次点击按钮 start,QTimer 定时器仍会继续启动,其具体现象如下图所示。

在这里插入图片描述

2. QTimer 定时器使用实现代码

2.1 主窗口头文件 widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
//定时器对象头文件
#include <QTimer>

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

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

private slots:
    void on_pushButton_clicked();

    void on_pushButtonstop_clicked();

private:
    Ui::Widget *ui;

    //创建定时器对象
    QTimer *mytimer;
};

#endif // WIDGET_H

2.1 主窗口源文件 widget.cpp

#include "widget.h"
#include "ui_widget.h"

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

    mytimer = new QTimer(this);

    connect(mytimer,&QTimer::timeout,
            [=]()
            {
                static int i = 0;
                i++;
                ui->lcdNumber->display(i);
            }
            );
}

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

void Widget::on_pushButton_clicked()
{
    //启动定时器
    //时间间隔为100ms
    //每隔100ms,定时器mytimer内容自动触发timeout()
    //如果定时器没有激活才启动
    if(mytimer->isActive() == false)
    {
        mytimer->start(100);
    }    
}

void Widget::on_pushButtonstop_clicked()
{
    //如果定时器已经激活才关闭
    if(true == mytimer->isActive())
    {
        mytimer->stop();
    }
}    
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

虚心求知的熊

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值