UDP通讯的基本概念和特点
UDP(User Datagram Protocol,用户数据报协议)是TCP/IP协议族中的一种无连接协议,主要用于那些对实时性要求较高而可靠性要求较低的应用场景。UDP的主要特点包括:
无连接:UDP在发送数据之前不需要建立连接,减少了建立连接的开销。
不保证可靠传输:UDP不提供数据传输的可靠性保证,数据可能会丢失或重复。
面向报文:UDP以独立的数据报形式发送数据,每个数据报都是独立的,并且保留了数据的边界。
首部开销小:UDP的首部开销非常小,只有8个字节,适合传输小数据包。
支持多种通信模式:UDP支持一对一、一对多、多对一和多对多的通信模式。
UDP通讯的实现步骤
创建套接字:使用socket()函数创建一个UDP套接字。
绑定地址:使用bind()函数将套接字与本地地址绑定。
发送数据:使用sendto()函数发送数据包,需要指定目的地址和端口。
接收数据:使用recvfrom()函数接收数据包,可以获取发送方的地址和端口信息。
关闭套接字:使用close()函数关闭套接字,释放资源。
UDP通讯的应用场景
由于UDP具有无连接、快速传输的特点,它非常适合用于那些对实时性要求较高而可靠性要求较低的应用场景,例如:
视频会议:视频流传输需要低延迟,UDP可以满足这一需求。
在线游戏:游戏中的实时交互需要快速的数据传输,UDP可以减少延迟。
实时监控:监控系统需要实时传输视频和报警信息,UDP可以提供快速的数据传输。
多媒体直播:直播需要低延迟和高带宽利用率,UDP可以满足这些要求。
单播
一个UDP客户端发送数据报到指定地址和端口的另一UDP客户端,是一对一的数据传输。
组播(多播)
是一点对多点的通信,IPv6没有采用IPv4中的组播术语,而是将广播看成是多播的一个特殊例子。
多播与单播步骤是一样的,只有IP地址有所区别。
多播的地址是特定的,D类地址用于多播。D类IP地址就是多播IP地址,即224.0.0.0至239.255.255.255之间的IP地址,并被划分为局部连接多播地址、预留多播地址和管理权限多播地址3类:
1,局部多播地址:在224.0.0.0~224.0.0.255之间,这是为路由协议和其他用途保留的地址,路由器并不转发属于此范围的IP包
2,预留多播地址:在224.0.1.0~238.255.255.255之间,可用于全球范围(如Internet)或网络协议。
3,管理权限多播地址:在239.0.0.0~239.255.255.255之间,可供组织内部使用,类似于私有IP地址,不能用于Internet,可限制多播范围。
广播
广播与组播是一样的,只是ip地址有所不同,而且不用加入指定的组。单播的数据只是收发数据的特定主机进行处理,组播在特定组之间进行处理,而广播的数据整个局域网都进行处理。
广播可以理解为一个人通过广播喇叭对在场的全体说话,这样做的好处是通话效率高,信息一下子就可以传递到全体。
广播在网络中的应用较多,如客户机通过DHCP自动获得IP地址的过程就是通过广播来实现的。但是同单播和多播相比,广播几乎占用了子网内网络的所有带宽。拿开会打一个比方吧,在会场上只能有一个人发言,想象一下如果所有的人同时都用麦克风发言,那会场上就会乱成一锅粥。
在IP网络中,广播地址用IP地址"255.255.255.255"来表示,这个IP地址代表同一子网内所有的IP地址。
代码:
.h
csharp
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QUdpSocket>
#include <QTimer>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = nullptr);
~Widget();
void unicastOpen();
public slots:
void onSocketReadyRead();
private slots:
void on_openbtn_clicked();
void on_senbtn_clicked();
void on_autoBox_clicked(bool checked);
void on_leavebtn_clicked();
void on_typebox_activated(int index);
private:
Ui::Widget *ui;
QUdpSocket *m_socket = nullptr;
QTimer m_timer;
bool m_joinFg = false;
void multicastSendData();
void multicastReadData();
void multicastOpen();
void multicastLeave();
void broadcastSendData();
void broadcastReadData();
void broadcastOpen();
void broadcastClose();
void unicastClose();
void unicastReadData();
void unicastSendData();
void open();
void close();
void readData();
void sendData();
};
#endif // WIDGET_H
.cpp
csharp
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QNetworkInterface>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
///单播
//一个UDP客户端发送数据报到指定地址和端口的另一UDP客户端,是一对一的数据传输。
///组播(多播)
/*是一点对多点的通信,IPv6没有采用IPv4中的组播术语,而是将广播看成是多播的一个特殊例子。
多播与单播步骤是一样的,只有IP地址有所区别。
多播的地址是特定的,D类地址用于多播。D类IP地址就是多播IP地址,即224.0.0.0至239.255.255.255之间的IP地址,并被划分为局部连接多播地址、预留多播地址和管理权限多播地址3类:
1,局部多播地址:在224.0.0.0~224.0.0.255之间,这是为路由协议和其他用途保留的地址,路由器并不转发属于此范围的IP包
2,预留多播地址:在224.0.1.0~238.255.255.255之间,可用于全球范围(如Internet)或网络协议。
3,管理权限多播地址:在239.0.0.0~239.255.255.255之间,可供组织内部使用,类似于私有IP地址,不能用于Internet,可限制多播范围。*/
///广播
/*广播与组播是一样的,只是ip地址有所不同,而且不用加入指定的组。单播的数据只是收发数据的特定主机进行处理,组播在特定组之间进行处理,而广播的数据整个局域网都进行处理。
广播可以理解为一个人通过广播喇叭对在场的全体说话,这样做的好处是通话效率高,信息一下子就可以传递到全体。
广播在网络中的应用较多,如客户机通过DHCP自动获得IP地址的过程就是通过广播来实现的。但是同单播和多播相比,广播几乎占用了子网内网络的所有带宽。拿开会打一个比方吧,在会场上只能有一个人发言,想象一下如果所有的人同时都用麦克风发言,那会场上就会乱成一锅粥。
在IP网络中,广播地址用IP地址"255.255.255.255"来表示,这个IP地址代表同一子网内所有的IP地址。*/
m_socket = new QUdpSocket(this);
connect(m_socket,&QUdpSocket::readyRead,this,&Widget::onSocketReadyRead);
connect(&m_timer,&QTimer::timeout,this,[=](){
if (m_joinFg)
on_senbtn_clicked();
});
ui->ipedit1->setEnabled(false);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_openbtn_clicked()
{
if (ui->typebox->currentIndex() == 0)
multicastOpen();
else if (ui->typebox->currentIndex() == 1)
broadcastOpen();
else if (ui->typebox->currentIndex() == 2)
unicastOpen();
}
void Widget::on_leavebtn_clicked()
{
if (ui->typebox->currentIndex() == 0)
multicastLeave();
else if (ui->typebox->currentIndex() == 1)
broadcastClose();
else if (ui->typebox->currentIndex() == 2)
unicastClose();
}
void Widget::onSocketReadyRead()
{
if (ui->typebox->currentIndex() == 0)
multicastReadData();
else if (ui->typebox->currentIndex() == 1)
broadcastReadData();
else if (ui->typebox->currentIndex() == 2)
unicastReadData();
}
void Widget::on_senbtn_clicked()
{
if (ui->typebox->currentIndex() == 0)
multicastSendData();
else if (ui->typebox->currentIndex() == 1)
broadcastSendData();
else if (ui->typebox->currentIndex() == 2)
unicastSendData();
}
void Widget::multicastOpen()
{
m_socket->setSocketOption(QAbstractSocket::MulticastTtlOption,100);
QString ip = ui->ipedit1->text()+ui->ipedit2->text();
int port = ui->portedit->text().toInt();
if (m_socket->bind(QHostAddress::AnyIPv4,port,QUdpSocket::BindFlag::ShareAddress | QUdpSocket::BindFlag::ReuseAddressHint))
{
m_socket->joinMulticastGroup(QHostAddress(ip));
QString msg = "加入组播"+ip+":"+ui->portedit->text();
ui->msgedit->append(msg);
m_joinFg = true;
qDebug()<<msg;
}
else
{
ui->msgedit->append("加入组播失败");
m_joinFg = false;
}
}
void Widget::multicastLeave()
{
QString ip = ui->ipedit1->text()+ui->ipedit2->text();
m_socket->leaveMulticastGroup(QHostAddress(ip));
m_socket->abort();
ui->msgedit->append("退出组播");
ui->autoBox->setChecked(false);
m_joinFg = false;
m_timer.stop();
}
void Widget::multicastReadData()
{
while(m_socket->hasPendingDatagrams())
{
QByteArray datagram;
datagram.resize(m_socket->pendingDatagramSize());
QHostAddress peerAddr;
quint16 peerPort;
m_socket->readDatagram(datagram.data(),datagram.size(),&peerAddr,&peerPort);
QString str=datagram.data();
QString peer="[从 "+peerAddr.toString()+":"+QString::number(peerPort)+" 发送] ";
ui->msgedit->append(peer+str);
qDebug()<<peer+str;
}
}
void Widget::multicastSendData()
{
QString ip = ui->ipedit1->text()+ui->ipedit2->text();
m_socket->writeDatagram(ui->sendedit->text().toUtf8(),QHostAddress(ip),ui->portedit->text().toInt());
}
void Widget::broadcastOpen()
{
int port = ui->portedit->text().toInt();
if (m_socket->bind(QHostAddress::AnyIPv4,port,QUdpSocket::BindFlag::ShareAddress | QUdpSocket::BindFlag::ReuseAddressHint))
{
QString msg = "打开广播"+ui->portedit->text();
ui->msgedit->append(msg);
m_joinFg = true;
qDebug()<<msg;
}
else
{
ui->msgedit->append("打开广播失败");
m_joinFg = false;
}
}
void Widget::broadcastClose()
{
m_socket->abort();
ui->msgedit->append("退出广播");
ui->autoBox->setChecked(false);
m_joinFg = false;
m_timer.stop();
}
void Widget::broadcastReadData()
{
//读取数据
while(m_socket->hasPendingDatagrams())//hasPendingDatagrams()如果至少有一个数据报等待读取,则返回true;
{
QByteArray datagram;
datagram.resize(m_socket->pendingDatagramSize());
QHostAddress peerAddr;
quint16 peerPort;
m_socket->readDatagram(datagram.data(),datagram.size(),&peerAddr,&peerPort);
QString str=datagram.data();
QString peer="[从 "+peerAddr.toString()+":"+QString::number(peerPort)+" 发送] ";
ui->msgedit->append(peer+str);
qDebug()<<peer+str;
}
}
void Widget::broadcastSendData()
{
m_socket->writeDatagram(ui->sendedit->text().toUtf8(),QHostAddress::Broadcast,ui->portedit->text().toInt());
}
void Widget::unicastOpen()
{
if (m_socket->bind(QHostAddress::LocalHost,ui->portedit->text().toInt()))
{
QString msg = "打开单播";
ui->msgedit->append(msg);
m_joinFg = true;
qDebug()<<msg;
}
else
{
ui->msgedit->append("打开单播失败");
m_joinFg = false;
}
}
void Widget::unicastClose()
{
m_socket->abort();
ui->msgedit->append("退出单播");
ui->autoBox->setChecked(false);
m_joinFg = false;
m_timer.stop();
}
void Widget::unicastReadData()
{
//读取数据
while(m_socket->hasPendingDatagrams())//hasPendingDatagrams()如果至少有一个数据报等待读取,则返回true;
{
QByteArray datagram;
datagram.resize(m_socket->pendingDatagramSize());
QHostAddress peerAddr;
quint16 peerPort;
m_socket->readDatagram(datagram.data(),datagram.size(),&peerAddr,&peerPort);
QString str=datagram.data();
QString peer="[从 "+peerAddr.toString()+":"+QString::number(peerPort)+" 发送] ";
ui->msgedit->append(peer+str);
qDebug()<<peer+str;
}
}
void Widget::unicastSendData()
{
m_socket->writeDatagram(ui->sendedit->text().toUtf8(),QHostAddress::LocalHost,ui->portedit->text().toInt());
}
void Widget::on_autoBox_clicked(bool checked)
{
if (checked)
m_timer.start(200);
else
m_timer.stop();
}
void Widget::on_typebox_activated(int index)
{
if (index == 1 || index == 2)
ui->ipedit2->setEnabled(false);
}