文章目录
一、UDP 通信过程
1. Linux 下的 UDP 通信过程
2. QT 下的 UDP 通信过程
3. 在 QT 中实现 UDP 通信的流程
4. TCP/IP 和 UDP的区别
二、UDP 文本发送
1. UDP 文本发送实例演示
2. UDP 广播
3. UDP 组播
三、UDP 文本发送实现代码
1. 主窗口头文件 widget.h
2. 主窗口源文件 widget.cpp
四、QTimer 定时器的使用
1. QTimer 定时器使用实例演示
2. QTimer 定时器使用实现代码
2.1 主窗口头文件 widget.h
2.1 主窗口源文件 widget.cpp
由于每次代码都是在原有程序上修改,因此除了新建项目,不然一般会在学完后统一展示代码。
提示:具体项目创建流程和注意事项见
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) { //格式化 [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 应该指定为组播地址,关于组播地址的分类介绍如下
- 注册加入到组播地址需要使用 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 定时器使用实例演示
首先,我们需要创建一个定时器对象。
随后,当我们按下按钮 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(); } }