用户数据报协议(User Data Protocol,UDP)是一种简单轻量级、不可靠、面向数据报、无连接的传输层协议,可以应用在可靠性不是十分重要的场合,如短消息、广播信息等。
适合应用的情况有一下几种:
- 网络数据大多为短消息;
- 拥有大量客户端;
- 对数据安全性无特殊要求;
- 网络负担非常重,但对响应速度要求高。
一、UDP协议工作原理
如图所示,UDP客户端向UDP服务器发送一定长度的请求报文,报文大小的限制与各系统的协议实现有关,但不得超过其下层IP协议规定的64KB;UDP服务器同样以报文形式作出响应。如果服务器未收到此请求,客户端不会进行重发,因此报文的传输是不可靠的。
二、UDP编程模型
下面介绍基于UDP协议的经典编程模型,程序编写的通用流程如图所示。
可以看出,在UDP方式下客户端并不与服务器建立连接,它只负责调用发送函数向服务器发出数据报。类似地,服务器也不从客户端接收连接,只负责调用接收函数,等待来自某客户端的数据到达。
Qt中通过QUdpSocket类实现UDP协议的编程。接下来通过一个实例,介绍如何实现基于UDP协议的广播应用,它由UDP服务器和UDP客户端两部分组成。
三、UDP服务器编程实例
- 工程文件
QT += network
- 头文件
#include <QDialog> #include <QLabel> #include <QLineEdit> #include <QPushButton> #include <QVBoxLayout> #include <QUdpSocket> #include <QTimer> #include <QHostAddress> class UdpServer : public QDialog { Q_OBJECT public: UdpServer(QWidget *parent = 0,Qt::WindowFlags f=0); ~UdpServer(); public slots: void StartBtnClicked(); void timeout(); private: QLabel *TimerLabel; QLineEdit *TextLineEdit; QPushButton *StartBtn; QVBoxLayout *mainLayout; int port; bool isStarted; QUdpSocket *udpSocket; QTimer *timer; };
- 源文件
#include "udpserver.h" UdpServer::UdpServer(QWidget *parent,Qt::WindowFlags f) : QDialog(parent,f) { setWindowTitle(tr("UDP Server")); TimerLabel = new QLabel(tr("计时器:"),this); TextLineEdit = new QLineEdit(this); StartBtn = new QPushButton(tr("开始"),this); mainLayout = new QVBoxLayout(this); mainLayout->addWidget(TimerLabel); mainLayout->addWidget(TextLineEdit); mainLayout->addWidget(StartBtn); port = 5555; isStarted = false; udpSocket = new QUdpSocket(this); timer = new QTimer(this); connect(StartBtn,SIGNAL(clicked(bool)),this,SLOT(StartBtnClicked())); connect(timer,SIGNAL(timeout()),this,SLOT(timeout())); } UdpServer::~UdpServer() { } void UdpServer::StartBtnClicked() { if(!isStarted) { StartBtn->setText(tr("停止")); timer->start(1000); isStarted = true; } else { StartBtn->setText(tr("开始")); isStarted = false; timer->stop(); } } void UdpServer::timeout() { QString msg = TextLineEdit->text(); int length = 0; if(msg=="") return; QByteArray sendBuff = msg.toUtf8(); length=udpSocket->writeDatagram(sendBuff,sendBuff.length(), QHostAddress::Broadcast,port); if(length !=msg.length()) return; }
其中,
- setWindowTitle(tr("UDP Server")):设置窗体的标题。
- TimerLabel = new QLabel(tr("计时器: "),this)到mainLayout->addWidget(StartBtn)这段代码:初始化了各个控件并设置布局。
- port =5555:设置UDP的端口号参数,服务器定时向此端口发送广播信息。
- timer = new QTimer(this):创建一个QUdpSocket。
- connect(timer,SIGNAL(timeout()),this,SLOT(timeout())):定时发送广播信息。
- QHostAddress::Broadcast:指定向广播地址发送。
注意:发送消息时需要转成UTF-8,否则发送中文字符时客户端会受到乱码!
四、UDP客户端编程实例
- 工程文件
QT += network
- 头文件
#include <QDialog> #include <QVBoxLayout> #include <QTextEdit> #include <QPushButton> #include <QMessageBox> #include <QUdpSocket> #include <QHostAddress> class UdpClient : public QDialog { Q_OBJECT public: UdpClient(QWidget *parent = 0,Qt::WindowFlags f=0); ~UdpClient(); public slots: void CloseBtnClicked(); void dateReceived(); private: QTextEdit *ReceiveTextEdit; QPushButton *CloseBtn; QVBoxLayout *mainLayout; int port; QUdpSocket *udpSocket; };
- 源文件
#include "udpclient.h" UdpClient::UdpClient(QWidget *parent,Qt::WindowFlags f) : QDialog(parent,f) { setWindowTitle(tr("UDP Client")); ReceiveTextEdit = new QTextEdit(this); CloseBtn = new QPushButton(tr("close"),this); mainLayout = new QVBoxLayout(this); mainLayout->addWidget(ReceiveTextEdit); mainLayout->addWidget(CloseBtn); port = 5555; udpSocket = new QUdpSocket(this); connect(CloseBtn,SIGNAL(clicked(bool)),this,SLOT(CloseBtnClicked())); connect(udpSocket,SIGNAL(readyRead()),this,SLOT(dateReceived())); bool result = udpSocket->bind(port); if(!result) { QMessageBox::information(this,tr("error"),tr("udp socket create error!")); return; } } UdpClient::~UdpClient() { } void UdpClient::CloseBtnClicked() { close(); } void UdpClient::dateReceived() { while (udpSocket->hasPendingDatagrams()) { QByteArray datagram; datagram.resize(udpSocket->pendingDatagramSize()); udpSocket->readDatagram(datagram.data(),datagram.size()); QString msg = QString::fromUtf8(datagram.data()); ReceiveTextEdit->insertPlainText(msg); } }
其中,
- setWindowTitle(tr("UDPClient"')):设置窗体的标题。
- ReceiveTextEdit = new QTextEdit(this)到 mainLayout->addWidget(CloseBtn)这段代码:初始化各个控件并设置布局。
- port =5555:设置UDP的端口号参数,指定在此端口上监听数据。
- udpSocket = new QUdpSocket(this):创建一个QUdpSocket。
- connect(udpSocket,SIGNAL(readyRead()),this,SLOT(dateReceived())) :连接QIODevice的readyRead()信号。QUdpSocket 也是一个IO设备,从QIODevice继承而来,当有数据到达I/O设备时,发出readyRead()信号。
- bool result=udpSocket->bind(port):绑定到指定的端口上。
- while(udpSocket->hasPendingDatagrams()):判断UdpSocket中是否有数据报可读,hasPendingDatagrams()方法在至少有一个数据报可读时返回true,否则返回false。
- QByteArray datagram到udpSocket->readDatagram(datagram.data(),datagram.size())这段代码:实现了读取第一个数据报,pendingDatagramSize()可以获得第一个数据报的长度。
- ReceiveTextEdit->insertPlainText(msg):显示数据内容。