UDP通信实例(2)--组播初探

UDP组播要注意两点:1)组播收发两端的端口号要一致;2)加入组播的套接字必须绑定IP地址 INADDR_ANY,而不是所在计算机的具体IP,否则会有问题。

下面给一个例子,是从https://www.cnblogs.com/jingliming/p/4477264.html 参考改编来的,结合Qt的线程类,把组播放入子线程处理。代码在ubuntu平台下测试,能正常接收数据。

头文件:

#ifndef UDPMULTICAST_H
#define UDPMULTICAST_H

#include <QThread>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>

class UDPMulticast : public QThread
{
    Q_OBJECT
public:
    explicit UDPMulticast(QObject *parent = 0);
    ~UDPMulticast();
    static UDPMulticast *   pGetInstance(void);

    int                 m_iLocalSock;
    bool                m_bInitSuccess;
    bool                m_bThrdPermission;
    struct sockaddr_in  m_local_addr;
    bool                m_bIsRunning;
    struct ip_mreq      mreq;

    void                vStart(void);
    void                vStop(void);
    bool                bGetRunningFlag(void);
signals:

public slots:

protected:
    void                run(void);
};

#endif // UDPMULTICAST_H

cpp:

#include "udpmulticast.h"

#define MCAST_PORT 8888
#define MCAST_ADDR "224.0.0.88" /*一个局部连接多播地址,路由器不进行转发*/
#define MCAST_INTERVAL 5  //发送时间间隔
#define BUFF_SIZE 256   //接收缓冲区大小
#define MULTI_ADDR INADDR_ANY//0x0303A8C0//0xC0A80303//

using namespace std;
UDPMulticast *   g_pUDPMulticast = NULL;

UDPMulticast::UDPMulticast(QObject *parent) : QThread(parent)
{
    m_bInitSuccess = false;
    m_bThrdPermission = false;
    m_bIsRunning = false;
    g_pUDPMulticast = this;

    int err=-1;
    //1 create socket
    m_iLocalSock = socket(AF_INET,SOCK_DGRAM,0);

    /*2 绑定socket*/
    //explanation of bind function can be seen at "man bind"
    struct sockaddr_in local_addr;
    local_addr.sin_family=AF_INET;
    local_addr.sin_addr.s_addr=htonl(MULTI_ADDR);
    local_addr.sin_port=htons(MCAST_PORT);
    err = ::bind(m_iLocalSock,(struct sockaddr*)&local_addr,sizeof(local_addr));
    if(0 == err)
    {
        /*3 设置回环许可*/
        /*https://docs.microsoft.com/zh-cn/windows/desktop/WinSock/ip-multicast-2
          The Winsock version of the IP_MULTICAST_LOOP option is semantically                  
different than the UNIX version of the IP_MULTICAST_LOOP option:
          In Winsock, the IP_MULTICAST_LOOP option applies only to the receive       path.     
          In the UNIX version, the IP_MULTICAST_LOOP option applies to the send path.
          在winows系统下,IP_MULTICAST_LOOP不能影响组播的发送,只能控制组播回环的接收;
          在linux系统下,IP_MULTICAST_LOOP不能影响组播的接收,只能控制组播回环的发送*/
        int loop=1;
        //explanation of setsockopt function can be seen at "man setsockopt"
        err=setsockopt(m_iLocalSock,IPPROTO_IP,IP_MULTICAST_LOOP,&loop,sizeof(loop));
        if(0 == err)
        {
            /*4 将本机加入广播组*/
            mreq.imr_multiaddr.s_addr=inet_addr(MCAST_ADDR);//广播地址
            mreq.imr_interface.s_addr=htonl(MULTI_ADDR); //网络接口为默认
            err=setsockopt(m_iLocalSock,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));
            if(0 == err)
            {
                m_bInitSuccess = true;
            }
        }
    }
}

UDPMulticast::~UDPMulticast()
{
    if(m_bInitSuccess)
    {
        /*退出广播组*/
        int err=setsockopt(m_iLocalSock,IPPROTO_IP,IP_DROP_MEMBERSHIP,&mreq,sizeof(mreq));
    }

    if(m_iLocalSock > -1)
    {
        close(m_iLocalSock);
    }
}

