目录
一、 QUdpSocket基本概念和相关函数
QUdpSocket提供了UDP套接字API,用来接收和发送UDP数据报。
QUdpSocket类最通用的使用方式是:用bind()函数绑定一个IP地址和端口Port,然后调用writeDatagram()和readDatagram()函数传输数据。
enum QUdpSocket::BindFlagflags QUdpSocket::BindMode
BindFlag 这些值可以组成不同的标志,传递给QUdpSocket::bind()函数来修改bind()的特性。BindMode 是typedef for QFlags<BindFlag>。它是BindFlag值得或操作。
常量定义 | 值 | 描述 |
QUdpSocket::ShareAddress | 0x1 | 1、允许其他服务绑定同样的地址和端口 2、当多进程通过监听同一地址和端口,进而共享单个服务的负载时,将十分有用(例如:一个拥有几个预先建立的监听者的WEB服务器能够改善响应时间)。不过,由于任何服务都允许重新绑定(rebind),该选项应该引起某些安全上的考虑 3、需要注意的是,把该选项和ReuseAddressHint结合,也会允许你的服务重新绑定一个已存在的共享地址 4、在Unix上,该选项等同于SO_REUSEADDR;在Windows上,该选项被忽略 |
QUdpSocket::DontShareAddress | 0x2 | 1、采用专有的方式绑定某个地址和端口,其他任何服务都不能再重新绑定 2、通过该选项,确保绑定成功,指定的服务将是地址和端口唯一监听者,就算是拥有ReuseAddressHint的服务也不允许重新绑定 3、在安全性上,该选项优于ShareAddress,但是在某些操作系统上需要管理员的权限才能运行 4、在Unix和Mac OS上,绑定地址和端口的默认行为是非共享,所以该选项会被忽略;在Windows上,等同于SO_EXCLUSIVEADDRUSE套接字选项 |
QUdpSocket::ReuseAddressHint | 0x4 | 1、为QUdpSocke提供提示,即在地址和端口已经被其他套接字绑定的情况下,也应该试着重新绑定 2、在Unix上,该选项被忽略;在Windows上等同于SO_REUSEADDR 套接字选项 |
QUdpSocket::DefaultForPlatform | 0x0 | 1、当前平台的默认选项 2、在Unix和Mac OS上,该选项等同于DontShareAddress + ReuseAddressHint;在Windows上等同于ShareAddress |
如果要使用QIODevice中的read(), readLine(), write()等函数,必须首先调用connectToHost()函数,直接建立一个和对方的连接
qint64 QUdpSocket::writeDatagram(const char *data, qint64 size, const QHostAddress &address, quint16 port)
只要向网络写入了一个数据报,SOCKET就产生一个bytesWritten()信号,如果仅仅是发送数据报,无需调用bind()。数据报到来,readyRead()信号被产生,此时hasPendingDatagrams()函数返回真(true)。调用pendingDatagramSize()获取第一个数据报的长度(size),readDatagram()读取数据报内容。
qint64 QUdpSocket::readDatagram(char *data, qint64 maxSize, QHostAddress *address = Q_NULLPTR, quint16 *port = Q_NULLPTR)
- 接收不大于maxSize字节的数据报并将其存储在数据中。发送方的主机地址和端口存储在address 地址和port 端口中(除非指针为0);成功时返回数据报的大小,否则返回-1
- 如果maxSize太小,数据报的其余部分将丢失,因此为避免数据丢失,请在读取数据报之前调用PendingDatagramSize()确定数据报的大小;
- 如果maxSize为0,则数据报将被丢弃
QUdpSocket *receiver;
//每当有数据报发送过来时,QUdpSocket都会发射readyRead信号,这样就可以在自定义的槽中读取数据
connect(receiver,SIGNAL(readyRead()),this,SLOT(processPendingDatagram()));
读取数据报
void Receiver::processPendingDatagram()
{
while (m_pUdpSocket->hasPendingDatagrams())//如果有数据报需要读取
{
//读数据包
QByteArray datagram;
datagram.resize(m_pUdpSocket->pendingDatagramSize());
m_pUdpSocket->readDatagram(datagram.data(), datagram.size());
}
}
注意:接收到readyRead()信号后,应当读取该数据报,否则下一个数据报到来后将不再产生readyRead()信号。
QHostAddress类提供一个IP地址。这个类提供一种独立于平台和协议的方式来保存IPv4和IPv6地址。QHostAddress通常与QTcpSocket、QTcpServer、QUdpSocket一起使用,来连接到主机或建立一个服务器。
枚举 QHostAddress::SpecialAddress:
常量 值 描述
QHostAddress::Null 0 空地址对象,相当于QHostAddress()。
QHostAddress::LocalHost 2 IPv4本地主机地址,相当于QHostAddress(“127.0.0.1”)。
QHostAddress::LocalHostIPv6 3 IPv6本地主机地址,相当于 QHostAddress(“::1”)。
QHostAddress::Broadcast 1 Pv4广播地址,相当于QHostAddress(“255.255.255.255”)。
QHostAddress::AnyIPv4 6 IPv4 any-address,相当于QHostAddress(“0.0.0.0”)。与该地址绑定的socket将只监听IPv4接口。
QHostAddress::AnyIPv6 5 IPv6 any-address,相当于QHostAddress(“::”)。与该地址绑定的socket将只监听IPv4接口。
QHostAddress::Any 4 双any-address栈,与该地址绑定的socket将侦听IPv4和IPv6接口。
二、QUdpSocket广播
在使用QUdpSocket发送广播消息时,当发送端配有多个ip地址时,发送端需要绑定一个指定的ip地址才能实现向次局域网内的其他主机发送广播消息,否则其他主机不能收到;如果只是本机法送和接收消息,那么ip地址可以设置为 QHostAddress::LocalHost。接收端只需要绑定发送端指定的端口即可。
查找可用的广播地址:
QList<QNetworkInterface> interfaceList = QNetworkInterface::allInterfaces();
foreach (QNetworkInterface interface, interfaceList)
{
qDebug() << interface.humanReadableName(); //打印网卡名称
QList<QNetworkAddressEntry> entryList = interface.addressEntries();
foreach(QNetworkAddressEntry entry, entryList)
{
QString str = entry.broadcast().toString();
if(str != "")
{
qDebug() << str; //打印可用的广播地址
}
}
}
发送端:广播write发送数据应该绑定ip,否则多网卡发送不正确
QUdpSocket *sender;
QHostAddress addr;
QString ip = "192.168.66.66";
addr.setAddress(ip);
sender->bind(addr,5555,QUdpSocket::ShareAddress);
QByteArray datagram = ui->lineEdit->text().toLatin1();
sender->writeDatagram(datagram.data(),datagram.size(),QHostAddress::Broadcast,5556);//把数据写入报文并发送
接收端需保证有一个ip地址和服务端的绑定ip(192.168.66.XXX)在同一网段:
QUdpSocket *receiver;
receiver->bind(5556,QUdpSocket::ShareAddress); //端口设置为server端设置的端口
//每当有数据报发送过来时,QUdpSocket都会发射readyRead信号,这样就可以在自定义的槽中读取数据
connect(receiver,SIGNAL(readyRead()),this,SLOT(processPendingDatagram()));
读取数据报
void Receiver::processPendingDatagram()
{
QHostAddress addr; //服务器ip
quint16 port; //服务器发送的端口号
while (receiver->hasPendingDatagrams())//如果有数据报需要读取
{
//读数据包
QByteArray datagram;
datagram.resize(receiver->pendingDatagramSize());
receiver ->readDatagram(datagram.data(), datagram.size(),&addr,&port);
qDebug()<<"send addr="<<addr.toString(); //"192.168.66.66"
qDebug()<<"send port="<<port; //5555
qDebug()<<"receive msg="<<datagram;
}
}
三、QUdpSocket组播
组播发送socket不需要加入组播,只要向组播地址发送数据:
QUdpSocket *sender;
QHostAddress multiAddr;
QString multiIp = "224.5.5.5"; //组播ip
quint16 sendPort = 11111; //发送端口
quint16 receivePort = 22222; //接收端口
multiAddr.setAddress(multiIp);
sender->bind(sendPort,QUdpSocket::ShareAddress); //此处添加发送ip接收端接不到,绑定对应网卡取代
QByteArray datagram = ui->lineEdit->text().toLatin1();
sender->writeDatagram(datagram.data(),datagram.size(),multiAddr,receivePort);//把数据写入报文并发送
组播接收端:需要加入组播组,最好设置绑定的网卡
QUdpSocket *receiver;
QHostAddress multiAddr;
QString multiIp = "224.5.5.5"; //组播ip
quint16 receivePort = 22222; //接收端口
multiAddr.setAddress(multiIp);
bool result = receiver->bind(QHostAddress::Any, receivePort , QUdpSocket::ShareAddress);
if(!result)
{
qDebug()<<"bind failed";
}
bool result1 = receiver->joinMulticastGroup(multiAddr); //加入组播组
if(!result1)
{
qDebug()<<"joinMuticastGroup failed";
}
//每当有数据报发送过来时,QUdpSocket都会发射readyRead信号,这样就可以在自定义的槽中读取数据
connect(receiver,SIGNAL(readyRead()),this,SLOT(processPendingDatagram()));
//读取数据报
void Receiver::processPendingDatagram()
{
QHostAddress addr; //服务器ip
quint16 port; //服务器发送的端口号
while (receiver->hasPendingDatagrams())//如果有数据报需要读取
{
//读数据包
QByteArray datagram;
datagram.resize(receiver->pendingDatagramSize());
receiver->readDatagram(datagram.data(), datagram.size(),&addr,&port);
qDebug()<<"send addr="<<addr.toString(); //"192.168.66.66"
qDebug()<<"send port="<<port; //5555
qDebug()<<"receive msg="<<datagram;
}
}