当前位置: 首页 > news >正文

Qt网络编程——QTcpServer和QTcpSocket

文章目录

    • 核心API
    • TCP回显服务器
    • TCP回显客户端

核心API

QTcpServer用于监听端口和获取客户端连接

名称类型说明对标原生API
listen(const QHostAddress&, quint16 port)方法绑定指定的地址和端口号,并开始监听bind和listen
nextPendingConnection()方法从系统中获取到一个建立好的tcp连接
返回一个QTcpSocket,表示这个客户端的连接
通过这个socket对象完成和客户端之间的通信
accept
newCondition()信号有新的客户端建立好连接之后触发无(类似IO多路复用的通知机制)

QTcpSocket用于客户端和服务器之前的数据交互

tcp读取的数据是字节流,因此读取和返回的都是字节数组,这和udp的QNetworkDatagram数据报不一样

事件循环,可以简单理解为是Qt程序内部一个带有“生物钟”这样的东西,周期性执行一些逻辑

名称类型说明对标原生API
readAll()方法读取当前接收缓冲区的所有数据
返回QByteArray对象
read
write(const QByteArray&)方法将数据写入socket当中write
deleteLater方法暂时把socket对象标记为无效
Qt会在下一个事件循环中构造释放该对象
readyRead信号有数据到达并准备就绪时触发
disconnected信号连接断开时触发

QByteArray是字节数组,可以和QString互相转换

  • QString的构造函数可以把QByteArray转换成QString
  • QStringtoUtf8函数可以把QString转成QByteArray

TCP回显服务器

用这些接口,写一个回显服务器。

创建项目之后,如果要进行网络编程,第一步就是在.pro文件中加入network模块

image-20240925220250317

界面:

image-20240925220340130

widget.h

#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include <QTcpServer>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();void processConnection();QString process(const QString request);
private:Ui::Widget *ui;QTcpServer *tcpServer;
};
#endif // WIDGET_H

widget.cpp

  • 绑定和监听端口号一定是要等准备工作做完之后再进行,比如说如何处理连接、如何处理请求等…

  • 需要手动释放clientSocket,因为它是每个客户端都有这样一个对象,而QTcpServerQUdpServer都是只有一份。如果不对断开连接的客户端进行释放,累计的客户端会越来越多,这会导致两个问题:文件描述符泄漏内存泄漏

    也不能直接delete clientSocket,因为当前槽函数主要是围绕clientSocket来进行操作的,一旦delete,其他逻辑就无法使用clientSocket,使用要保证delete操作是最后一步,而且不会被return或者抛出异常给跳过。

    Qt提供了deleteLater,不是立即销毁,而是告诉Qt,下一轮事件循环中,再进行上述销毁操作。

    槽函数是在事件循环中进行的,进入下一轮事件循环表明上一轮事件肯定结束了。

#include "widget.h"
#include "ui_widget.h"
#include<QMessageBox>
#include<QTcpSocket>
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);//修改窗口标题this->setWindowTitle("tcp服务器");//创建QTcpServer实例tcpServer = new QTcpServer(this);//连接信号槽(如何处理连接)connect(tcpServer, &QTcpServer::newConnection, this, &Widget::processConnection);//绑定并监听端口号if(!tcpServer->listen(QHostAddress::Any, 8080)){QMessageBox::critical(this, "服务器启动失败", tcpServer->errorString());return;}
}Widget::~Widget()
{delete ui;
}void Widget::processConnection()
{//拿到socket对象QTcpSocket *clientSocket = tcpServer->nextPendingConnection();//peerAddress表示对端的ip地址QString log = "[" + clientSocket->peerAddress().toString() + ":" + QString::number(clientSocket->peerPort()) + "] online";//显示到界面ui->listWidget->addItem(log);//通过信号槽处理客户端发来的请求connect(clientSocket, &QTcpSocket::readyRead, this, [=](){//读取请求QString request = clientSocket->readAll();//处理请求const QString &response = process(request);//返回响应clientSocket->write(response.toUtf8());//记录日志QString log = "[" + clientSocket->peerAddress().toString() + ":" + QString::number(clientSocket->peerPort()) + "]"+ "req: " + request + ", resp: " + response;ui->listWidget->addItem(log);});//客户端断开连接//disconnected表示已经断开,是一个信号connect(clientSocket, &QTcpSocket::disconnected, this, [=](){QString log = "[" + clientSocket->peerAddress().toString() + ":" + QString::number(clientSocket->peerPort()) + "] offline";ui->listWidget->addItem(log);//手动释放clientSocketclientSocket->deleteLater();});
}QString Widget::process(const QString request)
{return request;
}

