Qt-网络 - 补充
QHostInfo/QNetworkInterface
.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QHostInfo>
#include <QHostAddress>
#include <QNetworkInterface>
QT_BEGIN_NAMESPACE
namespace Ui { class Dialog; }
QT_END_NAMESPACE
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = nullptr);
~Dialog();
void lookupHostInfo(const QHostInfo &host);
private:
Ui::Dialog *ui;
QString protocalName(QAbstractSocket::NetworkLayerProtocol protocal);
QString interfaceType(QNetworkInterface::InterfaceType type);
};
#endif // DIALOG_H
.cpp
#include "dialog.h"
#include "ui_dialog.h"
Dialog::Dialog(QWidget *parent)
: QDialog(parent)
, ui(new Ui::Dialog)
{
ui->setupUi(this);
// 本机主机名
QString hostName=QHostInfo::localHostName();
// 通过主机名获取ip地址es 或通过ip地址解析域名
QHostInfo hostInfo=QHostInfo::fromName(hostName);
qDebug()<<hostName;
// 与主机关联的ip地址列表
QList<QHostAddress> addrList=hostInfo.addresses();
foreach (QHostAddress host, addrList) {
qDebug()<<"******";
// .toString ip地址
// .protocal 协议 int类型/枚举类型
qDebug()<<"ip地址:\t"<<host.toString();
qDebug()<<"协议:\t"<<" "<<host.protocol();
qDebug()<<protocalName((QAbstractSocket::NetworkLayerProtocol)host.protocol()); // 不进行类型转换Qt中也是可以的
}
qDebug()<<"====== QHostInfo::lookupHost ======";
// .lookupHost 异步方式(信号和槽),查找信息,可以是主机名、域名和ip地址
QString domain_name="www.baidu.com";
int Id_info=QHostInfo::lookupHost(domain_name,this,&Dialog::lookupHostInfo);
qDebug()<<Id_info;
qDebug()<<"====== QNetworkInterface ======";
// QNetworkInterface 获得运行程序的主机的所有IP地址和网络接口列表,包括子网掩码和广播地址
// 一个网络接口可能包含0或多个IP地址,每个都有一个掩码或广播地址关联
// .allInterfaces 网络接口列表
// .allAddress 无需直到子网掩码和广播地址,获得主机上的所有IP地址
QList<QNetworkInterface> interfaceList=QNetworkInterface::allInterfaces();
foreach (QNetworkInterface interface, interfaceList) {
if (!interface.isValid()) // 如果不包含有效的信息
{
continue;
}
qDebug()<<"***** interface info *******";
qDebug()<<"设备名称:\t"<<interface.humanReadableName(); // 设备名称 .name()
qDebug()<<"硬件地址:\t"<<interface.hardwareAddress(); // 硬件地址/MAC地址 string表示
qDebug()<<"接口类型:\t"<<interface.type(); // 接口类型
// 查看与ip地址相关连的掩码和广播地址
QList<QNetworkAddressEntry> entryList=interface.addressEntries(); // 地址列表
foreach (QNetworkAddressEntry entry,entryList)
{
qDebug()<<"******";
qDebug()<<"IP地址:\t"<<entry.ip().toString();
qDebug()<<"子网掩码:\t"<<entry.netmask().toString();
qDebug()<<"广播掩码:\t"<<entry.broadcast().toString();
}
}
qDebug()<<"====== QNetworkInterface::allAddress ======";
QList<QHostAddress> interfaceAddrList=QNetworkInterface::allAddresses();
if (!interfaceAddrList.isEmpty())
{
foreach(QHostAddress host ,interfaceAddrList)
{
qDebug()<<"******";
qDebug()<<"ip地址:\t"<<host.toString();
qDebug()<<"协议:\t"<<host.protocol();
}
}
}
Dialog::~Dialog()
{
delete ui;
}
QString Dialog::protocalName(QAbstractSocket::NetworkLayerProtocol protocal)
{
switch (protocal) {
case QAbstractSocket::IPv4Protocol:
return "IPv4";
break;
case QAbstractSocket::IPv6Protocol:
return "IPv4";
default:
return "unknow ip protocal";
break;
}
}
void Dialog::lookupHostInfo(const QHostInfo &host)
{
qDebug()<<"====== QNetworkInterface::lookupHost ======";
QList<QHostAddress> addrList=host.addresses();
if (addrList.isEmpty())
{
qDebug()<<"can not find any info matched the host";
}
foreach(QHostAddress addr,addrList)
{
qDebug()<<addr.toString()<<"\t"<<addr.protocol();
}
}
QString Dialog::interfaceType(QNetworkInterface::InterfaceType type)
{//根据枚举值返回字符串
switch(type)
{
case QNetworkInterface::Unknown:
return "Unknown";
case QNetworkInterface::Loopback:
return "Loopback";
case QNetworkInterface::Ethernet:
return "Ethernet";
case QNetworkInterface::Wifi:
return "Wifi";
default:
return "Other type";
}
}
TCP
QTcpServer的父类是QObject
QTcpSocket是从QIODevice间接继承的类,因此QTCPSocket是一种I/O设备类,具有流数据的读写功能,除构造函数和析构函数,QTcpSocket类的其他函数都是从父类QAbstractSocket继承或重写的,QAbstractSocket类用于TCP通信
异步/同步
连接
connectToHost是以异步方式连接到服务器的,不会阻塞运行,成功连接后发送connected()信号
// 异步变同步(非阻塞变阻塞)
socket->connectToHost(ip_address,port); // 非阻塞
if (socket->waitForConnected(sec)) // 设置阻塞等待秒数
{
// code...
}
读写
接收、发送数据二者之间是异步的,有各自的缓冲区
断开连接
disconnect/disconnectFromHost
QTcpSocket::disconnect() 函数和 QTcpSocket::disconnectFromHost() 函数在 QTcpSocket 类中都用于断开与远程主机的连接,但它们的实现略有不同。下面是它们的区别:
disconnect() 函数:这个函数用于断开与远程主机的连接,并且不再发送任何数据。这意味着,如果您要在关闭连接之前发送任何数据,这不是一个好的选择。而且,这个函数一旦调用,连接就不能再用了,如果您需要再次建立连接,必须创建一个新的 QTcpSocket 对象。
disconnectFromHost() 函数:这个函数也用于断开与远程主机的连接,但它会在数据发送完毕后关闭连接。这意味着,如果您需要先发送数据然后断开连接,就应该使用这个函数。另外,这个函数并不会立即关闭连接,而是将断开连接的请求发送到 QTcpSocket 事件队列中等待。
因此,如果您想在发送数据后安全地断开连接,disconnectFromHost() 是一个更好的选择。而如果您只是想快速关闭连接,disconnect() 是一个更好的选择。
数据通信
socket之间的通讯协议一般由两种,基于行和基于块
基于行的数据通讯协议一般用于纯文本数据的通信,每一行数据以一个换行符结束,canReadLine()判断是否有新的一行数据需要读取,如果有就调用函数readLine()
基于块的数据通讯协议一般用于二进制的数据传输,需要自定义具体的格式
TcpSocket间接继承自QIODevice
例子
服务端端
.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTcpServer>
#include <QTcpSocket>
#include <QHostInfo>
#include <QHostAddress>
#include <QTimer>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
void stop();
private:
Ui::MainWindow *ui;
QTcpServer *tcpServer;
QTcpSocket *tcpSocket;
QString getLocalIP();
QTimer *sendDataTimer;
private slots:
void getNewConnect();
void clientConnected();
void socketStateChanged(QAbstractSocket::SocketState socketState);
void clientDisConnected();
void socketReadyRead();
void timerSendData();
};
#endif // MAINWINDOW_H
.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
tcpServer=nullptr;
tcpSocket=nullptr;
QString localIP=getLocalIP();
tcpServer=new QTcpServer(this);
connect(tcpServer,&QTcpServer::newConnection,this,&MainWindow::getNewConnect);
QString IpToListen="127.0.0.1";
quint16 portToListen=8080;
// 开始监听
tcpServer->listen(QHostAddress(IpToListen),portToListen);
sendDataTimer=new QTimer(this);
sendDataTimer->setInterval(1000);
connect(sendDataTimer,&QTimer::timeout,this,&MainWindow::timerSendData);
}
MainWindow::~MainWindow()
{
if (tcpSocket!=nullptr)
{
if (tcpSocket->state()==QAbstractSocket::ConnectedState)
{
// close 则会自动调用disconnectFromHost,成功断开后发射disconnected信号
tcpSocket->disconnectFromHost(); // 断开与客户端的链接
// delete tcpSocket; // 不要这样写,涉及到对象树的概念
}
}
if (tcpServer->isListening())
{
tcpServer->close(); // 关闭服务器
// delete tcpServer; // 不要这样写,涉及到对象树的概念
}
if (sendDataTimer->isActive())
{
sendDataTimer->stop();
}
delete ui;
}
QString MainWindow::getLocalIP()
{
QString hostName=QHostInfo::localHostName(); // 本机主机名
QHostInfo hostInfo=QHostInfo::fromName(hostName);
QString localIP="";
QList<QHostAddress> addrList=hostInfo.addresses(); // 本机IP地址列表,注意不包括127.0.0.1
if (addrList.isEmpty())
{
return localIP;
}
foreach(QHostAddress host,addrList)
{
if (host.protocol()==QAbstractSocket::IPv4Protocol)
{
localIP=host.toString();
break;
// qDebug()<<localIP;
}
}
return localIP;
}
void MainWindow::getNewConnect()
{
tcpSocket=tcpServer->nextPendingConnection(); // 从新连接列表中取出一个
connect(tcpSocket,&QTcpSocket::connected,this,&MainWindow::clientConnected);
clientConnected();
connect(tcpSocket,&QTcpSocket::stateChanged,this,&MainWindow::socketStateChanged);
connect(tcpSocket,&QTcpSocket::disconnected,this,&MainWindow::clientDisConnected);
socketStateChanged(tcpSocket->state());
connect(tcpSocket,&QTcpSocket::readyRead,this,&MainWindow::socketReadyRead);
sendDataTimer->start();
}
void MainWindow::clientConnected()
{
qDebug()<<"====== client socket connected ======";
qDebug()<<"client IP:\t"<<tcpSocket->peerAddress().toString();
qDebug()<<"client port:\t"<<QString::number(tcpSocket->peerPort());
}
void MainWindow::socketStateChanged(QAbstractSocket::SocketState socketState)
{
switch(socketState)
{
case QAbstractSocket::UnconnectedState:
qDebug()<<("socket new state:UnconnectedState");
break;
case QAbstractSocket::HostLookupState:
qDebug()<<("socket new state:HostLookupState");
break;
case QAbstractSocket::ConnectingState:
qDebug()<<("socket new state:ConnectingState");
break;
case QAbstractSocket::ConnectedState:
qDebug()<<("socket new state:ConnectedState");
break;
case QAbstractSocket::BoundState:
qDebug()<<("socket new state:BoundState");
break;
case QAbstractSocket::ClosingState:
qDebug()<<("socket new state:ClosingState");
break;
case QAbstractSocket::ListeningState:
qDebug()<<("socket new state:ListeningState");
}
}
void MainWindow::clientDisConnected()
{
qDebug()<<"====== client socket disconnected ======";
tcpSocket->deleteLater();
if (sendDataTimer->isActive())
{
sendDataTimer->stop();
}
}
void MainWindow::socketReadyRead()
{
while (tcpSocket->canReadLine())
{
// 换行符结束
// maxsize-1 byte结束
// 设备数据结尾被读取
// qDebug()<<tcpSocket->readLine(); // 默认以换行符为一行
QByteArray readData=tcpSocket->readLine();
qDebug()<<readData<<" size: "<<readData.size();
}
}
void MainWindow::stop()
{
if (sendDataTimer->isActive())
{
sendDataTimer->stop();
}
if (tcpServer->isListening())
{
if (tcpSocket!=nullptr)
{
if (tcpSocket->state()==QAbstractSocket::ConnectedState)
{
tcpSocket->disconnectFromHost(); // 等所有数据都放完毕再断开
// tcpSocket->disconnect();// 立即断连,抛弃数据
qDebug()<<"====== client socket disconnectfromhost ======";
}
}
}
tcpServer->close();
qDebug()<<"====== server socket closed ======";
}
void MainWindow::timerSendData()
{
static qint64 send_num=0;
QString msg="hello world";
QByteArray msgArray=msg.toUtf8();
msgArray.append('\n'); // 手动添加结尾变成一行数
tcpSocket->write(msgArray);
send_num++;
qDebug()<<"send the "<<send_num<<" times";
}
客户端
.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTcpSocket>
#include <QHostInfo>
#include <QHostAddress>
#include <QTimer>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
void stop();
private:
Ui::MainWindow *ui;
QTcpSocket *tcpClient;
QTimer *sendDataTimer;
private slots:
void clientConnected();
void clientDisConnected();
void socketStateChanged(QAbstractSocket::SocketState socketState);
void socketReadyRead();
void timerSendData();
};
#endif // MAINWINDOW_H
.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
tcpClient=new QTcpSocket(this);
connect(tcpClient,&QTcpSocket::connected,this,&MainWindow::clientConnected);
connect(tcpClient,&QTcpSocket::disconnected,this,&MainWindow::clientDisConnected);
connect(tcpClient,&QTcpSocket::stateChanged,this,&MainWindow::socketStateChanged);
connect(tcpClient,&QTcpSocket::readyRead,this,&MainWindow::socketReadyRead);
tcpClient->connectToHost(QHostAddress("127.0.0.1"),8080);
sendDataTimer=new QTimer(this);
sendDataTimer->setInterval(1000);
connect(sendDataTimer,&QTimer::timeout,this,&MainWindow::timerSendData);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::clientConnected()
{
qDebug()<<"====== client socket connected ======";
qDebug()<<"client IP:\t"<<tcpClient->peerAddress().toString();
qDebug()<<"client port:\t"<<QString::number(tcpClient->peerPort());
sendDataTimer->start();
}
void MainWindow::clientDisConnected()
{
qDebug()<<"====== client socket disconnected ======";
tcpClient->deleteLater();
}
void MainWindow::socketStateChanged(QAbstractSocket::SocketState socketState)
{
switch(socketState)
{
case QAbstractSocket::UnconnectedState:
qDebug()<<("socket new state:UnconnectedState");
break;
case QAbstractSocket::HostLookupState:
qDebug()<<("socket new state:HostLookupState");
break;
case QAbstractSocket::ConnectingState:
qDebug()<<("socket new state:ConnectingState");
break;
case QAbstractSocket::ConnectedState:
qDebug()<<("socket new state:ConnectedState");
break;
case QAbstractSocket::BoundState:
qDebug()<<("socket new state:BoundState");
break;
case QAbstractSocket::ClosingState:
qDebug()<<("socket new state:ClosingState");
break;
case QAbstractSocket::ListeningState:
qDebug()<<("socket new state:ListeningState");
}
}
void MainWindow::socketReadyRead()
{
while (tcpClient->canReadLine())
{
// 换行符结束
// maxsize-1 byte结束
// 设备数据结尾被读取
qDebug()<<tcpClient->readLine(); // 默认以换行符为一行
// QByteArray readData=tcpClient->readLine();
// qDebug()<<readData<<" size: "<<readData.size();
}
}
void MainWindow::stop()
{
if (!(tcpClient->state()==QAbstractSocket::ConnectedState))
{
return;
}
tcpClient->disconnectFromHost();
}
void MainWindow::timerSendData()
{
static qint64 send_num=0;
QString msg="callback hello world";
QByteArray msgArray=msg.toUtf8();
msgArray.append('\n'); // 手动添加结尾变成一行数
tcpClient->write(msgArray);
send_num++;
qDebug()<<"send the "<<send_num<<" times";
}
UDP
QUdpSocket 与QTcpSocket具有相同的父类QAbstractSocket
单播:一个udp客户端发出的数据只发送到一个指定地址和端口的udp客户端,一对一传输
广播:一个udp客户端发出的数据报,在同一网络范围内所有的udp客户端都可以收到;QUdpSocket支持ipv4广播,广播经常用于实现网络发现的协议,要广播数据,只需在数据报中指定接收端的地址为QHostAddress::Broadcase,一般的广播地址为255.255.255.255
组播:也叫多播,udp客户端加入一个由组播IP地址指定的多播组,(用同一个组播IP地址接收组播数据报的所有主机构成一个组,称为多播组或组播组),成员向组播地址发送的数据报,组内成员都可以接收到
udp发送时,一般不建议超过512个字节,数据报的内容可以是字符串(字符串无需以换行符结束),也可以是自定义格式的二进制数据
readyRead / hasPendingDatagrams
readyRead是信号,表示数据缓冲区有数据到来,可以去读取数据了
hasPendingDatagrams是函数,表示套接字已经接收到数据了,并且还没有被读取和处理
二者都可以作为检查套接字中是否有数据到来
hasPendingDatagrams,如果在循环中没有主动读取数据,那么hasPendingDatagrams一致是true
注意事项
正常操作下,socket会读取完当前数据包所有字节后,再去监听加一个readyRead信号,(所以多次发送的数据只有当第一次数据接收半壁才能收到下一份数据)
readyRead信号触发问题
当接收端收到数据时,readyRead信号会被触发,并且槽函数会被调用,但是如果在槽函数处理数据的过程中,信号再次被触发,那么这个信号的状态就会被覆盖掉,从而导致槽函数只被调用一次,因此可以在槽函数中设置一个循环,直到接收缓冲区中的数据完全被处理完才退出循环
另外,如果槽函数处理数据的过程中,没有读取完整个缓冲区的数据,那么下一次readyRead信号就会被覆盖掉,导致槽函数只被调用一次,因此可以在槽函数中使用循环读取缓冲区的数据,直到读取完所有数据为止
另外,如果槽函数处理的速度太慢,导致缓冲区中的数据没有及时处理完,导致下一次数据来时缓冲区让然有数据,此时readyRead信号不会被再次触发
除了上述,在槽函数中使用循环的方法外,还可以通过调整缓冲区的大小或者增加数据处理的速度来避免数据积压缓冲区的情况
udp readAll() 和 readDatagram()
readAll()用于读取当前可用的所有数据,但是在UDP协议传输数据时,数据可能分散到多个数据包中,一个数据包的大小也可能比较大,因此使用readAll()函数不能保证能够完整的读取一条完整的消息
readDatagram()函数读取数据报文,这个函数可以指定缓存区大小,保证每次只读取一个数据报文
在UDP通信中,发送发将数据按照MTU分割成若干个数据报,每个数据报都有一个标识,接收方将这些数据报按照标识符进行重组,重组后的数据就是原始的数据
udp的readAll()函数是将接收缓冲区中的所有数据读取出来,而readDatagram函数是读取一个完整的数据报,因此,如果一个数据报被分割成了多个数据包发送,readAll函数可能无法读取完成的数据报,而readDatagram可以
当数据包是一个分片数据包,那么qt框架会将数据包缓存起来,并等待接收其他分片数据包来进行组合,当所有的分片数据包都被接收时,qt会将这些数据包组合成一个完整的数据报,并将其返回给用户
需要注意的是,udp协议并不保证数据的顺序性,因此在组合分片数据包时,需要根据数据头中的标识符将数据报进行排序,以保证数据的正确性,这个过程有qt框架自动完成,不需要手动进行处理
writeDatagram
writeDatagram会自动将数据报分割成多个数据包进行发送,当发送数据报大小超过MTU时,UDP协议会将数据报分割成多个数据包进行传输,以保证数据的可靠性和完整性,这个过程被称为分片
qt中可以通过设置UDP的最大数据报大小MTU来控制分片的大小,默认值是512个字节,如果需要发送的数据报的大小超过MTU,则需要将其分割成多个数据包进行发送,这个过程有qt框架自动完成,不需要手动分片
(自我理解):虽然UDP本身没有数据粘包的问题,但是如果手动发送的数据就不是一个根据协议定制好的数据报,那么还是需要进行手动的处理粘包问题
#include "widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
sock=new QUdpSocket(this);
sock->bind(QHostAddress("127.0.0.1"),8080);
connect(sock,&QUdpSocket::readyRead,this,&Widget::readData);
}
Widget::~Widget()
{
}
void Widget::readData()
{
// QByteArray arr=sock->read(1);
// while (sock->hasPendingDatagrams())
// {
QByteArray arr;
arr.resize(sock->pendingDatagramSize());
sock->readDatagram(arr.data(),arr.size());
qDebug()<<"read data";
// }
}
数据通讯
.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QHostAddress>
#include <QHostInfo>
#include <QUdpSocket>
#include <QTimer>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
QUdpSocket *udpSocket;
void socketStateChanged(QAbstractSocket::SocketState socketState);
void socketReadyRead();
void stop();
bool isBroadCast;
QTimer *sendDataTimer;
void timerSendData();
};
#endif // MAINWINDOW_H
.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
isBroadCast=false;
udpSocket=new QUdpSocket(this);
connect(udpSocket,&QUdpSocket::stateChanged,this,&MainWindow::socketStateChanged);
socketStateChanged(udpSocket->state());
connect(udpSocket,&QUdpSocket::readyRead,this,&MainWindow::socketReadyRead);
sendDataTimer=new QTimer(this);
sendDataTimer->setInterval(1000);
connect(sendDataTimer,&QTimer::timeout,this,&MainWindow::timerSendData);
if (udpSocket->bind(QHostAddress("127.0.0.1"),8080)) // QHostAddress::Any
{
qDebug()<<"bind ip/port: "<<udpSocket->localAddress()<<"/"<<udpSocket->localPort();
sendDataTimer->start();
}
else
{
qWarning()<<"[ERROR]: con not bind the ip";
}
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::socketStateChanged(QAbstractSocket::SocketState socketState)
{
switch(socketState)
{
case QAbstractSocket::UnconnectedState:
qDebug()<<("socket new state:UnconnectedState");
break;
case QAbstractSocket::HostLookupState:
qDebug()<<("socket new state:HostLookupState");
break;
case QAbstractSocket::ConnectingState:
qDebug()<<("socket new state:ConnectingState");
break;
case QAbstractSocket::ConnectedState:
qDebug()<<("socket new state:ConnectedState");
break;
case QAbstractSocket::BoundState:
qDebug()<<("socket new state:BoundState");
break;
case QAbstractSocket::ClosingState:
qDebug()<<("socket new state:ClosingState");
break;
case QAbstractSocket::ListeningState:
qDebug()<<("socket new state:ListeningState");
}
}
void MainWindow::socketReadyRead()
{
while (udpSocket->hasPendingDatagrams()) // 是否有待读取的数据报
{
QByteArray dataGram;
dataGram.resize(udpSocket->pendingDatagramSize()); // 待读取的数据报的字节数
QHostAddress peerAddr;
quint16 peerPort;
udpSocket->readDatagram(dataGram.data(),dataGram.size(),&peerAddr,&peerPort);
QString msg(dataGram); // QString msg=dataGram.data();
qDebug()<<"receive data from "<<peerAddr.toString()<<" / "<<QString::number(peerPort)<<": "<<msg;
}
}
void MainWindow::stop()
{
udpSocket->abort(); // 解除绑定
}
void MainWindow::timerSendData()
{
if (!isBroadCast) // 单播
{
QHostAddress targetAddr("127.0.0.1");
qint64 targetPort=5300;
QString msg="hello wrold";
QByteArray msgArray=msg.toUtf8();
udpSocket->writeDatagram(msgArray,targetAddr,targetPort);
}
else if (isBroadCast) // 广播
{
QString msg="hello wrold broadcast";
QByteArray msgArray=msg.toUtf8();
// udpSocket->writeDatagram(msgArray,QHostAddress::Broadcast,);
}
}
大小端
以字节序传输
构造与发送
// dataArr 是小端字节序
int data=0x1234;
QByteArray dataArr;
dataArr.resize(sizeof(data));
memset(dataArr.data(),0,sizeof(dataArr.size()));
memcpy(dataArr.data(),&data,dataArr.size());
// dataArrBigEnd 是大端字节序
QByteArray dataArrBigEnd;
dataArrBigEnd.resize(sizeof(data));
memset(dataArrBigEnd.data(),0,sizeof(dataArrBigEnd.size()));
dataArrBigEnd[2]=((char)((data>>8)&0xFF)); // 取高字节
dataArrBigEnd[3]=((char)((data)&0xFF)); // 取低字节
// dataArrFromBigEnd 是通过转换后的大端字节序
int dataBigEnd=qToBigEndian(data);
QByteArray dataArrFromBigEnd;
dataArrFromBigEnd.resize(sizeof(dataBigEnd));
memset(dataArrFromBigEnd.data(),0,sizeof(dataArrFromBigEnd.size()));
memcpy(dataArrFromBigEnd.data(),&dataBigEnd,dataArrFromBigEnd.size());
// sendSock->writeDatagram(dataArr,sock->localAddress(),sock->localPort()); // 小端字节序
// sendSock->writeDatagram(dataArr,sock->localAddress(),sock->localPort()); // 小端字节序
sendSock->writeDatagram(dataArrBigEnd,sock->localAddress(),sock->localPort()); // 大端字节序
sendSock->writeDatagram(dataArrBigEnd,sock->localAddress(),sock->localPort()); // 大端字节序
接收与解析
while (sock->hasPendingDatagrams())
{
// 读取
QByteArray arr;
arr.resize(sock->pendingDatagramSize());
memset(arr.data(),0,sizeof(arr.size()));
sock->readDatagram(arr.data(),arr.size());
qDebug()<<arr<<" "<<arr.toHex();
int data;
bool isLittleEndian=false; // 是否以小端发送
if (isLittleEndian)
{
// version 1 以内存拷贝方式
// memcpy(&data,arr.data(),sizeof(sizeof(data)));
// version 2 直接调用函数进行转换
data=qFromLittleEndian<int>(arr.constData());
qDebug()<<data;
}
else if (!isLittleEndian)
{
// version 1 以内存拷贝方式
// memcpy(&data,arr.data(),sizeof(data));
// int dataBigEnd=qFromBigEndian(data);
// qDebug()<<dataBigEnd;
// version 2 直接调用函数进行转换
data=qFromBigEndian<int>(arr.constData());
qDebug()<<data;
}
以QDataStream传输
注意字节序与字节对齐的问题 – 需要进一步查看
构造与发送
QByteArray sendArr;
QDataStream sendStream(&sendArr,QIODevice::WriteOnly);
// sendStream.setByteOrder(QDataStream::LittleEndian); // 如果显示设置字节序的大小,会默认是大端序
int a=0x1234;
short b=0x56;
char c=0x88;
sendStream<<b<<c<<a;
// 默认是大端的方式
sendSock->writeDatagram(sendArr,sock->localAddress(),sock->localPort());
接收与解析
while (sock->hasPendingDatagrams())
{
QByteArray arr;
arr.resize(sock->pendingDatagramSize());
memset(arr.data(),0,sizeof(arr.size()));
sock->readDatagram(arr.data(),arr.size());
qDebug()<<arr<<" "<<arr.toHex();
QDataStream readStream(arr);
// 注意要个发送时候定义的字节序相对应,默认是大端字节序
// readStream.setByteOrder(QDataStream::LittleEndian);
// readStream.setByteOrder(QDataStream::BigEndian);
int a;
short b;
char c;
readStream>>b>>c>>a;
}
字节序与QDataStream联合使用
发送
QByteArray sendArr;
int a=0x1234;
short b=0x56;
char c=0x88;
sendArr.resize(sizeof(int)+sizeof(short)+sizeof(char));
// 以小端拷贝
memcpy(sendArr.data(),&b,sizeof(b));
memcpy(sendArr.data()+sizeof(b),&c,sizeof(c));
memcpy(sendArr.data()+sizeof(c)+sizeof(b),&a,sizeof(a));
// 以小端发送
sendSock->writeDatagram(sendArr,sock->localAddress(),sock->localPort());
接收
while (sock->hasPendingDatagrams())
{
QByteArray arr;
arr.resize(sock->pendingDatagramSize());
memset(arr.data(),0,sizeof(arr.size()));
sock->readDatagram(arr.data(),arr.size());
qDebug()<<arr<<" "<<arr.toHex();
QDataStream readStream(arr);
// 注意要个发送时候定义的字节序相对应,
readStream.setByteOrder(QDataStream::LittleEndian); // 小端字节序接收
// readStream.setByteOrder(QDataStream::BigEndian);
int a;
short b;
char c;
readStream>>b>>c>>a;
}
QDataStream 直接作用在socket上 – 还没细看
接受自定义类型数据
构造 与发送数据
// 头文件中的声明
struct ADC
{
ADC(){} // 对于构造函数 不需要写分号;
unsigned short usCmdHead1 ; //0xffff
unsigned short usCmdLen2 ; //结构体长度
unsigned short wCmdFlag; //帧头 0xabcd
unsigned char bMonth;// 未填
unsigned short TailFlag; //帧尾 0xdcba
};
// 构造函数中的代码
adc->usCmdHead1=0xffff;
adc->usCmdLen2=(unsigned short)(sizeof(ADC));
adc->wCmdFlag=0xabcd;
adc->bMonth=0x88;
adc->TailFlag=0xdcba;
qDebug()<<sizeof(ADC);
qDebug()<<sizeof(char)<<sizeof(unsigned short);
QByteArray adcArr(reinterpret_cast<char *>(adc),sizeof(ADC));
sendSock->writeDatagram(adcArr,sock->localAddress(),sock->localPort());
adcRecv=new ADC();
memset(adcRecv,0,sizeof(ADC));
接收与解析数据
主要看如何进行赋值运算
// 与readyRead信号关联的槽函数
while (sock->hasPendingDatagrams())
{
QByteArray arr;
arr.resize(sock->pendingDatagramSize());
memset(arr.data(),0,sizeof(arr.size()));
sock->readDatagram(arr.data(),arr.size());
qDebug()<<arr<<" "<<arr.toHex();
// version 1
adcRecv=reinterpret_cast<struct ADC *>(arr.data()); // !!!
qDebug()<<adcRecv->usCmdHead1;
qDebug()<<adcRecv->usCmdLen2;
qDebug()<<adcRecv->wCmdFlag;
qDebug()<<adcRecv->bMonth;
qDebug()<<adcRecv->TailFlag;
// version 2
ADC adc_temp;
memcpy(&adc_temp,arr.data(),sizeof(adc_temp)); // !!!
qDebug()<<adc_temp.usCmdHead1;
qDebug()<<adc_temp.usCmdLen2;
qDebug()<<adc_temp.wCmdFlag;
qDebug()<<adc_temp.bMonth;
qDebug()<<adc_temp.TailFlag;
}