嵌入式Linux应用程序开发-(9)UDP网络通信应用程序(UDP-Socket)

基于UDP协议的网络通信应用程序(UDP-Socket)

前两篇文章介绍了基于TCP/IP协议的网络通信应用程序。

嵌入式Linux应用程序开发-(7)TCP-IP网络通信应用程序(TCP-Client)

嵌入式Linux应用程序开发-(8)TCP-IP网络通信应用程序(TCP-Server)

        相比起TCP/IP协议的可靠,面向连接,基于字节流通信这些特性,UDP协议是一种轻量级,不可靠,基于数据报的传输协议。很多人会问,为什么UDP协议传输不可靠还要继续使用它?那是因为UDP协议使用起来很方便,不需要建立连接,资源消耗少,通信效率高,在线播放音频或者视频的时候,使用UDP协议比使用TCP/IP协议有更高的传输效率,因为在这种使用场景下,即使丢失一两个数据包,对结果都影响不大。在网络质量不佳的情况下,使用UDP协议传输可能丢包严重,开发者应该注意根据不同的场合选择合适的协议。

        对于UDP传输协议,数据报有以下三种传输方式:

单播:UDP单播传输,即发送方指定接收方的IP地址和端口号,把数据直接发送到对方。这种属于一对一的数据传输方式。

组播:接收方需要加入组播地址和端口号,发送方往组播的IP地址发送数据,只要是加入了组播地址的设备,都能收到数据,属于一对多的数据传输方式。

广播:接收方不需要加入广播地址,只指定接收的端口号,发送方往广播的IP地址发送数据,则局域网内的所有设备都能收到数据,属于一对多的数据传输方式。

        总的来说,不管是单播,组播,还是广播,对于发送方,每一次数据传输都需要指定接收方的IP地址和端口。而对于接收方,单播和广播的接收方式是一样的,都是监听局域网内所有的IP地址,组播则多了一个步骤,需要加入指定的组播地址才能接收组播数据。

        使用嵌入式QT进行UDP协议的应用程序开发,QT的network类库提供了QUdpSocket类,这个类继承于QAbstractSocket类,并提供了UDP通信的基本操作方法,如绑定函数bind(),加入组播joinMulticastGroup(),离开组播leaveMulticastGroup(),数据报的读写操作函数,等等。具体可以参阅 QtNetwork/qudpsocket.h 文件的内容。

目标:使用QT提供的UDP网络通信类,实现一个简单的Udp_Socket收发工具

功能:

(1)界面显示开发板本机IP地址。

(2)可手动输入接收端的IP地址和端口。

(3)可选择UDP数据报的发送方式:组播,单播,广播。

(4)界面显示UDP协议的网络收发数据,并提供清屏按钮。

(5)提供手动发送按钮和自动发送按钮。

开发板运行Udp_Socket收发工具后,界面如下图所示:

界面描述:

(1)Udp_Socket收发工具运行后,在界面显示开发板的IP地址。

(2)可输入接收端的IP地址和端口。

(3)选择UDP的发送方式,单播,组播或广播。

(4)点击 [tcp_send_data] ,往指定的IP地址和端口发送数据。

(5)点击 [START_AUTO_SEND] ,以一秒的频率间隔发送数据。

 

 

以下是Udp_Socket收发工具的开发过程。

(1)先用Qt Creator构建一个工程,命名为:009_udp_socket,关于如何构建工程,请参考“第一个嵌入式QT应用程序”的具体内容。

(2)创建工程后,修改009_udp_socket.pro里面的内容,添加QT里面的网络通信模块network,使工程支持QT网络类的调用,如下图所示。

(3)双击打开“widget.ui”文件,构建界面,构建后的界面如下图所示:

应用程序界面描述如上面内容所示,这里不作重复。

(4)对于Udp_Socket收发工具,我们使用一个类UdpSocket对其进行封装,这个类定义了UDP数据的收发函数,以及获取自身IP地址的函数,还定义了一个信号,用来通知ui界面接收到数据并显示。UdpSocket类的具体内容如下图所示:

