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

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_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.kler.cn/news/325223.html

相关文章:

  • centos7 semanage 离线安装 SELinux
  • Vue3 + TS 实现同一项目同一链接,pc端打开是web应用,手机打开是H5应用
  • Solidity语言:重点学习Solidity编程语言,这是EVM上最常用的智能合约语言。
  • 关于大模型的10个思考
  • 828华为云征文 | 云服务器Flexus X实例:向量数据库 pgvector 部署,实现向量检索
  • Stable Diffusion零基础学习
  • 基于SpringBoot+Vue+MySQL的体育商城系统
  • Linux,C高级——day4
  • 【AI写作】解释 RESTful API,以及如何使用它构建 web 应用程序。
  • 基于nodejs+vue的水产品销售管理系统
  • 大厂面试真题:G1比CMS好在哪?一定好吗
  • CSR、SSR、SSG
  • 【STM32开发环境搭建】-3-STM32CubeMX Project Manager配置-自动生成一个Keil(MDK-ARM) 5的工程
  • 6.数据结构与算法-线性表的链式表示和实现-单链表
  • wireshark使用要点
  • 【STM32】江科大STM32笔记汇总(已完结)
  • Google BigTable架构详解
  • 无人驾驶车联网5G车载路由器应用
  • C++ 创建型设计模式
  • 怎么获取一个文件夹下的所有文件名?
  • MATLAB读取TIF文件,并可视化
  • 基于SpringBoot+Vue+MySQL的美食点餐管理系统
  • 项目集成SpringSecurity框架
  • Python项目Flask框架整合Redis
  • 揭秘移动硬盘RAW:原因、恢复策略与预防措施
  • 【TS】TypeScript内置条件类型-ReturnType
  • Java五子棋
  • 召回11 地理位置召回、作者召回、缓存召回
  • Oracle 表空间时间点恢复
  • 【自动化测试】Appium Server如何安装和Appium Server安装困难的原因和解决方法以及常见的一些安装失败的错误和解决方法