由于每次代码都是在原有程序上修改,因此除了新建项目,不然一般会在学完后统一展示代码。
提示:具体项目创建流程和注意事项见
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/IP | UDP |
---|
是否连接 | 面向连接 | 无连接 |
传输方式 | 基于流 | 基于数据报 |
传输可靠性 | 可靠 | 不可靠 |
传输效率 | 效率低 | 效率高 |
能否广播 | 不能 | 能 |
二、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)
{
QString str = QString("[%1:%2] %3")
.arg(cliAddr.toString())
.arg(port)
.arg(buf);
ui->textEdit->setText(str);
}
}
- 然后,在 ui 界面通过按钮 send 转到槽函数操作,进行发送信息的代码编写,具体实现代码如下。
void Widget::on_pushButtonsend_clicked()
{
QString ip = ui->lineEditIP->text();
qint16 port = ui->lineEditport->text().toInt();
QString str = ui->textEdit->toPlainText();
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(QHostAddress::AnyIPv4,8888);
三、UDP 文本发送实现代码
1. 主窗口头文件 widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QUdpSocket>
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;
QUdpSocket *udpsocket;
};
#endif
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(QHostAddress::AnyIPv4,8888);
udpsocket->joinMulticastGroup(QHostAddress("224.0.0.2"));
setWindowTitle("服务器端口为:8888");
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)
{
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()
{
QString ip = ui->lineEditIP->text();
qint16 port = ui->lineEditport->text().toInt();
QString str = ui->textEdit->toPlainText();
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
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()
{
if(mytimer->isActive() == false)
{
mytimer->start(100);
}
}
void Widget::on_pushButtonstop_clicked()
{
if(true == mytimer->isActive())
{
mytimer->stop();
}
}