学习目标:使用udp通信
前置环境
运行环境:qt creator 4.12
学习内容
UDP 协议基础知识
1、UDP(用户数据报协议)是轻量的、不可靠的、面向数据报、无连接的协议,用于可靠性要求不高的场合。两个应用程序之间进行UDP 通信不需先建立持久的 socket 连接,UDP 每次发送数据报都需要指定目标地址和端口。
2.ÙDP 报文没有可靠性保证、顺序保证和流量控制字段等,可靠性一较差。但是正因为 UDP 协议的控制选项较少在数据传输过程中延迟小、数据传输效率高,适合对可靠性要求不高的应用程序,或者可以保障可靠性的应用程序,如 DNS、TFTP、SNMP 等。
3、UDP 报头由 4个域组成,其中每个域各占用 2 个字节,具体包括
源端口号、目标端口号、数据包长度、校验值。端口号有效范围0--65535,假设端口号大于 49151的端口都代表动态端口
4、QUdpSocket类从QAbstractSocket类继承 基本跟QTcpSocket共用大部分的接口函数,主要区别在于 QUdpSocket 以数据报传输数据,不是以连续的数据流,发送方发送数据报使用函数QUdpSocket::writeDataGram(),数据报长度一般不超过 512 个字节,每个数据报包含发送方和接收方的 IP 地址和端口等数据信息。
QUdpSocket是Qt中用于UDP网络通信的类,它提供了以下一些常用的成员函数:
-
bind(const QHostAddress &address, quint16 port)
: 绑定到指定的地址和端口。 -
bind(quint16 port, BindMode mode = DefaultForPlatform)
: 绑定到指定的端口,并设置绑定模式。 -
writeDatagram(const QByteArray &datagram, const QHostAddress &host, quint16 port)
: 将指定的数据报发送到指定的主机和端口。 -
writeDatagram(const char *data, qint64 size, const QHostAddress &host, quint16 port)
: 将指定大小的数据发送到指定的主机和端口。 -
readDatagram(char *data, qint64 maxSize, QHostAddress *address = nullptr, quint16 *port = nullptr)
: 从接收缓冲区中读取一个数据报,并将其存储在指定的缓冲区中。 -
pendingDatagramSize()
: 返回下一个待读取的数据报的大小。 -
hasPendingDatagrams()
: 检查是否有待读取的数据报。 -
joinMulticastGroup(const QHostAddress &groupAddress)
: 加入一个多播组。 -
leaveMulticastGroup(const QHostAddress &groupAddress)
: 退出一个多播组。 -
setMulticastInterface(const QNetworkInterface &interface)
: 设置用于多播的网络接口。 -
multicastInterface()
: 返回用于多播的网络接口。 -
setSocketOption(SocketOption option, const QVariant &value)
: 设置套接字选项。 -
socketOption(SocketOption option)
: 返回指定的套接字选项的值。 -
state()
: 返回套接字的当前状态。 -
error()
: 返回最近一次发生的错误。 -
abort()
用于中断或终止当前的 UDP 网络操作。可以确保QUdpSocket
实例回到一个干净、可重用的状态,而不会产生任何残留的网络操作。需要注意的是,abort()
函数只会中断当前正在进行的操作,而不会关闭套接字本身。如果需要彻底关闭套接字,可以调用close()
函数。-
立即中断正在进行的任何 UDP 发送或接收操作。
-
清空内部缓冲区,丢弃所有待发送或待接收的数据。
-
将套接字的状态设置为
QAbstractSocket::UnconnectedState
。
-
这些成员函数涵盖了UDP网络编程的各个方面,包括数据发送、数据接收、多播管理、套接字选项设置等。使用这些函数可以方便地实现UDP通信的各种功能。
实现项目
Qt udp通讯编程
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//获取当前主机ip 放到ip选项栏
QHostInfo info =QHostInfo::fromName(QHostInfo::localHostName());
QList<QHostAddress> addrs=info.addresses();
if(!addrs.empty()){
foreach(const QHostAddress& addr,addrs){
if(addr.protocol()==QAbstractSocket::IPv4Protocol){
ui->plainTextEdit->appendPlainText("本机当前ipv4:"+addr.toString());
ui->tarip->addItem(addr.toString());
}
}
}
udpsocket=new QUdpSocket(this);
ui->pushButton->setEnabled(true);
ui->pushButton_3->setEnabled(false);
//用作消息到达 处理消息
// QObject::connect(udpsocket,SIGNAL(readyRead()),this,SLOT(UdpSockerRecvData())); 老版本Qt4.0语法糖 5.0弃用但是仍然可使用
//QObject::connect(udpsocket,SIGNAL(QUdpSocket::readyRead()),this,SLOT(MainWindow::UdpSockerRecvData())); 新版本5.0 误区点
//新版本5.0
QObject:: connect(udpsocket, &QUdpSocket::readyRead, this, &MainWindow::UdpSockerRecvData);
}
void MainWindow::UdpSockerRecvData(){
while(udpsocket->hasPendingDatagrams())
{
QByteArray datagrams;
datagrams.resize(udpsocket->pendingDatagramSize());
QHostAddress paddress;
quint16 pport;
// 通过readDatagram()此函数读取数据报,
udpsocket->readDatagram(datagrams.data(),datagrams.size(),&paddress,&pport);
QString strs=datagrams.data();
QString peer="[From:"+paddress.toString()+":"+QString::number(pport)+"]:";
ui->plainTextEdit->appendPlainText(peer+strs);
qDebug()<<"UdpSockerRecvData:"<<peer;
}
}
MainWindow::~MainWindow()
{
delete ui;
udpsocket->close();
delete udpsocket;
}
void MainWindow::on_pushButton_clicked()//绑定端口
{
QString ip=ui->tarip->currentText();
qint16 port =ui->curport->value();
if(udpsocket->bind(QHostAddress(ip),port)){
ui->plainTextEdit->appendPlainText("**********绑定成功**********");
ui->plainTextEdit->appendPlainText("$$$$$$$$$$绑定端口$$$$$$$$$$:"+QString::number(udpsocket->localPort()));
ui->pushButton->setEnabled(false);
ui->pushButton_3->setEnabled(true);
qDebug()<<"绑定端口:"<<port;
}else ui->plainTextEdit->appendPlainText("**********绑定失败**********");
}
void MainWindow::on_pushButton_3_clicked()//解除绑定端口
{
qDebug()<<"解除绑定端口:"<<udpsocket->localPort();
udpsocket->abort();
ui->pushButton->setEnabled(true);
ui->pushButton_3->setEnabled(false);
ui->plainTextEdit->appendPlainText("**********已经停止服务**********");
}
void MainWindow::on_pushButton_2_clicked()//发送数据
{
// 获取目标IP地址 端口 发送消息内容
QString targetip=ui->tarip->currentText();
uint16_t targetport =ui->tarport->value();
QString msg =ui->editmsg->text();
qDebug()<<"发送数据:"<<targetip<<targetport;
QByteArray send =msg.toUtf8();
// 发送数据报信息
udpsocket->writeDatagram(send,QHostAddress(targetip),targetport);
ui->plainTextEdit->appendPlainText("[out]:"+msg);
ui->editmsg->clear();
}
void MainWindow::on_pushButton_4_clicked()//广播发送
{
qint16 tarport =ui->tarport->value();
QString msg =ui->editmsg->text();
QByteArray send =msg.toUtf8();
udpsocket->writeDatagram(send,QHostAddress::Broadcast,tarport);
ui->plainTextEdit->appendPlainText("[broadcast out]:"+msg);
ui->editmsg->clear();
ui->editmsg->setFocus(); // 将光标焦点定位到编辑框控件
}
总结
-
获取本地IP地址:
- 通过
QHostInfo::fromName(QHostInfo::localHostName())
获取本地主机信息 - 遍历
QHostInfo::addresses()
中的地址,找到 IPv4 协议的地址 - 将这些地址添加到
tarip
下拉框中供用户选择
- 通过
-
创建 UDP 套接字:
- 使用
QUdpSocket
类创建 UDP 套接字实例 - 将
readyRead
信号连接到UdpSockerRecvData
槽函数,用于处理接收到的数据
- 使用
-
绑定 UDP 端口:
- 在
on_pushButton_clicked
函数中,获取用户选择的 IP 地址和端口 - 使用
udpsocket->bind(QHostAddress(ip), port)
绑定 UDP 套接字 - 根据绑定结果更新 UI 状态和按钮可用性
- 在
-
接收 UDP 数据报:
- 在
UdpSockerRecvData
槽函数中,通过udpsocket->hasPendingDatagrams()
检查是否有待处理的数据报 - 使用
udpsocket->readDatagram()
读取数据报,获取发送方地址和端口 - 将接收到的消息显示在
plainTextEdit
窗口中
- 在
-
发送 UDP 数据报:
- 在
on_pushButton_2_clicked
函数中,获取用户输入的目标 IP 地址、端口和消息 - 使用
udpsocket->writeDatagram()
将消息发送到指定的目标 - 在
plainTextEdit
窗口中显示发送的消息
- 在
-
广播 UDP 数据报:
- 在
on_pushButton_4_clicked
函数中,获取用户输入的目标端口和消息 - 使用
udpsocket->writeDatagram(data, QHostAddress::Broadcast, port)
将消息广播到所有客户端 - 在
plainTextEdit
窗口中显示广播的消息
- 在
-
资源清理:
- 在
MainWindow
析构函数中,关闭 UDP 套接字并删除实例 - 确保资源得到正确释放,避免内存泄漏
- 在