1. UDP组播特性
UDP组播是主机之间 " 一对一组 " 的通信模式,
当多个客户端加入由一个组播地址定义的多播组之后,
客户端向组播地址和端口发送的UDP数据报,组内成员都可以接收到。
用同一个IP多播地址接收多播数据报的所有主机构成了一个组,称为多播组(或组播组)。
所有的信息接收者都加入到一个组内,加入之后,流向组地址的数据报开始向接收者传输,
组中的所有成员都能接收到数据报。组中的成员是动态的,主机可以在任何时间加入和离开组。
采用UDP组播必须使用一个组播地址。组播地址(组播报文的目的地址)
是D类IP地址(D类地址不能出现在IP报文的源IP地址字段),有特定的地址段。
多播组可以是永久的也可以是临时的。多播组地址中,有一部分由官方分配,称为永久多播组。
永久多播组保持并联的是它的IP地址,组中的成员构成可以发生变化。
永久多播组中的成员数量可以是任意的,可以为零。
那些没有保留下来的供永久多播组使用的IP组播地址,可以被临时多播组使用。
2. 组播IP地址
- 224.0.0.0~224.0.0.255:为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其他地址供路由协议使用;
- 224.0.1.0~224.0.1.255:是公用组播地址,可以用于Internet;
- 224.0.2.0~238.255.255:为用户可用的组播地址(临时组地址),全网范围内有效;
- 239.0.0.0~239.255.255.255:为本地 管理组播地址,仅在特定的本地范围内有效。
在家庭或办公室局域网内测试UDP组播功能,可以使用的组播地址范围是239.0.0.0~239.255.255.255。
3. Qt程序实现
(1)声明所需变量:
QUdpSocket *udpSocket; //用于与连接的客户端通讯的QUdpSocket
QHostAddress groupAddress; //组播地址
(2)声明槽函数:
// 自定义槽函数
void onSocketReadyRead(); // 读取socket传入的数据
// 界面事件系统生成的槽函数
void on_actJoin_triggered(); // 加入组播
void on_actLeave_triggered(); // 离开组播
void on_btnMulticast_clicked(); // 发送组播消息
(3)构造函数中初始化socket,连接信号槽
udpSocket = new QUdpSocket(this); // 用于与连接的客户端通讯的QUdpSocket
// Multicast路由层次,1表示只在同一局域网内
// 组播TTL: 生存时间,每跨1个路由会减1,多播无法跨过大多数路由所以为1
// 默认值是1,表示数据包只能在本地的子网中传送。
udpSocket->setSocketOption(QAbstractSocket::MulticastTtlOption, 1);
connect(udpSocket, SIGNAL(readyRead()), this, SLOT(onSocketReadyRead()));
将MulticastTtlOption设置为1,MulticastTtlOption是UDP组播数据报的生存周期,数据报每跨一个路由会减1,缺省值为1,表示多播数据只能在同一路由下的局域网内传播。
(4)加入组播
void MainWindow::on_actJoin_triggered()
{
QString IP = ui->comboIP->currentText();
groupAddress = QHostAddress(IP); // 多播组地址
quint16 groupPort=ui->spinPort->value(); // 端口
// QHostAddress::AnyIPv4 与此地址绑定的socket将仅侦听IPv4交互
// groupPort,多播组统一的一个端口
// QUdpSocket::ShareAddress 允许其他服务绑定到相同的地址和端口
// QUdpSocket::ReuseAddressHint 向QAbstractSocket提供提示,提示它应尝试重新绑定服务,即使地址和端口已被另一个套接字绑定。在Windows和Unix上,这相当于SO_REUSEADDR套接字选项。
// QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint 组合使用才能在本机同时启动多个程序绑定相同端口,适合没有局域网只有一台电脑的本地测试使用
if (udpSocket->bind(QHostAddress::AnyIPv4, groupPort, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint)) //先绑定端口
{
udpSocket->joinMulticastGroup(groupAddress); //加入IP地址为groupAddress的多播组,绑定端口groupPort进行通信
ui->plainTextEdit->appendPlainText("**加入组播成功");
ui->plainTextEdit->appendPlainText("**组播地址IP:"+IP);
ui->plainTextEdit->appendPlainText("**绑定端口:"+QString::number(groupPort));
ui->actJoin>setEnabled(false);
ui->actLeave->setEnabled(true);
ui->comboIP->setEnabled(false);
}
else
ui->plainTextEdit->appendPlainText("**绑定端口失败");
}
(5)发送组播消息
void MainWindow::on_btnMulticast_clicked()
{
quint16 groupPor t= ui->spinPort->value();
QString msg = ui->editMsg->text();
QByteArray datagram = msg.toUtf8();
udpSocket->writeDatagram(datagram,groupAddress,groupPort);
ui->plainTextEdit->appendPlainText("[multicst] "+msg);
ui->editMsg->clear();
ui->editMsg->setFocus();
}
(6)读取数据报
// 由readyRead()信号触发的槽函数。
// 绑定端口后,只要有UDP数据报到达指定的地址和端口,就会发出信号QUdpSocket::readyRead()
void MainWindow::onSocketReadyRead()
{
while(udpSocket->hasPendingDatagrams())
{
QByteArray datagram;
datagram.resize(udpSocket->pendingDatagramSize());
QHostAddress peerAddr;
quint16 peerPort;
udpSocket->readDatagram(datagram.data(),datagram.size(),&peerAddr,&peerPort);
QString str = datagram.data();
QString peer = "[From "+peerAddr.toString()+":"+QString::number(peerPort)+"] ";
ui->plainTextEdit->appendPlainText(peer+str);
}
}
(7)退出组播
void MainWindow::on_actLeave_triggered()
{
udpSocket->leaveMulticastGroup(groupAddress);// 退出组播
udpSocket->abort(); // 中止当前连接并重置套接字。与disconnectFromHost()不同,此函数会立即关闭套接字,丢弃写入缓冲区中的所有挂起数据。
ui->actJoin->setEnabled(true);
ui->actLeave->setEnabled(false);
ui->comboIP->setEnabled(true);
ui->plainTextEdit->appendPlainText("**已退出组播,解除端口绑定");
}
4. 程序演示
可以同时启动多个程序,加入相同组播地址,绑定相同端口,即可收到组内的任何用户的消息,自己主机上发送的消息自己也会收到。