发送和接收UDP数据报
QUdpSocket类可以用来发送和接收UDP数据报(datagram)。UDP是一种不可靠的,面向数据报的协议。一些应用层的协议使用UDP,因为它比TCP更加小巧轻便。采用UDP,数据是以包(数据报)的形式从一个主机发送到另一个主机的。这里并没有连接的概念,而且如果UDP包没有被成功投递,它不会向发送者报告任何错误。
我们将会通过Weather Balloon和Weather Station 这两个实例来看看在Qt应用程序中是如何使用UDP的。Weather Balloon应用程序模拟气象气球的功能,每2秒钟就发送个包含当前天气情况的UDP数据报(假设使用无线连接)。
class WeatherBalloon : public QPushButton
{
Q_OBJECT
public:
WeatherBalloon(QWidget *parent = 0);
double temperature() const;
double humidity() const;
double altitude() const;
private slots:
void sendDatagram();
private:
QUdpSocket udpSocket;
QTimer timer;
};
WeatherBalloon类派生自QPushButton。它借助QPushButton的QUdpSocket私有变量与WeatherStation进行通信。
WeatherBalloon::WeatherBalloon(QWidget *parent)
: QPushButton(tr("Quit"), parent)
{
connect(this, SIGNAL(clicked()), this, SLOT(close()));
connect(&timer, SIGNAL(timeout()), this, SLOT(sendDatagram()));
timer.start(2 * 1000);
setWindowTitle(tr("Weather Balloon"));
}
在构造函数中,利用一个QTimer来实现每2秒钟调用一次sendDatagram()。
void WeatherBalloon::sendDatagram()
{
QByteArray datagram;
QDataStream out(&datagram, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_3);
out << QDateTime::currentDateTime() << temperature() << humidity()
<< altitude();
udpSocket.writeDatagram(datagram, QHostAddress::LocalHost, 5824);
}
在sendDatagram()中,生成并发送一个包含当前日期、时间、温度、湿度和高度的数据报:
这个数据报是利用QUdpSocket::writeDatagram()发送的。writeDatagram()的第二个和第三个参数是IP 地址和另一端的端口号(Weather Station)。 对于这个实例,我们假设Weather Station 和Weather Balloon 运行在同一台机器上,所以使用127.0.0.1(QHostAddress::LocalHost)的IP地址,它是指定本地主机的特殊地址。
与QTcpSocket::connectToHost()不同,QUdpSocket::writeDatagram()不接受主机名称,而只能使用主机地址。如果想在这里把主机名称解析成为它的IP地址,有两种选择:在查找发生时阻塞,然后使用QHostnfo::fromNamne()静态函数;或者,使用QHostUnfo::lookupHost()静态函数。当查找完成时,它将立即返回,同时利用含有相应地址的:QHostInfo对象传递而调用槽。
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
WeatherBalloon balloon;
balloon.show();
return app.exec();
}
main()函数仅仅创建了一个WeatherBalloon对象,它既作为一个UDP端进行服务,也作为一个QPushButton显示在屏幕上。单击QPushButton,就可以退出应用程序。现在看一下WeatherStation客户端的源代码:
weatherballon.h
#ifndef WEATHERBALLOON_H
#define WEATHERBALLOON_H
#include <QPushButton>
#include <QTimer>
#include <QUdpSocket>
class WeatherBalloon : public QPushButton
{
Q_OBJECT
public:
WeatherBalloon(QWidget *parent = 0);
double temperature() const;
double humidity() const;
double altitude() const;
private slots:
void sendDatagram();
private:
QUdpSocket udpSocket;
QTimer timer;
};
#endif
weatherballon.cpp
#include <QtCore>
#include <QtNetwork>
#include <cstdlib>
#include "weatherballoon.h"
WeatherBalloon::WeatherBalloon(QWidget *parent)
: QPushButton(tr("Quit"), parent)
{
connect(this, SIGNAL(clicked()), this, SLOT(close()));
connect(&timer, SIGNAL(timeout()), this, SLOT(sendDatagram()));
timer.start(2 * 1000);
setWindowTitle(tr("Weather Balloon"));
}
double WeatherBalloon::temperature() const
{
return -20.0 + (2.0 * std::rand() / (RAND_MAX + 1.0));
}
double WeatherBalloon::humidity() const
{
return 20.0 + (2.0 * std::rand() / (RAND_MAX + 1.0));
}
double WeatherBalloon::altitude() const
{
return 7000 + (100.0 * std::rand() / (RAND_MAX + 1.0));
}
void WeatherBalloon::sendDatagram()
{
QByteArray datagram;
QDataStream out(&datagram, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_3);
out << QDateTime::currentDateTime() << temperature() << humidity()
<< altitude();
udpSocket.writeDatagram(datagram, QHostAddress::LocalHost, 5824);
}
main.cpp
#include <QApplication>
#include "weatherballoon.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
WeatherBalloon balloon;
balloon.show();
return app.exec();
}
现在看一下Weather Station客户端的源代码:
class WeatherStation : public QDialog
{
Q_OBJECT
public:
WeatherStation(QWidget *parent = 0);
private slots:
void processPendingDatagrams();
private:
QUdpSocket udpSocket;
QLabel *dateLabel;
QLabel *timeLabel;
QLabel *temperatureLabel;
QLabel *humidityLabel;
QLabel *altitudeLabel;
QLineEdit *dateLineEdit;
QLineEdit *timeLineEdit;
QLineEdit *temperatureLineEdit;
QLineEdit *humidityLineEdit;
QLineEdit *altitudeLineEdit;
};
WeatherStation类派生自QDialog。它监听一个特定的UDP端口,解析任何到来的数据报(来自于Weather Balloon),并且把它们的内容显示到5个只读的QLineEdit中。这里唯一需要注意的私有变量是QUdpSocket类型的udpSocket,我们将利用它来接收数据报。
WeatherStation::WeatherStation(QWidget *parent)
: QDialog(parent)
{
udpSocket.bind(5824);
connect(&udpSocket, SIGNAL(readyRead()),
this, SLOT(processPendingDatagrams()));
...
}
在构造函数中,首先把QUdpSocket绑定到WeatherBalloon所传送的端口。因为我们没有指定主机地址,套接字将在运行Weather Station的机器上接收发往任意IP地址的数据报。然后,将套接字的readyRead()信号与提取和显示数据的processPendingDatagrams()私有槽连接起来。
void WeatherStation::processPendingDatagrams()
{
QByteArray datagram;
do {
datagram.resize(udpSocket.pendingDatagramSize());
udpSocket.readDatagram(datagram.data(), datagram.size());
} while (udpSocket.hasPendingDatagrams());
QDateTime dateTime;
double temperature;
double humidity;
double altitude;
QDataStream in(&datagram, QIODevice::ReadOnly);
in.setVersion(QDataStream::Qt_4_3);
in >> dateTime >> temperature >> humidity >> altitude;
dateLineEdit->setText(dateTime.date().toString());
timeLineEdit->setText(dateTime.time().toString());
temperatureLineEdit->setText(tr("%1 C").arg(temperature));
humidityLineEdit->setText(tr("%1%").arg(humidity));
altitudeLineEdit->setText(tr("%1 m").arg(altitude));
}
当接收到数据报时,就调用processPendingDatagrams()槽。QUdpSocket 将收到的数据报进行排队并让我们可以一次一个地读取它们。通常情况下,应该只有一个数据报,但是不能排除在发射readyRead()信号前发送端连续发送一些数据报的可能性。如果那样的话,可以忽略除最后一个以外的其他所有数据报,因为之前的数据报包含的只是过期的天气情况。
pendingDatagramSize()函数返回第一个待处理的数据报的大小。从应用程序的角度来看,数据报总是作为一个单一的数据单元来发送和接收的。这意味着只要有任意字节的数据可用,就认为整个数据报都可以被读取。readDatagram()调用把第一个待处理的数据报的内容复制到指定的char*缓冲区中(如果缓冲区空间太小,就直接截断数据),并且前移至下一个待处理的数据报。一旦读取了所有的数据报,就把最后一个数据报(包含最新气象状况测量值的数据报)分解为几个部分,并且用新的数据来组装QLineEdit。
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
WeatherStation station;
station.show();
return app.exec();
}
最后,在main()中,我们创建并显示WeatherStation。
至此就完成了UDP发送端和接收端的代码。这个应用程序已经尽可能地简单了,它只是由Weather Balloon发送数据报,由Weather Station接收它们。在绝大多数实际应用中,这两个应用程序都需要通过它们的套接字读取和写入。QUdpSocket::writeDatagram()函数可以传递主机地址和端
口号,所以QUdpSocket可以从用bind()绑定的主机和端口读取数据,并且将其写入到其他的主机和端口。
weatherstation.h
#ifndef WEATHERSTATION_H
#define WEATHERSTATION_H
#include <QDialog>
#include <QUdpSocket>
class QLabel;
class QLineEdit;
class WeatherStation : public QDialog
{
Q_OBJECT
public:
WeatherStation(QWidget *parent = 0);
private slots:
void processPendingDatagrams();
private:
QUdpSocket udpSocket;
QLabel *dateLabel;
QLabel *timeLabel;
QLabel *temperatureLabel;
QLabel *humidityLabel;
QLabel *altitudeLabel;
QLineEdit *dateLineEdit;
QLineEdit *timeLineEdit;
QLineEdit *temperatureLineEdit;
QLineEdit *humidityLineEdit;
QLineEdit *altitudeLineEdit;
};
#endif
weatherstation.cpp
#include <QtGui>
#include <QtNetwork>
#include "weatherstation.h"
WeatherStation::WeatherStation(QWidget *parent)
: QDialog(parent)
{
udpSocket.bind(5824);
connect(&udpSocket, SIGNAL(readyRead()),
this, SLOT(processPendingDatagrams()));
dateLabel = new QLabel(tr("Date:"));
timeLabel = new QLabel(tr("Time:"));
temperatureLabel = new QLabel(tr("Temperature:"));
humidityLabel = new QLabel(tr("Humidity:"));
altitudeLabel = new QLabel(tr("Altitude:"));
dateLineEdit = new QLineEdit;
timeLineEdit = new QLineEdit;
temperatureLineEdit = new QLineEdit;
humidityLineEdit = new QLineEdit;
altitudeLineEdit = new QLineEdit;
dateLineEdit->setReadOnly(true);
timeLineEdit->setReadOnly(true);
temperatureLineEdit->setReadOnly(true);
humidityLineEdit->setReadOnly(true);
altitudeLineEdit->setReadOnly(true);
QGridLayout *mainLayout = new QGridLayout;
mainLayout->addWidget(dateLabel, 0, 0);
mainLayout->addWidget(dateLineEdit, 0, 1);
mainLayout->addWidget(timeLabel, 1, 0);
mainLayout->addWidget(timeLineEdit, 1, 1);
mainLayout->addWidget(temperatureLabel, 2, 0);
mainLayout->addWidget(temperatureLineEdit, 2, 1);
mainLayout->addWidget(humidityLabel, 3, 0);
mainLayout->addWidget(humidityLineEdit, 3, 1);
mainLayout->addWidget(altitudeLabel, 4, 0);
mainLayout->addWidget(altitudeLineEdit, 4, 1);
setLayout(mainLayout);
setWindowTitle(tr("Weather Station"));
}
void WeatherStation::processPendingDatagrams()
{
QByteArray datagram;
do {
datagram.resize(udpSocket.pendingDatagramSize());
udpSocket.readDatagram(datagram.data(), datagram.size());
} while (udpSocket.hasPendingDatagrams());
QDateTime dateTime;
double temperature;
double humidity;
double altitude;
QDataStream in(&datagram, QIODevice::ReadOnly);
in.setVersion(QDataStream::Qt_4_3);
in >> dateTime >> temperature >> humidity >> altitude;
dateLineEdit->setText(dateTime.date().toString());
timeLineEdit->setText(dateTime.time().toString());
temperatureLineEdit->setText(tr("%1 C").arg(temperature));
humidityLineEdit->setText(tr("%1%").arg(humidity));
altitudeLineEdit->setText(tr("%1 m").arg(altitude));
}
main.cpp
#include <QApplication>
#include "weatherstation.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
WeatherStation station;
station.show();
return app.exec();
}