1. 程式人生 > 其它 >64.QT-單播、廣播、組播

64.QT-單播、廣播、組播

本章主要描述QT中如何實現單播、廣播、組播,大家可以直接參考qt官方例子:

  • Broadcast Sender : 廣播方式傳送
  • Broadcast Receiver : 廣播方式接收
  • Multicast Sender : 組播方式傳送
  • Multicast Receive : 組播方式接收

需要用到的函式

bool QAbstractSocket::bind(const QHostAddress &address, quint16 port = 0, QAbstractSocket::BindMode mode = DefaultForPlatform);
//使用BindMode模式繫結到埠埠上的地址。
//對於UDP套接字,繫結後,當UDP資料報到達指定的地址和埠時,訊號QUdpSocket::readyRead()就會發出。因此,這個函式對於編寫UDP伺服器很有用。 //對於TCP套接字,此函式可用於指定用於輸出連線的介面,這在多個網路介面的情況下非常有用。 //預設情況下,套接字使用DefaultForPlatform BindMode繫結。如果不指定埠,則選擇隨機埠。 //如果成功,函式返回true,套接字進入BoundState;否則返回false。 mode取值有: //QUdpSocket::ShareAddress : 允許其他server繫結到相同的地址和埠。當多個程序通過偵聽相同的地址和埠來共享單個server的負載時,這是很有用的。但是該選項需要考慮安全影響。 注意,通過將此選項與ReuseAddressHint結合,您還將允許您的服務重新繫結現有的共享地址。 //QUdpSocket::DontShareAddress: 繫結地址和埠,且不允許其他server進行繫結。可以保證在成功時,您的server是唯一偵聽地址和埠的服務。 QUdpSocket::ReuseAddressHint: 向QAbstractSocket提供一個提示,即即使地址和埠已經被另一個套接字繫結,它也應嘗試重新繫結server。 QUdpSocket::DefaultForPlatform: 平臺的預設選項。在Unix和macOS上,它等價於(DontShareAddress + ReuseAddressHint),在Windows上,它等價於ShareAddress。 其中QHostAddress除了填指定地址外,還可以設定如下所示: QHostAddress::Null - 空地址物件。相當於QHostAddress()。參見QHostAddress: isNull() QHostAddress::LocalHost - IPv4本地主機地址。相當於QHostAddress(127.0.0.1) QHostAddress::LocalHostIPv6 - IPv6本地主機地址。相當於QHostAddress("::1") QHostAddress::Broadcast - IPv4廣播地址。相當於QHostAddress("255.255.255.255") QHostAddress::AnyIPv4 - IPv4任何地址。相當於QHostAddress("0.0.0.0")。繫結此地址的套接字只能在IPv4介面上偵聽。 QHostAddress::AnyIPv6 - IPv6任何地址。相當於QHostAddress("::")。繫結此地址的套接字只能在IPv6介面上偵聽。 QHostAddress::Any - 任意地址。繫結此地址的套接字將同時監聽IPv4和IPv6介面。
bool QAbstractSocket::bind(quint16 port = 0, QAbstractSocket::BindMode mode = DefaultForPlatform); 這是個過載函式,預設地址為QHostAddress:Any. qint64 QUdpSocket::readDatagram(char *data, qint64 maxSize, QHostAddress *address = nullptr, quint16 *port = nullptr); //接收不大於maxSize位元組的資料報,並將其儲存在資料中。傳送者的主機地址和埠儲存在*address和*port中
qint64 QUdpSocket::writeDatagram(const char *data, qint64 size, const QHostAddress &address, quint16 port); 將大小為size的資料報傳送到埠埠的主機地址地址。返回成功時傳送的位元組數;否則返回1. 由於udp不穩定.所以資料報資料量儘量少,通常不建議傳送大於512位元組的資料報. 如果在連線的UDP套接字上呼叫此函式可能導致錯誤,沒有資料包被髮送。如果您正在使用已連線的套接字,請使用write()傳送資料報。 QNetworkDatagram QUdpSocket::receiveDatagram(qint64 maxSize = -1) //接收不大於maxSize位元組的資料報,並將接受的資料報,以及傳送者的主機地址和埠放在QNetworkDatagram物件中返回。

1.單播

單播用來一個UDP客戶端發出的資料報只發送到另一個指定地址和埠的UDP客戶端,是一對一的資料傳輸。
我們在以本地IP為例,初始化如下所示:

qDebug()<<"udpSocket1繫結: "<<udpSocket1->bind(QHostAddress::LocalHost, 7755); // 客戶端1
qDebug()<<"udpSocket1繫結: "<<udpSocket2->bind(QHostAddress::LocalHost, 7756); // 客戶端2

connect(udpSocket1, &QUdpSocket::readyRead,
this, &Widget::readPendingDatagrams);
connect(udpSocket2, &QUdpSocket::readyRead,
this, &Widget::readPendingDatagrams);

讀資料槽函式如下所示:

void Widget::readPendingDatagrams()
{
    QUdpSocket *udpSocket = dynamic_cast<QUdpSocket *>(sender());
    
    
    while (udpSocket->hasPendingDatagrams()) {
        QNetworkDatagram datagram = udpSocket->receiveDatagram();
        qDebug()<<QString(datagram.data())<<","<<datagram.senderAddress()<<datagram.senderPort();
    }
}

