Qt网络编程——QTcpServer和QTcpSocket

news2024/9/30 16:56:00

文章目录

    • 核心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_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    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);
        //手动释放clientSocket
        clientSocket->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.coloradmin.cn/o/2166456.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【2024/9/25更新】最新版植物大战僵尸V2.5.1发布啦

下载链接⬇️⬇️ 最新版V2.5.1下载 点击下载 历史版本下载 点击下载 更新公告 2.5.1版本更新公告: 关卡阅整 两面夹击关卡系列关卡降低出怪倍率 两面夹击关卡出怪种类调整 两面夹击部分关卡初始阳光调整 两面夹击关卡中可使用投手类植物- 两面夹击关卡中的脑子血量…

远程升级频频失败?原因竟然是…

最近有客户反馈在乡村里频繁出现掉线的情况。 赶紧排查原因&#xff01; 通过换货、换SIM卡对比排查测试&#xff0c;发现只有去年采购的那批模块在客户环境附近会出现掉线的情况&#xff0c;而今年采购的模块批次就不会掉线。。。 继续追究原因&#xff0c;联系对应的销售工…

基于springboot vue 大学生竞赛管理系统设计与实现

博主介绍&#xff1a;专注于Java vue .net php phython 小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作 ☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不然下次找不到哟 我的博客空间发布了1000毕设题目 方便大家学习使用 感兴趣的…

路径报错问题

项目场景&#xff1a; 假设这是我的项目结构&#xff0c;我现在需要在aa.js文件中引入并使用aa.geojson文件&#xff0c; 问题&#xff1a; 当我引入路径是const filePath ../geo/aa.geojson&#xff1b;的时候&#xff0c;系统报错 "aa.geojson is not Found",找不…

【WRF数据介绍第二期】气象驱动场数据介绍及下载

WRF数据介绍第二期&#xff1a;气象驱动场数据介绍及下载 WRF官网-Free Data数据下载 EAR5数据数据下载 参考 WRF运行流程如下&#xff0c;所需的外部数据源包括静态地理数据&#xff08;Static Geography Data&#xff09;和网格气象数据&#xff08;Gridded Meteorological D…

基于AI网关的智慧煤矿安全监测应用

煤矿安全一直是矿业管理的重中之重。由于煤矿环境的恶劣与复杂性&#xff0c;例如工作中间环节多、设施设备多样且集中、空间狭小、环境闭塞、有害气体隐患、粉尘聚集等&#xff0c;针对煤矿的安全监测和防范时常面临着极大的挑战。 随着AI技术的发展与普及&#xff0c;依托AI实…

关于前端框架的对比和选择

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///C爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于【前端框架的对比和选择】相关内容&…

力扣 中等 275.H指数

文章目录 题目介绍题解 题目介绍 题解 h指数不一定都满足citations[mid] n - mid&#xff0c;例如[0,1,4,5,6]的h指数是3。 题目说了用对数时间复杂度来实现&#xff0c;说明一定是用二分法&#xff0c;草纸上推导一下即可得出以下代码。 class Solution {public int hInde…

MySQL自动测试框架Test Framework工具实践

导读 之前的文章&#xff08;《MySQL自动测试框架Test Framework浅析》&#xff09;从源码级别对MySQL自动测试框架Test Framework进行了简要分析&#xff0c;本文接下来从实践的角度介绍Test Framework工具的使用方法。 1 简介 Test Framework主要应用于MySQL等相关数据库项…

Docker 容器技术:颠覆传统,重塑软件世界的新势力

一、Docker简介 什么是docker Docker 是一种开源的容器化平台&#xff0c;它可以让开发者将应用程序及其所有的依赖项打包成一个标准化的容器&#xff0c;从而实现快速部署、可移植性和一致性。 从功能角度来看&#xff0c;Docker 主要有以下几个重要特点&#xff1a; 轻量…

用Python实现运筹学——Day 3: 线性规划模型构建

