目录
一.项目展示
编辑
二.开发流程
三.QTcpServer、QTcpSocket、QUdpSocket类的学习
1.QTcpServer服务端
2.QTcpSocket客户端
3.Udp通信
四.网络调试助手
1.首先我们实现当用户选择不同协议类型时不同的UI组件如何切换
2.实现打开/关闭按键图片的切换
方式一:通过其父类所提供的void setIcon(const QIcon &icon)函数去实现
方式二:重写QPushButton的事件
3.定时发送
4.实现代码如下
一.项目展示
二.开发流程
三.QTcpServer、QTcpSocket、QUdpSocket类的学习
Tcp面向连接的基于字节流的协议(点对点)
Udp面向无连接的基于报文的协议(一对一、一对多、多对多)
3.1QTcpServer类中包含以下函数
listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0); | 用于监听 |
QTcpSocket nextPendingConnection(); | 从监听套接字中获取一个已准备好的客户端进行连接 |
deleteLater(); | 释放资源 |
disconnectFromHost(); | 断开连接 |
QTcpServer类包含以下信号
newConnection(); | 监听到有新的连接时 |
acceptError(QAbstractSocket::SocketError socketError); | 尝试接受新的连接时发生错误 |
3.2QTcpSocket类包含以下函数
connectToHost(const QHostAddress &address = QHostAddress::Any, quint16 port = 0); | 连接服务端 |
QHostAddress peerAddress(); | 获取ip地址 |
quint16 clientPort(); | 获取端口号 |
readall(); | 读取消息 |
write(QByteArray data); | 发送数据 |
state(); | 判断当前状态,如:if(socket->state== QAbstractSocket::ConnectedState) |
deleteLater(); | 释放资源 |
QTcpSocket类包含以下信号
connected(); | 成功连接 |
readyRead(); | 有新的数据发来时 |
disconnected(); | 断开连接时 |
3.3QUdpSocket类包含以下函数
bind(const QHostAddress &address = QHostAddress::Any, quint16 port = 0); | 绑定ip地址和端口,接受数据时使用 |
bool hasPendingDatagrams() const; | 用于检查是否有待处理的数据报。返回 true: 表示有一个或多个待处理的数据报 |
pendingDatagramSize(); | 通常用于设置接受缓冲区的大小时使用,返回下一个待处理的数据报的大小 |
readDatagram(buffer.data(), buffer.size(), &sender, &senderPort); | 将接收到的数据存储到提供的缓冲区中 |
writeDatagram(<要发送的数据>, <ip地址>, <端口号>); | ip为QHostAddress::Broadcast代表广播发送 |
close(); | 关闭udp套接字 |
deleteLater(); | 释放资源 |
QUdpSocket类包含以下信号
readyRead(); | 有新的数据发来时 |
3.4以下程序可获取本地ip地址
// 获取本地主机名称
QString localHostName = QHostInfo::localHostName();
// 根据本地主机名称获取本地主机信息
QHostInfo info = QHostInfo::fromName(localHostName);
// 将本地主机的所有 IP 地址赋值给容器中
QList<QHostAddress> addresses = info.addresses();
// 清空 comboBox_2 并添加 IPv4 地址
ui->comboBox_2->clear();
for (const auto& address : addresses) {
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
qDebug() << "IPv4 Address:" << address.toString();
ui->comboBox_2->addItem(address.toString());
}
}
1.QTcpServer服务端
QTcpServer* server = new QTcpServer(this); //创建一个监听套接字
server->listen(); //开始监听指定ip与端口
connect(server, &QTcpServer::newConnection, this, [=](){ //信号与槽连接:当监听到有新的连接时
QTcpSocket* clientSocket; //创建一个网络套接字
clientSocket = server->nextPendingConnection(); //从监听套接字中获取一个已准备好的客户端进行连接
//获取客户端的ip与端口号
QHostAddress clientAddress = clientSocket->peerAddress();
quint16 clientPort = clientSocket->peerPort();
//信号与槽连接:当有新的消息时
connect(clientSocket, &QTcpSocket::readyRead, this, [=](){
QByteArray data = clientSocket->readAll(); //读取消息
);
//信号与槽连接:当客户端断开连接时
connect(clientSocket, &QTcpSocket::disconnected, this, [=](){
clientPort->deleteLater(); //释放资源
);
if(clientSocket->state() == QAbstractSocket::ConnectedState){
cnt = clientSocket->write(arrayData);
}
);
server->close(); //关闭服务端
2.QTcpSocket客户端
QTcpSocket* socket = new QTcpSocket(this);
connect(socket, &QTcpSocket::connected, this, [=]() { //客户端连接到服务端槽函数
//...
});
connect(socket, &QTcpSocket::readyRead, this, [=]() { //客户端接收到数据槽函数
int sum = 0;
QByteArray data = socket->readAll(); //读取接收到的数据
sum = data.size();
}
connect(socket, &QTcpSocket::disconnected, this, [=]() { //客户端与服务端断开连接槽函数
qDebug() << "Disconnected from server.";
socket->deleteLater(); //断开连接时删除 socket
});
if(socket->state() == QAbstractSocket::ConnectedState){ //客户端发送数据
socket->write(arrayData);
}
socket->connectToHost(remIpaddress, remPort); //尝试连接服务端
socket->disconnectFromHost(); //主动断开与客户端的连接
socket->deleteLater(); //释放资源
3.Udp通信
QUdpSocke* udp = new QUdpSocket(this);
udp->bind(ipaddress, honts.toInt(); //接受数据时使用,绑定ip地址和端口
//绑定信号与槽,当接收到数据时
connect(udp, &QUdpSocket::readyRead, this, [=](){
while (udp->hasPendingDatagrams()) { //检查是否有待处理的数据报,循环读取
QByteArray buffer;
buffer.resize(int(udp->pendingDatagramSize())); //确保接收缓冲区大小合适
QHostAddress sender; //发送者的地址
quint16 senderPort; //发送者的端口
//读取数据并存入buffer中
udp->readDatagram(buffer.data(), buffer.size(), &sender, &senderPort);
}
);
udp->writeDatagram(<要发送的数据>, <ip地址>, <端口号>);
QHostAddress broadcastAddress = QHostAddress::Broadcast; // 设置广播地址
qint64 bytesWritten = con->writeDatagram(arrayData, broadcastAddress, port); // 发送广播
udp->close();
udp->deleteLater();
四.网络调试助手
1.首先我们实现当用户选择不同协议类型时不同的UI组件如何切换
绑定信号&QComboBox::currentIndexChanged与QComBobox组件,当QComboBox组件的索引变化时,执行下面槽函数,实现不同的UI组件之间的切换
void Widget::on_CurrentIndexChanged() {
// 获取用户当前选择的索引
currentIndex = ui->comboBox_1->currentIndex();
// 删除垂直布局中的第六个组件(如果存在)
if (ui->verticalLayout->count() >= 6) {
QWidget* widget = ui->verticalLayout->itemAt(5)->widget(); // 获取第六个控件
if (widget) {
ui->verticalLayout->removeWidget(widget); // 从布局中移除
widget->deleteLater(); // 删除组件
}
}
// 根据选择的索引更新界面
if (currentIndex == 1) { // 用户选择了客户端
ui->label_2->setText("(2)本地主机地址");
ui->label_3->setText("(3)远程主机地址");
box = new QComboBox(this); // 创建新的 QComboBox
box->addItem("192.168.56.1 :8080"); // 向组件中添加内容
box->setEditable(true); // 设置组件中的内容可修改
ui->verticalLayout->addWidget(box); // 将组件添加到垂直布局中
box->show(); // 显示新创建的 QComboBox
} else if (currentIndex == 0) { // 用户选择了UDP
ui->lineEdit_rev->setText("192.168.238.1");
ui->lineEdit_revHonts->setText("8080");
ui->label_2->setText("(2)本地主机地址");
ui->label_3->setText("(3)本地主机端口");
edt = new QLineEdit(this); // 创建新的 QLineEdit
edt->setText("8080"); // 设置新的 QLineEdit 显示的内容
ui->verticalLayout->addWidget(edt); // 将组件添加到垂直布局中
edt->show(); // 显示新创建的 QLineEdit
} else { // 用户选择了服务端
ui->label_2->setText("(2)本地主机地址");
ui->label_3->setText("(3)本地主机端口");
edt = new QLineEdit(this); // 创建新的 QLineEdit
edt->setText("8080"); // 设置新的 QLineEdit 显示的内容
ui->verticalLayout->addWidget(edt); // 将组件添加到垂直布局中
edt->show(); // 显示新创建的 QLineEdit
}
}
2.实现打开/关闭按键图片的切换
方式一:通过其父类所提供的void setIcon(const QIcon &icon)函数去实现
const QString ICON_PATH_MM = ":/pictures/mm.png"; // 图标路径常量
const QString ICON_PATH_PP = ":/pictures/pp.png"; // 图标路径常量
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->pushButton->setCheckable(true);
// 设置按钮透明背景
ui->pushButton->setStyleSheet("QPushButton {"
"background-color: transparent;"
"border: none;" // 可选:去掉按钮边框
"}");
// 设置图标和大小
setButtonIconAndSize(ICON_PATH_MM);
}
void Widget::on_pushButton_clicked(bool checked)
{
// 根据按钮的 checked 状态切换图标
setButtonIconAndSize(checked ? ICON_PATH_PP : ICON_PATH_MM);
}
// 封装设置图标和大小的逻辑
void Widget::setButtonIconAndSize(const QString &iconPath)
{
QSize buttonSize = ui->pushButton->size(); // 获取按钮的当前大小
ui->pushButton->setIconSize(buttonSize); // 设置图标大小
ui->pushButton->setIcon(QIcon(iconPath)); // 设置图标
}
方式二:重写QPushButton的事件
bool t = true;
myBtnOpen::myBtnOpen(QWidget *parent):QPushButton(parent)
{
// 加载初始图片
pic.load(":/pictures/mm.png");
// 设置按钮的固定大小为图片的大小
//setFixedSize(pic.size());
//setFixedSize(100,100);
// 刷新界面,触发 paintEvent 进行绘制
update();
}
void myBtnOpen::paintEvent(QPaintEvent *e)
{
// 创建一个 QPainter 对象,负责绘制图片
QPainter painter(this);
// 使用 QPainter 在按钮区域内绘制当前加载的图片
painter.drawPixmap(rect(), pic);
}
void myBtnOpen::mousePressEvent(QMouseEvent *e)
{
if(e->button() == Qt::LeftButton){
//打开
if(t == true){
pic.load(":/pictures/mm.png");
update();
t = false;
}else{
pic.load(":/pictures/pp.png");
update();
t = true;
}
}
QPushButton::mousePressEvent(e);
}
3.定时发送
通过定时器实现,当用户勾选定时发送组件时
1.初始化定时器,设置定时器定时时间,启动定时器。
2.在构造函数中绑定定时器的超时信号与槽函数,通过Lambda表达式调用发送函数既可以
//定时发送槽函数
void Widget::on_checkBox_10_clicked(bool checked)
{
if(checked){
//设置定时器定时时间
timer->setInterval(ui->lineEdit_msData->text().toInt());
//启动定时器
timer->start();
}else{
//停止定时器
timer->stop();
}
}
//发送按键槽函数
void Widget::on_pushButton_send_clicked()
{
//(1)先获取用户输入的要发送的内容
QString data = ui->textEdit_sendData->toPlainText();
QByteArray arrayData = data.toLocal8Bit();
int cnt = 0;
//(2)判断Hex发送是否勾选,此处为勾选
if (ui->checkBox_9->isChecked()) {
// a.检查字节数是否是偶数
if (0 != arrayData.size() % 2) {
ui->label_state->setText("input error!");
return;
}
// b.检查是否符合Hex表达
for (char c : arrayData) {
if (!isxdigit(c)) {
ui->label_state->setText("input error!");
return;
}
}
// c.转化为16进制
arrayData = QByteArray::fromHex(arrayData);
}
//(3).开始发送
if (t == 1) { // 客户端发送数据
cnt = socket->write(arrayData);
} else if (t == 2) { // 服务端发送数据
cnt = clientSocket->write(arrayData);
}
//(4)判断是否发送成功
if (cnt == -1) {
ui->label_state->setText("send error!");
} else {
if (ui->checkBox_8->isChecked()) {
ui->textEdit_sendData->clear();
}
sendCnt += cnt;
ui->label_state->setText("send OK!");
ui->label_sendCnt->setText("发送: " + QString::number(sendCnt));
}
}
4.实现代码如下
tunnek/mi (github.com)