class UdpSocket : public QUdpSocket
{
    Q_OBJECT
public:
    explicit UdpSocket(QObject *parent = 0,int port = 0);
    ~UdpSocket();

    QUdpSocket *udp_socket;
    QString get_local_ipaddr(void);   //获取本机的IP地址
    int listen_port;

public slots:
    void slot_udp_read_data();       //udp读取数据槽函数
    //udp发送数据槽函数
    void slot_udp_send_data(const QString ip, quint16 port, QByteArray data_send, unsigned char send_type);

signals:
    //数据接收信号,用于通知ui线程,让ui显示服务端接收的数据来自哪个客户端
    void signals_udp_receive_data(const QString ip, int port, const QByteArray &data);
};

(5)对于UdpSocket类,在其构造函数里,绑定监听的IP地址和端口号,并禁止在组播发送时自己接收数据。绑定QUdpSocket()类的readyRead()信号,一旦发现数据即调用槽函数进行处理。UdpSocket类的构造函数如下图所示。

//UdpSocket的构造函数
UdpSocket::UdpSocket(QObject *parent, int port) : QUdpSocket(parent)
{
    this->listen_port = port;

    udp_socket = new QUdpSocket();

    //绑定监听的端口号,监听所有的IP地址
    //udp_socket->bind((QHostAddress)get_local_ipaddr(),listen_port);
    udp_socket->bind(QHostAddress::Any, listen_port);
    udp_socket->setSocketOption(QAbstractSocket::MulticastLoopbackOption, 0);//禁止本机接收

    //绑定数据读取的槽函数
    connect(udp_socket, SIGNAL(readyRead()), this, SLOT(slot_udp_read_data()));
}

(6)当接收到UDP的数据报时,底层系统就会触发readRead()信号,进而调用数据接收的槽函数void UdpSocket::slot_udp_read_data()进行处理。这个函数不断读取缓冲区的数据,读取完成后,发送信号通知ui界面进行数据显示。数据接收槽函数如下图所示:

//udp数据读取的槽函数
void UdpSocket::slot_udp_read_data()
{
    QByteArray ba;
    QHostAddress address;
    quint16 port;

    //不断读取缓冲区的数据
    while(udp_socket->hasPendingDatagrams())
    {
        ba.resize(udp_socket->pendingDatagramSize());
        udp_socket->readDatagram(ba.data(), ba.size(),&address,&port);
    }

    //发送数据接收信号,告知ui界面显示数据内容
    emit signals_udp_receive_data(address.toString(),(int)port,ba);
}

(7)应用程序进行UDP数据发送时,可以调用QUdpSocket::writeDatagram()进行数据发送,对于单播,组播,广播这三种发送方式,需要对应不同的IP地址,UDP数据发送函数的具体实现,如下图所示:

//udp发送数据的槽函数
void UdpSocket::slot_udp_send_data(const QString ip, quint16 port, QByteArray data_send, unsigned char send_type)
{
    switch(send_type)
    {
        case TYPE_UNICAST:   //使用单播方式发送
            udp_socket->writeDatagram(data_send,QHostAddress(ip),port);
        break;

        case TYPE_GROUPCAST: //使用组播方式发送
            udp_socket->writeDatagram(data_send,QHostAddress(GROUPCAST_ADDR),port);
        break;

        case TYPE_BROADCAST:  //使用组播方式发送
            udp_socket->writeDatagram(data_send,QHostAddress::Broadcast,port);
        break;

        default:break;
    }
}

(8)以上就是UdpSocket类的主要函数实现,接下来,分析一下窗体构造Widget类的具体实现过程。