一、学习内容 线性规划模型构建的步骤与技巧 线性规划&#xff08;Linear Programming, LP&#xff09;模型构建是运筹学中的核心内容&#xff0c;通常用于求解资源的最优分配问题。要从实际问题中提取出一个线性规划模型&#xff0c;需要按照以下步骤进行&#xff1a; 问题描…

JavaWeb——Vue组件库Element(1/6):快速入门(什么是Element,安装,引入ElementUI组件库,复制组件代码,启动项目 )

目录 什么是Element 快速入门 安装 引入ElementUI组件库 访问官网&#xff0c;复制组件代码 启动项目 小结 了解完前端的工程化之后&#xff0c;接下来了解一门新的前端技术&#xff1a;Vue 的组件库 Element。 学习完 Element 之后&#xff0c;即使作为一名 Java 后…

VMware 如何上网

需求 在PC window中下载了VMware&#xff0c;并且加载的是Ubuntu系统。PC电脑连接的是手机热点。 可以看出WLAN连接的名称是&#xff1a;Wi-Fi 6 AX201 16MHz 如何让Ubuntu系统也能够上网。并且更新库&#xff0c;能够sudo apt-get install xxx相关库。 目前虚拟机中的Ubun…

PMP--二模--解题--121-130

文章目录 9.资源管理&#xff01;团建不是万能的121、 [单选] 项目团队中一些经验丰富的成员抱怨项目经理。这些高级项目团队成员觉得项目经理在事无巨细地管理他们&#xff0c;阻碍他们完成工作。当项目经理意识到这些问题时&#xff0c;应该怎么做&#xff1f; 14.敏捷--组织…

深度学习之开发环境(CUDA、Conda、Pytorch)准备(4)

目录 1.CUDA 介绍 1.1 CUDA 的基本概念 1.2 CUDA 的工作原理 1.3 CUDA 的应用领域 2. 安装CUDA 2.1 查看GPU版本 2.2 升级驱动&#xff08;可选&#xff09; 2.3 查看CUDA版本驱动对应的支持的CUDA ToolKit工具包 2.4 下载Toolkit 2.5 安装&#xff08;省略&#xff0…

数据结构讲解二叉树 【一】

&#x1f381;&#x1f381;创作不易&#xff0c;关注作者不迷路&#x1f380;&#x1f380; C语言二叉树 【一】 前言一、数概念及结构1.数的概念1.2树的相关概念1.3树的表示 二、二叉树的概念及结构2.12.2二叉树的性质2.3二叉树的存储结构 三、二叉树的顺序结构实现3.1二叉树…

【有啥问啥】“弱激励学习(Weak Incentive Learning)”的原理与过程解析

“弱激励学习&#xff08;Weak Incentive Learning&#xff09;”的原理与过程解析 一、引言 在机器学习、人工智能以及更广泛的教育与培训领域&#xff0c;学习范式的多样性为提升智能体&#xff08;AI模型、学生或企业员工&#xff09;的能力提供了丰富的路径。弱激励学习作…

【最简单最直观的排序 —— 插入排序算法】

【最简单最直观的排序 —— 插入排序算法】 插入排序是一种简单直观的排序算法。其基本思想是把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中&#xff0c;直到所有的记录插入完为止&#xff0c;得到一个新的有序序列。 插入排序的核心就是多趟选择插…

python模块之getopt

getopt.getopt(args, shortopts, longopts[]) 解析命令行选项及参数列表。 args&#xff1a;要解析的参数列表&#xff0c;但不包括当前执行的python脚本名称&#xff0c;一般等同于sys.argv[1:]。 shortopts&#xff1a;要识别的短选项字符串&#xff0c;如果后接:表示需要…

C++入门day4-面向对象编程(下)

前言&#xff1a;C入门day3-面向对象编程&#xff08;中&#xff09;-CSDN博客 初识&#xff1a;继承特性 继承的基础语法 class A{ public:int a; }; class B:public A { public:int b; }; B类通过继承A类后&#xff0c;内部会继承一个int变量 a&#xff1a;从下图我们可以…