UI界面图
1. 工程文件及头文件添加代码
工程文件xxx.pro中添加:
#网口通信
QT += network
头文件xxx.h中添加:
//UDP
#include <QUdpSocket>
#include <QNetworkDatagram>
2. 接收数据
(1) uSocket初始化
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
/* UDP -注意逻辑- */
//创建QUdpSocket对象
uSocket = new QUdpSocket(this);
//绑定本地IP和端口号,IPLocal与PortLocal为自定义的全局变量
uSocket->bind(QHostAddress(IPLocal),PortLocal);
//默认关闭
uSocket->close();
//收到数据时,会触发readyRead()信号,自定义readPendingDatagrams()进行读取数据
connect(uSocket,&QUdpSocket::readyRead, this,&MainWindow::UDP_DataReceived);
}
/* UDP网口 ↓*/
//默认本地端口号
quint16 PortLocal =8089;
//默认本地IP地址
QString IPLocal ="192.168.10.200";
QT官方文档:bind函数的定义和所需参数, 咱直接传个IP地址和端口号就行
// ### Qt6: make the first one virtual
bool bind(const QHostAddress &address, quint16 port = 0, BindMode mode = DefaultForPlatform);
bool bind(quint16 port = 0, BindMode mode = DefaultForPlatform);
问题记录:关于QUdpSocket数据类型的 bind () 与 close () 与 open ()
之前将IP与端口号绑定后,正常接收数据,关闭网口再打开发现收不到数据了😂
后面发现UDP关闭后,再打开需要重新绑定,即便uSocket对象一直存在。
另外open的时候注意,记得写open方式,QT官方文档:
virtual bool open(OpenMode mode);
public:
enum OpenModeFlag {
NotOpen = 0x0000,
ReadOnly = 0x0001,
WriteOnly = 0x0002,
ReadWrite = ReadOnly | WriteOnly,
Append = 0x0004,
Truncate = 0x0008,
Text = 0x0010,
Unbuffered = 0x0020
};
Q_DECLARE_FLAGS(OpenMode, OpenModeFlag)
而且,记得传参数时加上QIODevice:: (我之前没加,一直尝试,不知道哪出了问题,坑。)
uSocket->open(QIODevice::ReadWrite);
结论:close后open要重新bind
(2)UDP接收数据
//UDP接收
void MainWindow::UDP_DataReceived(){
//while循环中读取数据,只要有数据,就一直读取并处理。
while (uSocket->hasPendingDatagrams()) {
QNetworkDatagram datagram = uSocket->receiveDatagram();
UDP_DataHex.append(datagram.data().toHex());
//报文头判断,true则继续,false则将数据丢弃
if(UDP_DataHex.at(0)=='f'||UDP_DataHex.at(1)=='f'){
count +=1;
//qDebug()<<"UDP test count:"<< count;
if(count==10){
DataRecevied_Hex=UDP_DataHex;
//数据模拟(连通单片机时应注释掉)
DataRecevied_Hex="ffb27f02d9022600e114";
//校验位判断,true则输出,false则将数据丢弃
if(Parity_DataReceived()){
ui->IPDestination->setText(datagram.senderAddress().toString());
ui->PortDestination->setText(QString::number(datagram.senderPort()));
ui->UDP_DataReceived->append(Line75Percent+TimeStamp() + DataFactory(DataRecevied_Hex));
//AngleCurrent();
}else{}
//UDP_DataHex数据输出后,清空内容,并将计数器归零
count=0;
UDP_DataHex="";
}else{}
}else{
//UDP_DataHex头两个字符不是ff,则清空内容,并将计数器归零
count=0;
UDP_DataHex="";
}
}
}
3. 接收数据校验
将接受到的数据进行校验和,如何最后接收到的数据最后两位与计算后所得相同,返回true。
bool MainWindow::Parity_DataReceived(){
bool judge;
//校验位计算与转换
int ache=0;
//↓除去校验位本身的长度
for(int i=0;i<DataRecevied_Hex.size()-2; i+=2){
ache += HexToDec(DataRecevied_Hex.at(i)).toInt()*16 + HexToDec(DataRecevied_Hex.at(i+1)).toInt();
//qDebug() << "test"<< ache;
}
QString Parity =QString::number(ache, 16);
if(Parity.size()==1){
Parity.prepend("000");
}
else if(Parity.size()==2){
Parity.prepend("00");
}
else if(Parity.size()==3){
Parity.prepend("0");
}
Parity.remove(0,2);
QString str =DataRecevied_Hex.at(18);
str.append(DataRecevied_Hex.at(19));
if(Parity==str){
qDebug() << "校验成功,校验位为:" << Parity;
return judge = true;
}else{
ui->UDP_DataReceived->append("<font color=\"#B22222\">数据校验失败,接收校验位为:"+str+ " 应为:"+Parity);
qDebug() << "校验失败,数据校验位为:" << str << "计算校验位为:" << Parity;
return judge = false;
}
}
4. UDP数据发送
//最终数据发送[判断是 串口发送 or UDP发送]
void MainWindow::SendMode(){
QByteArray dataByte =QByteArray::fromHex(DataSend_Hex.toLatin1());
//发送方式判断
if(serial->isOpen()){
//串口写入
qDebug() << "串口发送数据:"<< dataByte;
serial->write(dataByte);
ui->Serial_DataSend->append(Line75Percent+TimeStamp()+"<font color=\"#05BDFF\">"+DataFactory(DataSend_Hex));
}else if(uSocket->isOpen()){
qDebug() << "UDP发送数据:";
//读取用户输入的IP地址与端口号
QString IPDestination = ui->IPDestination->text();
quint16 PortDestination = ui->PortDestination->text().toInt();
qDebug() << "IPDestination:"<<IPDestination;
qDebug() << "PortDestination:"<<PortDestination;
//发送数据
uSocket->writeDatagram(dataByte,QHostAddress(IPDestination),PortDestination);
//控件增加
ui->UDP_DataSend->append(Line75Percent+TimeStamp()+"<font color=\"#05BDFF\">"+DataFactory(DataSend_Hex));
}else{
qDebug() << "请打开串口或UDP";
}
};
数据发送用的函数QT官方文档:
qint64 writeDatagram(const char *data, qint64 len, const QHostAddress &host, quint16 port);
writeDatagram(数据,QHostAddress(IP地址),端口)
5. 控件QLineEdit输入限制
实际应用时,应该限制用户所输入的内容(如只能输入IP地址):
(1)方法一:正则表达式
首先这种方法点号是不驻留的,其次输入时无法跳过点号完全删除其他数字
例如255.123.255.255,是没办法直接删除123的,必须将后面内容全部删除,才能更改123
ui->IPLocal->setValidator(new QRegExpValidator(QRegExp("\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b")));
(2)方法二: setInputMask函数
ui->IPLocal->setInputMask("000.000.000.000; ");
官方文档内容:
void setInputMask(const QString &inputMask);
其中"000.000.000.000; "的含义就是限制IP地址输入,这种方法点号会驻留就如UI界面中显示的一样,无法删除,但不存在方法一的问题。
6. 用户更改IP地址
首先判断输入是否为空,不为空再判断IP地址格式是否正确
//本地IP地址与端口号更改
void MainWindow::on_UDP_IPChange_clicked(){
if(ui->IPLocal->text()!=""&&IsIP(ui->IPLocal->text())){
IPLocal = ui->IPLocal->text();
}
if(ui->PortLocal->text()!=""){
PortLocal = ui->PortLocal->text().toInt();
}
uSocket->close();
uSocket->bind(QHostAddress(IPLocal),PortLocal);
uSocket->open(QIODevice::ReadWrite);
qDebug() <<"IP地址更改test:"+ IPLocal+" "+PortLocal;
};
QRegExp为QT的正则表达式,我直接趴下来的,具体表达式的含义可以自行查阅。
//是否为IP地址判断
bool MainWindow::IsIP(QString currentIP)
{
QRegExp rxp("\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b");
if(!rxp.exactMatch(currentIP))
{
QMessageBox::information(this, tr("错误"), tr("IP地址错误"));
return false;
}
return true;
}
7. 总结
相比串口通信,UDP参数不多,主要就四个,本机IP、本机端口、目标IP、目标端口。
经过串口的各种数据转换洗礼,这里反而轻松不少,只是要注意UDP接收数据容易出现问题,要想办法验证数据头和校验位。(其实保险起见,串口也应该验证一下的,(╯▔皿▔)╯再说吧)