前言
我们经常使用 “ping” 命令来测试两台主机之间 TCP/IP 通信是否正常, 其实 “ping” 命令的原理就是向对方主机发送 UDP 数据包,然后对方主机确认收到数据包, 如果数据包是否到达的消息及时反馈回来,那么网络就是通的。
准备工作
首先,要使用 Qt 的网络模块需要在 pro 中加上 network(如果是 VS IDE 就在模块选择里勾选上 network):
QT += network
引入相关类的头文件:
#include <QUdpSocket>
#include <QHostAddress>
#include <QNetworkDatagram>
Qt UDP 的操作流程:
QUdpSocket的接口
QUdpSocket 是 QAbstractSocket 的子类,用于发送和接收 UDP 数据报。
可以使用 bind() 显式的绑定地址和端口。参数中的地址可以使用 QHostAddress::Any 绑定任意地址,IPv4 等效于 “0.0.0.0” , IPv6 等效于 “::”;而 BindMode 一般可以设置 ShareAddress(允许其他服务绑定到相同的地址和端口) 和 DontShareAddress(不允许其他服务重新绑定),Windows 上默认等效于 ShareAddress。
bool QAbstractSocket::bind(const QHostAddress &address, quint16 port = 0, QAbstractSocket::BindMode mode = DefaultForPlatform)
bool QAbstractSocket::bind(quint16 port = 0, QAbstractSocket::BindMode mode = DefaultForPlatform)
绑定后,只要 UDP 数据报到达指定的地址和端口,就会触发 readyRead() 信号,此时可在槽函数中读取数据:
void QIODevice::readyRead()
可通过 hasPendingDatagrams() 判断是否有可读数据,通过 pendingDatagramSize() 判断数据长度。
对于数据报的读写依然是使用 read/write 相关接口:
qint64 QUdpSocket::readDatagram(char *data, qint64 maxSize, QHostAddress *address = nullptr, quint16 *port = nullptr)
QNetworkDatagram QUdpSocket::receiveDatagram(qint64 maxSize = -1)
qint64 QUdpSocket::writeDatagram(const char *data, qint64 size, const QHostAddress &address, quint16 port)
qint64 QUdpSocket::writeDatagram(const QNetworkDatagram &datagram)
qint64 QUdpSocket::writeDatagram(const QByteArray &datagram, const QHostAddress &host, quint16 port)
对于单播,可以直接指定目标地址和端口发送:
const QString address_text = "127.0.0.1";
const QHostAddress address = QHostAddress(address_text);
const unsigned short port = 12345;
udpSocket->writeDatagram(QNetworkDatagram(send_data,address,port));
对于广播,需要发送到广播地址 “255.255.255.255”,即使用 QHostAddress::Broadcast:
udpSocket->writeDatagram(QNetworkDatagram(send_data,QHostAddress::Broadcast,port));
对于组播,需要发送到指定的组播地址,不过要对方加入了这个组播(使用 joinMulticastGroup 和 leaveMulticastGroup 加入/退出组播):
//组播ip必须是D类ip
//D类IP段 224.0.0.0 到 239.255.255.255
//且组播地址不能是224.0.0.1
udpSocket->bind(QHostAddress::AnyIPv4,port); //根据Qt示例,组播的话IPv4和v6分开的
udpSocket->joinMulticastGroup(address); //QHostAddress("224.0.0.2")
udpSocket->writeDatagram(QNetworkDatagram(send_data,address,port)); //QHostAddress("224.0.0.2")
操作完之后,调用相关接口关闭和释放:
void QAbstractSocket::disconnectFromHost()
void QAbstractSocket::close()
void QAbstractSocket::abort()
其中, abort 调用了 close, close 调用了 disconnectFromHost。 abort 立即关闭套接字,并丢弃写缓冲区中的所有待处理数据。close 关闭套接字的 IO,以及套接字的连接。
QUdpSocket对象实例化
QUdpSocket* m_udpSocket;
m_udpSocket = new QUdpSocket(this);
设置套接字选项
void setSocketOption(QAbstractSocket::SocketOption option, const QVariant &value)
//Multicast路由层次,1表示只在同一局域网内
//组播TTL: 生存时间,每跨1个路由会减1,多播无法跨过大多数路由所以为1
//默认值是1,表示数据包只能在本地的子网中传送。
m_udpSocket->setSocketOption(QAbstractSocket::MulticastTtlOption, 1);
QAbstractSocket::MulticastTtlOption:设置多播,即将此设置为整数可以设置IP_MULTICAST_TTL(对于多播数据报为TTL)套接字选项。后面的value就是那个整数,即为TTL值,范围为0~255之间的任何值。
QAbstractSocket::MulticastLoopbackOption:将value设置为1以启用IP_MULTICAST_LOOP(组播回环)套接字选项。即IP_MULTICAST_LOOP用于控制数据是否回送到本地的回环接口,0则禁用。
UDP信号触发
每当QAbstractSocket的状态更改时,都会发出此信号。 socketState参数是新状态。
void QAbstractSocket::stateChanged(QAbstractSocket::SocketState socketState)
connect(m_udpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onSocketStateChange(QAbstractSocket::SocketState)));
UDP通信的时候通过stateChanged信号来触发,通过对应的槽函数来触发。
QAbstractSocket::SocketState:套接字的连接状态
UnconnectedState:表示套接字没有连接,对应的默认值是0
ConnectingState:套接字正在执行主机名查找,对应的值是1
ConnectingState:套接字已开始建立连接。对应的值是2
ConnectedState:连接处于建立状态,对应值是3
BoundState:套接字绑定到地址和端口。对应值是4
ListeningState:监听状态,仅内部使用,对应值是5
ClosingState:套接字即将关闭(数据可能仍在等待写入)。对应值是6
void ExMulticast::onSocketStateChange(QAbstractSocket::SocketState socketState)
{
switch (socketState) {
case QAbstractSocket::UnconnectedState:
m_labSocketState->setText("socket状态:UnconnectedState");
break;
case QAbstractSocket::HostLookupState:
m_labSocketState->setText("socket状态:HostLookupState");
break;
case QAbstractSocket::ConnectingState:
m_labSocketState->setText("socket状态:ConnectingState");
break;
case QAbstractSocket::ConnectedState:
m_labSocketState->setText("socket状态:ConnectedState");
break;
case QAbstractSocket::BoundState:
m_labSocketState->setText("socket状态:BoundState");
break;
case QAbstractSocket::ClosingState:
m_labSocketState->setText("socket状态:ClosingState");
break;
case QAbstractSocket::ListeningState:
m_labSocketState->setText("socket状态:ListeningState");
break;
default:
m_labSocketState->setText("socket状态:其他未知状态...");
break;
}
}