然後新增兩個按鈕:

void Widget::on_pushButton_clicked()
{
    QString str = "1傳送了資料";
    QByteArray datagram = str.toUtf8().data();
    udpSocket1->writeDatagram(datagram.data(),datagram.length(),QHostAddress::LocalHost,7756); // 傳送給客戶端2繫結的埠號(如果未繫結就會發送失敗)
}

void Widget::on_pushButton_2_clicked()
{
    QString str = "2傳送了資料";
    QByteArray datagram = str.toUtf8().data();
    udpSocket2->writeDatagram(datagram.data(),datagram.length(),QHostAddress::LocalHost,7755); // 傳送給客戶端1繫結的埠號(如果未繫結就會發送失敗)
}

提示: 不管客戶端是否bind()成功與否,都可以呼叫writeDatagram()隨意往某個地址埠傳送報文,因為UDP本身就是不需要建立連線的

如果我們想讓客戶端1和客戶端2都在同一個地址埠上收發訊息,那麼我們需要設定為:

qDebug()<<"udpSocket1繫結: "<<udpSocket1->bind(QHostAddress::LocalHost, 7755, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint);
qDebug()<<"udpSocket2繫結: "<<udpSocket2->bind(QHostAddress::LocalHost, 7755, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint);

2.廣播
廣播指一個UDP客戶端發出的資料報,在同一網路範圍內其他所有的UDP客戶端都可以收到。
廣播很簡單,我們以埠號45454為例:

  • 傳送方呼叫udpSocket->writeDatagram(datagram, QHostAddress::Broadcast, 45454);即可實現廣播發送.
  • 接收方需要bind(45454, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint)才行.等價於bind(QHostAddress::Any, 7755, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint);

如果接收方只是bind自身地址(QHostAddress::LocalHost)是收不到訊息的.

3.組播

組播也稱多播,凡是需要接受資料的客戶端都需要使用joinmultiastgroup()加入指定組播地址,然後傳送方只要往指定組播地址傳送資料。
加入指定組播地址的客戶端就會產生readyRead訊號,然後呼叫readDatagram()從指定的組播地址和埠去取資料。

組播地址屬於D類ip,只支援239.0.0.0—239.255.255.255,需要用到的函式:

bool QUdpSocket:joinmultiastgroup(const QHostAddress &groupAddress);
//加入指定組播地址所在組,如果成功,這個函式返回true;否則它將返回false
bool QUdpSocket::leaveMulticastGroup(const QHostAddress &groupAddress)
//離開指定組播地址所在組,如果成功,這個函式返回true;否則它將返回false

需要注意的是joinmultiastgroup()函式,如果我們加入的組播地址是IPv4,那麼bind的也必須明確是IPv4地址,比如這樣就會加入失敗:

groupAddress = QHostAddress("239.255.43.21");
udpSocket1->bind(QHostAddress::Any, 7755, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint);
udpSocket1->joinMulticastGroup(groupAddress);

因為QHostAddress::Any包含了IPv6,而groupAddress是個IPv4地址.

組播示例,初始化如下所示:

udpSocket1 = new QUdpSocket(this);
udpSocket2 = new QUdpSocket(this);
udpSocket3 = new QUdpSocket(this);

groupAddress = QHostAddress("239.255.43.21");
udpSocket1->bind(QHostAddress::AnyIPv4, 7755, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint);
udpSocket2->bind(QHostAddress::AnyIPv4, 7755, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint);
udpSocket1->joinMulticastGroup(groupAddress);
udpSocket2->joinMulticastGroup(groupAddress);

connect(udpSocket1, &QUdpSocket::readyRead,
this, &Widget::readPendingDatagrams);
connect(udpSocket2, &QUdpSocket::readyRead,
this, &Widget::readPendingDatagrams);

然後實現下面函式:

void Widget::readPendingDatagrams()
{
    QUdpSocket *udpSocket = dynamic_cast<QUdpSocket *>(sender());
    
    
    while (udpSocket->hasPendingDatagrams()) {
        QNetworkDatagram datagram = udpSocket->receiveDatagram();
        qDebug()<<QString(datagram.data())<<","<<datagram.senderAddress()<<datagram.senderPort();
    }
}


void Widget::on_pushButton_clicked()
{
    QString str = "udpSocket3往組播地址傳送資料了";
    QByteArray datagram = str.toUtf8().data();
    udpSocket3->writeDatagram(datagram.data(),datagram.length(),groupAddress,7755);
}

void Widget::on_pushButton_2_clicked()
{
    QString str = "udpSocket1往組播地址傳送資料了";
    QByteArray datagram = str.toUtf8().data();
    udpSocket1->writeDatagram(datagram.data(),datagram.length(),groupAddress,7755);
}

當我們點選pushButton按鈕,就會讓udpSocket3往組播地址傳送資料,此時udpSocket1和udpSocket2就會產生readyRead訊號從而去組播地址獲取資料.
當我們點選pushButton_2按鈕,就會讓udpSocket1往組播地址傳送資料,此時udpSocket1和udpSocket2也會產生readyRead訊號從而去組播地址獲取資料.