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訊號從而去組播地址獲取資料.