此段代码,从某种意义上来说,不够严谨,因为tcp是面向字节流的,可能会分为多段。

更好的做法是,将收到的数据,放到一个较大字节的缓冲区当中,然后按照约定好的协议,进行数据解析提取。

TCP回显客户端

ui界面:

image-20240925225359499

对于客户端,使用的就是QTcpSocketQTcpServer只是在服务端使用的

  • 调用connectToHost函数,此时系统就开始和对方服务器三次握手,三次握手也是需要时间的,而这个函数并不会阻塞等待握手完毕,是一个非阻塞的函数。
    所以需要搭配waitForConnected,等待连接建立成功
#include "widget.h"
#include "ui_widget.h"
#include<QMessageBox>
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);//设置窗口标题this->setWindowTitle("客户端");//创建socket对象实例socket = new QTcpSocket(this);//和服务器建立连接socket->connectToHost("127.0.0.1", 8080);//连接信号槽connect(socket, &QTcpSocket::readyRead, this, [=](){//读取响应内容QString response = socket->readAll();//响应内容显示到界面ui->listWidget->addItem("server say# " + response);});//等待连接建立结果if(!socket->waitForConnected()){QMessageBox::critical(this, "连接服务器失败", socket->errorString());return;}}Widget::~Widget()
{delete ui;
}void Widget::on_pushButton_clicked()
{//获取输入框内容const QString &text = ui->lineEdit->text();//发送到服务器socket->write(text.toUtf8());//将发送的消息显示到界面ui->listWidget->addItem("client say#" + text);//清空输入框内容ui->lineEdit->setText("");
}

在这里插入图片描述

Linux当中写Tcp服务器的时候,如果多个客户端同时访问,就只会生效一个,然后引入线程,每个客户端一个线程;

而这里并没有出现这类情况,这是因为之前是写的两层循环,里面的循环没有结束,导致外层循环不能快速调用到accpet,导致第二个客户端无法进行处理。

引入多线程,本质上就是将双重循环,化简成两个独立的循环。

Qt里面的信号槽机制,就无需写这些循环,比较方便。

http://www.lryc.cn/news/446299.html

相关文章:

  • CentOS 7 aarch64制作openssh 9.9p1 rpm包 —— 筑梦之路
  • Flink和Spark的区别
  • 以太网开发基础-MAC和PHY
  • Java 发布jar包到maven中央仓库(2024年9月保姆级教程)
  • Pandas和Seaborn可视化详解
  • 【Python】Windows下安装使用FFmpeg
  • LLM - 使用 XTuner 指令微调 多模态大语言模型(InternVL2) 教程
  • 【Python】数据可视化之热力图
  • 个人博客系统测试(selenium)
  • 【速成Redis】01 Redis简介及windows上如何安装redis
  • 入侵检测系统(IDS)和入侵预防系统(IPS)
  • pytorch 加载模型参数后 如何测试数据,应用模型预测数据,然后连续变量转换成 list 或者numpy.array padans并保存到csv文件中
  • uni-app开发流程(开发、预览、构建和发布过程)
  • Linux Shell: 使用 Expect 自动化 SCP 和 SSH 连接的 Shell 脚本详解
  • 深入分析MySQL事务日志-Undo Log日志
  • 828华为云征文 | 在Huawei Cloud EulerOS系统中安装Docker的详细步骤与常见问题解决
  • 什么是数据增强中的插值法?
  • springboot实战学习(9)(配置mybatis“驼峰命名“和“下划线命名“自动转换)(postman接口测试统一添加请求头)(获取用户详细信息接口)
  • 之前做了抵押贷款,现在房市不景气,马上贷款要到期了该怎么办?
  • poi生成的ppt,powerPoint打开提示内容错误解决方案
  • 基于stm32物联网身体健康检测系统
  • BeautifulSoup4在爬虫中的使用
  • Laya2.x出包alipay小游戏
  • Vue极简入门
  • 系统敏感信息搜索工具(支持Windows、Linux)
  • Fyne ( go跨平台GUI )中文文档-容器和布局 (四)
  • 文心智能体 恐怖类游戏
  • 智慧城市运营模式--政府和社会资本合作
  • 【Python报错已解决】ValueError: cannot convert float NaN to integer
  • ClickHouse 与 Quickwit 集成实现高效查询