本章主要描述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信號從而去組播地址獲取數據.
未完待續,下章學習: 65.QT-UDP組播實現多人共享桌面