(9)在Widget类的构造函数里,我们使用UdpSocket类定义一个udp_socket对象,并且绑定数据处理的槽函数。构建一个定时器,用于自动发送数据。显示本机的IP地址。构建一个QButtonGroup类对象,并使界面上的radioButton实现互斥。如下图所示:

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    bool ok;
    int listen_port = ui->lineEdit_port->text().toInt(&ok,10);

    //定义一个udp_socket对象
    udp_socket = new UdpSocket(this,listen_port);

    //关联数据处理的槽函数
    connect( udp_socket, SIGNAL(signals_udp_receive_data(QString,int,QByteArray)), \
             this, SLOT(slot_recv_udp_data(QString,int,QByteArray)));

    //构建一个UDP自动发送数据的定时器
    auto_send_timer = new QTimer();
    //关联定时器超时槽函数
    connect( auto_send_timer, SIGNAL(timeout()), this, SLOT(slot_auto_send_timer_handler()));
    //显示本机的IP地址
    ui->label_local_ip_display->setText(udp_socket->get_local_ipaddr());

    //把radiobuttons加入buttonGroup
    buttongroup = new QButtonGroup(this);
    buttongroup->setExclusive(true);// 设置互斥
    buttongroup->addButton(ui->radioButton_unicast);
    buttongroup->addButton(ui->radioButton_groupcast);
    buttongroup->addButton(ui->radioButton_broadcast);

    //连接信号槽,radiobutton改变时作出处理
    connect(buttongroup, SIGNAL(buttonClicked(QAbstractButton*)), \
            this, SLOT(slot_radiobutton_clicked(QAbstractButton*)));

    //默认是单播方式
    ui->radioButton_unicast->click();

}

(10)应用程序可以自由选择(单播,组播,广播)这三种发送方式的其中一种,可以通过使用radioButton来实现互斥,当发送方式发生改变时,调用以下槽函数进行处理。

//radiobutton改变时的处理槽函数
void Widget::slot_radiobutton_clicked(QAbstractButton* button)
{
    (void)button;

    if(ui->radioButton_unicast->isChecked())
    {
        send_type = TYPE_UNICAST;
        ui->label_local_ip_display->setText(udp_socket->get_local_ipaddr());   //显示本机的IP地址
        udp_socket->udp_socket->leaveMulticastGroup(QHostAddress(GROUPCAST_ADDR));
    }
    else if(ui->radioButton_groupcast->isChecked())
    {
        send_type = TYPE_GROUPCAST;
        ui->label_local_ip_display->setText(GROUPCAST_ADDR);   //显示组播的IP地址
        udp_socket->udp_socket->joinMulticastGroup(QHostAddress(GROUPCAST_ADDR));
    }
    else if(ui->radioButton_broadcast->isChecked())
    {
        send_type = TYPE_BROADCAST;
        ui->label_local_ip_display->setText(BROADCAST_ADDR);   //显示广播的IP地址
        udp_socket->udp_socket->leaveMulticastGroup(QHostAddress(GROUPCAST_ADDR));
    }
    else
    {
        send_type = TYPE_UNICAST;
        udp_socket->udp_socket->bind(QHostAddress(udp_socket->get_local_ipaddr()),udp_socket->listen_port);
        ui->label_local_ip_display->setText(udp_socket->get_local_ipaddr());   //显示本机的IP地址
        udp_socket->udp_socket->leaveMulticastGroup(QHostAddress(GROUPCAST_ADDR));
    }
}

(11)当应用程序接收到UDP数据时,会调用void Widget::slot_recv_udp_data()函数进行处理,在这个函数里面,把接收到的数据以及数据来自哪个IP地址,都显示出来。

//处理UPD数据的槽函数
void Widget::slot_recv_udp_data(const QString ip, int port, const QByteArray &data)
{
    (void)port;
    QString data_recv;

    data_recv = QString(data);

    //显示接收到的数据
    text_browser_append(1,ip+">>>"+data_recv);
}

(12)以上就是整个Udp_Socket应用程序的开发过程,相比起TCP/IP,使用UDP协议进行数据传输是比较方便快捷的,开发者应该根据不同的应用场景选择不同的数据传输协议,每种协议都各有利弊,合适的就是最好的。

(13)把应用程序下载到开发板上运行,如下图所示:

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

工程师进阶笔记

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值