void UDPMulticast::run(void)
{
    m_bIsRunning = true;
    socklen_t addr_len=0;
    struct sockaddr_in  remote_addr;
    char buff[BUFF_SIZE];
    int n=0;

    if(m_iLocalSock > -1 && m_bInitSuccess)
    {
        while(m_bThrdPermission)
        {
            addr_len=sizeof(remote_addr);
            memset(buff,0,BUFF_SIZE);
            n=recvfrom(m_iLocalSock,buff,BUFF_SIZE,0,(struct sockaddr*)&remote_addr,&addr_len);
            if(n==-1)
            {
                /*退出广播组, close socket*/
                int err=setsockopt(m_iLocalSock,IPPROTO_IP,IP_DROP_MEMBERSHIP,&mreq,sizeof(mreq));
                close(m_iLocalSock);
                m_iLocalSock = -1;
                m_bInitSuccess = false;
                cout<<"recv error"<<endl;
                break;
            }
            /*打印信息*/
            printf("RECV message from server : %s\n",buff);
            msleep(MCAST_INTERVAL);
        }

    }

    m_bIsRunning = false;
}

void UDPMulticast::vStart(void)
{
    m_bIsRunning = true;
    m_bThrdPermission = true;
    start();
}

void UDPMulticast::vStop(void)
{
    m_bThrdPermission = false;
    while(m_bIsRunning){}
}

bool UDPMulticast::bGetRunningFlag(void)
{
    return m_bIsRunning;
}

UDPMulticast * UDPMulticast::pGetInstance(void)
{
    return g_pUDPMulticast;
}

窗体h文件

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private slots:
    void on_pushButton_clicked();

private:
    Ui::MainWindow *ui;
};

#endif // MAINWINDOW_H

窗体cpp:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "udpmulticast.h"

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

MainWindow::~MainWindow()
{
    UDPMulticast * pUDP = UDPMulticast::pGetInstance();
    pUDP->vStop();
    delete ui;
}

void MainWindow::on_pushButton_clicked()
{
    UDPMulticast * pUDP = UDPMulticast::pGetInstance();
    pUDP->vStart();
}

最后讲下代码存在的问题。既然UDP放在线程类里,自然要在程序退出时终止线程。线程的启动使用函数vStart,结束用的是vStop。在vStop函数里,程序要等到while一轮之后,发现m_bThrdPermission为false,才能退出while,并置m_bIsRunning为false.同时,主线程要等待m_bIsRunning为false才退出。这个设计本来没问题,但是忽略了一个细节:recvfrom函数不收到数据是不会返回的,而要一直阻塞。所以,假如程序退出时没有数据送进来,recvfrom 就不会返回,主线程也无法结束。解决这个问题的办法:用非阻塞模式来接收数据。

mainWindow头文件:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QCloseEvent>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();
    static MainWindow *     pGetInstance(void);
    void                    vUpdateStatus(char *);
    void                    vDisplayMsg(char *);
private slots:
    void on_pushButton_clicked();

private:
    Ui::MainWindow *ui;
protected:
    void                    closeEvent(QCloseEvent *event);
};

#endif // MAINWINDOW_H

MainWindow的cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "udpmulticast.h"

MainWindow * g_pMainWin = NULL;

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    g_pMainWin = this;
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_pushButton_clicked()
{
    UDPMulticast * pUDP = UDPMulticast::pGetInstance();
    pUDP->vStart();
}

MainWindow * MainWindow::pGetInstance(void)
{
    return g_pMainWin;
}

void MainWindow::vUpdateStatus(char * pStatus)
{
    ui->label->setText(QString(pStatus));
}

void MainWindow::vDisplayMsg(char * pMsg)
{
    int iLen = strlen(pMsg);
    QString qstrMsg;
    for(int k = 0; k < iLen; k++)
    {
        qstrMsg += QString("%1").arg(pMsg[k], 2, 16, QChar('0'));
    }
    ui->edtMsg->setText(qstrMsg);
}

void MainWindow::closeEvent(QCloseEvent *event)
{
    UDPMulticast * pUDP = UDPMulticast::pGetInstance();
    pUDP->vStop();
    QMainWindow::closeEvent(event);
}

udpMulticast的头文件

#ifndef UDPMULTICAST_H
#define UDPMULTICAST_H

#include <QThread>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>

class UDPMulticast : public QThread
{
    Q_OBJECT
public:
    explicit UDPMulticast(QObject *parent = 0);
    ~UDPMulticast();
    static UDPMulticast *   pGetInstance(void);

    int                 m_iLocalSock;
    bool                m_bInitSuccess;
    bool                m_bThrdPermission;
    struct sockaddr_in  m_local_addr;
    bool                m_bIsRunning;
    struct ip_mreq      mreq;

    void                vStart(void);
    void                vStop(void);
    bool                bGetRunningFlag(void);
signals:

public slots:

protected:
    void                run(void);
};

