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;
}