Qt4_发送和接收UDP数据报

本文介绍了如何使用Qt的QUdpSocket在 WeatherBalloon 和 WeatherStation 之间发送和接收UDP数据报,展示了如何构造一个简单的气象气球应用,每2秒发送包含温度、湿度和高度的实时数据,以及一个接收端用于展示接收到的数据。
摘要由CSDN通过智能技术生成

发送和接收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();
}

在这里插入图片描述

  • 1
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阳光开朗男孩

你的鼓励是我最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值