#endif // UDPMULTICAST_H

udpMulticast的cpp

#include "udpmulticast.h"
#include "mainwindow.h"
#include <QDebug>
#include <errno.h>

#define MCAST_PORT 8888
#define MCAST_ADDR "224.0.0.88" /*一个局部连接多播地址,路由器不进行转发*/
#define MCAST_INTERVAL 5  //发送时间间隔
#define BUFF_SIZE 256   //接收缓冲区大小
#define MULTI_ADDR INADDR_ANY//0x0303A8C0//0xC0A80303//

using namespace std;
UDPMulticast *   g_pUDPMulticast = NULL;

UDPMulticast::UDPMulticast(QObject *parent) : QThread(parent)
{
    m_bInitSuccess = false;
    m_bThrdPermission = false;
    m_bIsRunning = false;
    g_pUDPMulticast = this;

    int err=-1;
    //1 create socket
    m_iLocalSock = socket(AF_INET,SOCK_DGRAM,0);

    /*2 绑定socket*/
    //explanation of bind function can be seen at "man bind"
    struct sockaddr_in local_addr;
    local_addr.sin_family=AF_INET;
    local_addr.sin_addr.s_addr=htonl(MULTI_ADDR);
    local_addr.sin_port=htons(MCAST_PORT);
    err = ::bind(m_iLocalSock,(struct sockaddr*)&local_addr,sizeof(local_addr));
    if(0 == err)
    {
        /*3 设置回环许可*/
        int loop=0;
        //explanation of setsockopt function can be seen at "man setsockopt"
        err=setsockopt(m_iLocalSock,IPPROTO_IP,IP_MULTICAST_LOOP,&loop,sizeof(loop));
        if(0 == err)
        {
            /*4 将本机加入广播组*/
            mreq.imr_multiaddr.s_addr=inet_addr(MCAST_ADDR);//广播地址
            mreq.imr_interface.s_addr=htonl(MULTI_ADDR); //网络接口为默认
            err=setsockopt(m_iLocalSock,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));
            if(0 == err)
            {
                m_bInitSuccess = true;
                MainWindow * pMainWin = MainWindow::pGetInstance();
                pMainWin->vUpdateStatus("connected");
            }
        }
    }
}

UDPMulticast::~UDPMulticast()
{
    if(m_bInitSuccess)
    {
        /*退出广播组*/
        int err=setsockopt(m_iLocalSock,IPPROTO_IP,IP_DROP_MEMBERSHIP,&mreq,sizeof(mreq));
    }

    if(m_iLocalSock > -1)
    {
        close(m_iLocalSock);
    }
}

void UDPMulticast::run(void)
{
    m_bIsRunning = true;
    socklen_t addr_len=0;
    struct sockaddr_in  remote_addr;
    char buff[BUFF_SIZE];
    int n=0;

    MainWindow * pMainWin = MainWindow::pGetInstance();
    if(m_iLocalSock > -1 && m_bInitSuccess)
    {
        timeval t;
        t.tv_sec = 1;
        t.tv_usec = 0;
        setsockopt(m_iLocalSock, SOL_SOCKET, SO_RCVTIMEO,
                   (const char *)&t, sizeof(t));

        while(m_bThrdPermission)
        {
            addr_len=sizeof(remote_addr);
            memset(buff,0,BUFF_SIZE);
            n=recvfrom(m_iLocalSock,buff,BUFF_SIZE,0,(struct sockaddr*)&remote_addr,
                       &addr_len);
            if(n > 0)
            {
                pMainWin->vDisplayMsg(buff);
            }
            else if(-1 == n)
            {
                //error, see man recvfrom
                if(EAGAIN == errno || EWOULDBLOCK == errno || EINTR == errno)
                {
                    //connection is still alive, dont break
                    //see man recv
                }
                else
                {
                    //none of the three errors, socket or connection must be down, end loop
                    break;
                }
            }
            else
            {
                //0 == n, connection shutdown
                break;
            }

            msleep(MCAST_INTERVAL);
        }

    }

    m_bIsRunning = false;
}

void UDPMulticast::vStart(void)
{
    m_bIsRunning = true;
    m_bThrdPermission = true;
    start();
}

void UDPMulticast::vStop(void)
{
    m_bThrdPermission = false;

    while(m_bIsRunning){}
}

bool UDPMulticast::bGetRunningFlag(void)
{
    return m_bIsRunning;
}

UDPMulticast * UDPMulticast::pGetInstance(void)
{
    return g_pUDPMulticast;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值