一、UDP通信概述
UDP(User Datagram Protocol,用户数据报协议)是轻量的、不可靠的、面向数据报、无连接的协议,它可以用于对可靠性要求不高的场合。与TCP通信不同,两个程序之间进行UDP通信无需预先建立持久的socket连接,UDP每次发送数据报都需要制定目标地址和端口。
QUdpSocket类用于实现UDP通信,它从QAbstractSocket类继承,因而与QTcpSocket共享大部分接口函数。主要区别是QUdpSocket以数据报传输数据,而不是以连续的数据流。发送数据报使用函数QUdpSocket::writeDatagram(),数据报的长度一般少于512字节,每个数据报包含发送者和接收者的IP地址和端口等信息。
图 1-1
二、UDP的分类
UDP消息传送有单播、广播、组播三种模式。
图 2-1
单播模式:一个UDP客户端发出的数据报只发送到另一个制定地址和端口的UDP客户端,是一对一的数据传输。
广播模式:一个UDP客户端发出的数据报,在同一网络范围内其他所有的UDP客户端都可以收到。支持IPV4广播,是实现网络发现的协议。要获取广播数据只需要在数据报中制定接收端地址为QHostAddress::Broadcast,广播地址是:255.255.255.255。
组播模式:也称多播。UDP客户端加入到另一个组播IP地址制定的多播组,成员向组播地址发送的数据报组内成员都可以接收到,类似于QQ群的功能。QUdpSocket::joinMulticastGroup()函数实现加入组播的功能,加入组播后可以正常进行数据收发,UDP通信实现比较灵活,TCP通信只有单播模式,没有广播与组播模式。UDP通信不能保证数据的准确性,但具有灵活性,可用于即时通信。
三、UDP单播与广播
UDP单播主要是建立在不同端上的一对一非连接的通信,时效高,具有较好的灵活性;UDP广播主要是建立在不同端上的一对多的非连接的通信。
效果图:
图 3-1
图3-2
代码示例:
mainwindow.h:
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
void initWindow();
private slots:
void onSockStateChange(QAbstractSocket::SocketState socketState);
void onSockReadyRead();
void on_actStart_clicked();
void on_actStop_clicked();
void on_clearBtn_clicked();
void on_exitBtn_clicked();
void on_btnSend_clicked();
void on_btnBroadcast_clicked();
private:
QString getLocalIP();
private:
Ui::MainWindow *ui;
QLabel *_pLabSocketState;
QUdpSocket *_pUdpSocket;
};
#endif // MAINWINDOW_H
mainwindow.cpp:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include
#include
#include
namespace Ui {
class MainWindow;
}
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
initWindow();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::initWindow()
{
_pLabSocketState = new QLabel(QString::fromLocal8Bit("Socket状态: "));
_pLabSocketState->setMinimumWidth(200);
ui->statusBar->addWidget(_pLabSocketState);
QString localIP = getLocalIP();
QString sTitle = this->windowTitle() + QString::fromLocal8Bit("----本机IP:") + localIP;
this->setWindowTitle(sTitle);
ui->comboTargetIP->addItem(localIP);
ui->bindSpinBox->setRange(0, 8999);
ui->bindSpinBox->setValue(1200);
ui->targetSpinBox->setRange(0, 8999);
ui->targetSpinBox->setValue(3355);
_pUdpSocket = new QUdpSocket(this);
connect(_pUdpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onSockStateChange(QAbstractSocket::SocketState)));
connect(_pUdpSocket, SIGNAL(readyRead()), this, SLOT(onSockReadyRead()));
}
void MainWindow::onSockStateChange(QAbstractSocket::SocketState socketState)
{
switch (socketState)
{
case QAbstractSocket::UnconnectedState:
_pLabSocketState->setText(QString::fromLocal8Bit("socket 状态: UnconnectedState"));
break;
case QAbstractSocket::HostLookupState:
_pLabSocketState->setText(QString::fromLocal8Bit("socket 状态: HostLookupState"));
break;
case QAbstractSocket::ConnectingState:
_pLabSocketState->setText(QString::fromLocal8Bit("socket 状态: ConnectingState"));
break;
case QAbstractSocket::ConnectedState:
_pLabSocketState->setText(QString::fromLocal8Bit("socket 状态: ConnectedState"));
break;
case QAbstractSocket::BoundState:
_pLabSocketState->setText(QString::fromLocal8Bit("socket 状态: BoundState"));
break;
case QAbstractSocket::ClosingState:
_pLabSocketState->setText(QString::fromLocal8Bit("socket 状态: ClosingState"));
break;
case QAbstractSocket::ListeningState:
_pLabSocketState->setText(QString::fromLocal8Bit("socket 状态: ListeningState"));
break;
default:
break;
}
}
void MainWindow::onSockReadyRead()
{
//读取收到的数据报
while(_pUdpSocket->hasPendingDatagrams())
{
QByteArray datagram;
datagram.resize(_pUdpSocket->pendingDatagramSize());
QHostAddress peerAddr;
quint16 peerPort;
_pUdpSocket->readDatagram(datagram.data(), datagram.size(), &peerAddr, &peerPort);
QString str = datagram.data();
QString peer = "[From "+ peerAddr.toString() +":" + QString::number(peerPort) +"] ";
ui->plainTextEdit->appendPlainText(peer + str);
}
}
QString MainWindow::getLocalIP()
{
QString hostName = QHostInfo::localHostName(); //主机名
QHostInfo hostInfo = QHostInfo::fromName(hostName);
QString localIP = "";
QList
if(!addList.isEmpty())
{
for(int i = 0; i
QHostAddress hostAddr = addList.at(i);
if(QAbstractSocket::IPv4Protocol == hostAddr.protocol())
{
localIP = hostAddr.toString();
break;
}
}
}
return localIP;
}
void MainWindow::on_actStart_clicked()
{
quint16 port = ui->bindSpinBox->value();
if(_pUdpSocket->bind(port))
{
ui->plainTextEdit->appendPlainText(QString::fromLocal8Bit("**已成功绑定"));
QString sMsg = QString::fromLocal8Bit("**绑定端口:") + QString::number(_pUdpSocket->localPort());
ui->plainTextEdit->appendPlainText(sMsg);
ui->actStart->setEnabled(false);
ui->actStop->setEnabled(true);
}
else
{
ui->plainTextEdit->appendPlainText(QString::fromLocal8Bit("**绑定失败"));
}
}
void MainWindow::on_actStop_clicked()
{
_pUdpSocket->abort();
ui->actStart->setEnabled(true);
ui->actStop->setEnabled(false);
ui->plainTextEdit->appendPlainText(QString::fromLocal8Bit("**已解除绑定"));
}
void MainWindow::on_clearBtn_clicked()
{
ui->editMsg->clear();
ui->plainTextEdit->clear();
}
void MainWindow::on_exitBtn_clicked()
{
_pUdpSocket->abort();
this->close();
}
void MainWindow::on_btnSend_clicked() //单播
{
//发送
QString targetIP = ui->comboTargetIP->currentText();
QHostAddress targetAddr(targetIP);
quint16 targetPort = ui->targetSpinBox->value();
QString msg = ui->editMsg->text();
QByteArray str = msg.toUtf8();
_pUdpSocket->writeDatagram(str, targetAddr, targetPort);
ui->plainTextEdit->appendPlainText("[out] " + msg);
ui->editMsg->clear();
ui->editMsg->setFocus();
}
void MainWindow::on_btnBroadcast_clicked() //广播
{
//广播
quint16 targetPort = ui->targetSpinBox->value();
QString msg = ui->editMsg->text();
QByteArray str = msg.toUtf8();
_pUdpSocket->writeDatagram(str, QHostAddress::Broadcast, targetPort);
ui->plainTextEdit->appendPlainText("[broadcast] " + msg);
ui->editMsg->clear();
ui->editMsg->setFocus();
}
四、Udp组播
Udp组播是主机之间“一对一组”的通信模式,当多个客户端加入由一个组播地址定义的多播组之后,客户端向组播地址和端口发送的UDP数据报,组内成员都可以接收到。组播报文的目的地址使用D类IP地址,D类地址不能出现在IP报文的源IP地址字段。
说明:关于组播IP地址,有如下约定:
1)224.0.0.0~224.0.0.255为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其他地址供路由协议使用;
2)224.0.1.0~238.255.255.255是公用组播地址,可以用于Internet;
3)224.0.2.0~238.255.255.255为用户可用组播地址(临时组播地址),全网范围内有效;
4)239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效;
效果图:
图 4-1
图 4-2
代码示例:
mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include
#include
#include
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
void onSocketStateChange(QAbstractSocket::SocketState socketState);
void onSocketReadyRead();//读取socket传入的数据
void on_bindBtn_clicked();
void on_stopBtn_clicked();
void on_btnSend_clicked();
void on_clearBtn_clicked();
void on_quitBtn_clicked();
private:
QLabel* LabSocketState;//socket状态显示标签
QUdpSocket* udpSocket;
QString getLocalIp();//获取本机IP地址
QHostAddress groupAddress;//组播地址
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
mainwindow.cpp:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include
#include
#include
#include
#include
#include
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->comboTargetIp->addItem("224.0.0.1");
ui->comboTargetIp->addItem("239.255.43.21");
ui->comboTargetIp->setCurrentIndex(0);
LabSocketState = new QLabel(QString::fromLocal8Bit("Socket 状态"));
LabSocketState->setMinimumWidth(200);
ui->statusBar->addWidget(LabSocketState);
QString localIP = getLocalIp();//获取IP地址
this->setWindowTitle(this->windowTitle()+QString::fromLocal8Bit("---本机IP")+localIP);
udpSocket = new QUdpSocket;
udpSocket->setSocketOption(QAbstractSocket::MulticastTtlOption,1);//对socket进行参数设置
connect(udpSocket,SIGNAL(stateChanged(QAbstractSocket::SocketState)),this,SLOT(onSocketStateChange(QAbstractSocket::SocketState)));
onSocketStateChange(udpSocket->state());
connect(udpSocket,SIGNAL(readyRead()),this,SLOT(onSocketReadyRead()));
}
void MainWindow::on_bindBtn_clicked()
{//加入组播
QString IP = ui->comboTargetIp->currentText();
groupAddress = QHostAddress(IP);
quint16 groupPort = ui->spinBox->value();//端口
if(udpSocket->bind(QHostAddress::AnyIPv4,groupPort,QUdpSocket::ShareAddress))
{
udpSocket->joinMulticastGroup(groupAddress);//加入多播组
ui->plainTextEdit->append(QString::fromLocal8Bit("**加入组播成功"));
ui->plainTextEdit->append(QString::fromLocal8Bit("组播地址IP: ")+IP);
ui->plainTextEdit->append(QString::fromLocal8Bit("**绑定端口:") + QString::number(groupPort));
ui->bindBtn->setEnabled(false);
ui->stopBtn->setEnabled(true);
ui->comboTargetIp->setEnabled(false);
}
else
{
ui->plainTextEdit->append(QString::fromLocal8Bit("**绑定端口失败"));
}
}
void MainWindow::on_stopBtn_clicked()
{//退出组播
udpSocket->leaveMulticastGroup(groupAddress);//退出组播
udpSocket->abort();
ui->bindBtn->setEnabled(true);
ui->stopBtn->setEnabled(false);
ui->comboTargetIp->setEnabled(true);
ui->plainTextEdit->append(QString::fromLocal8Bit("**已退出组播,解除端口绑定"));
}
void MainWindow::on_btnSend_clicked()
{ //发送组播消息
QString msg = ui->lineEdit->text();
QByteArray array;
array = msg.toUtf8();
udpSocket->writeDatagram(array,groupAddress,groupPort);
ui->plainTextEdit->append("[multicst]"+msg);
ui->lineEdit->clear();
ui->lineEdit->setFocus();
}
void MainWindow::on_clearBtn_clicked()
{
ui->plainTextEdit->clear();
}
void MainWindow::on_quitBtn_clicked()
{
this->close();
}
void MainWindow::onSocketReadyRead()
{//读取数据报
QByteArray datagram;
datagram.resize(udpSocket->pendingDatagramSize());
QHostAddress peerAddr;
quint16 peerPort;
udpSocket->readDatagram(datagram.data(),datagram.size(),&peerAddr,&peerPort);
QString str =datagram.data();
QString peerStr = "[From"+peerAddr.toString()+":"+QString::number(peerPort)+"]";
ui->plainTextEdit->append(peerStr + str);
}
QString MainWindow::getLocalIp()
{//获取本机IP
QString hostName = QHostInfo::localHostName();//本机主机名
QHostInfo hostInfo = QHostInfo::fromName(hostName);
QString localIP = " ";
QList
if(!addList.isEmpty())
{
for(int i=0;i
QHostAddress aHost = addList.at(i);
if(QAbstractSocket::IPv4Protocol == aHost.protocol())
{
localIP = aHost.toString();
break;
}
}
}
return localIP;
}
void MainWindow::onSocketStateChange(QAbstractSocket::SocketState socketState)
{
//socket状态变化
switch(socketState)
{
case QAbstractSocket::UnconnectedState:
{
LabSocketState->setText(QString::fromLocal8Bit("socket状态:UnconnectedState"));
break;
}
case QAbstractSocket::HostLookupState:
{
LabSocketState->setText(QString::fromLocal8Bit("socket状态:HostLookupState"));
break;
}
case QAbstractSocket::ConnectingState:
{
LabSocketState->setText(QString::fromLocal8Bit("socket状态:ConnectingState"));
break;
}
case QAbstractSocket::ConnectedState:
{
LabSocketState->setText(QString::fromLocal8Bit("socket状态:ConnectedState"));
break;
}
case QAbstractSocket::BoundState:
{
LabSocketState->setText(QString::fromLocal8Bit("socket状态:BoundState"));
break;
}
case QAbstractSocket::ClosingState:
{
LabSocketState->setText(QString::fromLocal8Bit("socket状态:ClosingState"));
break;
}
case QAbstractSocket::ListeningState:
{
LabSocketState->setText(QString::fromLocal8Bit("socket状态:ListeningState"));
break;
}
}
}
MainWindow::~MainWindow()
{
delete ui;
udpSocket = NULL;
delete udpSocket;
}