当前位置: 首页 > 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/a/325223.html

相关文章:

  • VScode使用Batch Runner插件在终端运行bat文件
  • PyTorch——从入门到精通:PyTorch基础知识(张量)【PyTorch系统学习】
  • 号卡分销系统,号卡系统,物联网卡系统源码安装教程
  • 《Java核心技术 卷I》用户界面中首选项API
  • 【Linux内核剖析】深入分析inet_init的处理机制
  • C++ 中的string类
  • 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++ 创建型设计模式
  • 怎么获取一个文件夹下的所有文件名?