QTTCP客户端服务端通信

news2024/11/25 1:06:37

目录

网络模块介绍

TCP介绍

 TCP 服务端应用实例

 TCP 客户端应用实例

运行结果:

网络模块介绍

Qt 网络模块为我们提供了编写 TCP / IP 客户端和服务器的类。它提供了较低级别的类,例
如代表低级网络概念的 QTcpSocket QTcpServer QUdpSocket ,以及诸如 QNetworkRequest
QNetworkReply QNetworkAccessManager 之类的高级类来执行使用通用协议的网络操作。
还提供了诸如 QNetworkConfiguration QNetworkConfigurationManager QNetworkSession 等类,
实现承载管理。
想要在程序中使用 Qt 网络模块,我们需要在 pro 项目配置文件里增加下面的一条语句。
QT += network

TCP介绍

TCP 协议( Transmission Control Protocol )全称是传输控制协议是一种 面向连接的、可靠的、
基于字节流 的传输层通信协议。
TCP 通信必 须先建立 TCP 连接 ,通信端分为客户端和服务端。服务端通过监听某个端口
来监听是否有客户端连接到来,如果有连接到来,则建立新的 socket 连接;客户端通过 ip
port 连接服务端,当成功建立连接之后,就可进行数据的收发了。需要注意的是,在 Qt 中,
Qt socket 当成输入输出流来对待的,数据的收发是通过 read() write() 来进行的,需要与我
们常见的 send() recv() 进行区分。
TCP 客户端与服务端通信示意图如下。

 TCP 服务端应用实例

本例大体流程
首先获取本地 IP 地址。创建一个 tcpSocket 套接字,一个 tcpServer 服务端。点击监听即监听本
地的主机 IP 地址和端口,同时等待服务端的连接。此程序需要结合客户端一起使用,可实现多个客户端的传播。
widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QTcpServer>
#include <QTcpSocket>
#include <QNetworkInterface>
#include <QDebug>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private:
    Ui::Widget *ui;
    /* tcp 服务器 */
    QTcpServer *tcpServer;
    /* 通信套接字 */
    QTcpSocket *tcpSocket;
    /* 存储本地的 ip 列表地址 */
    QList<QHostAddress> IPlist;
    /* 获取本地的所有 ip */
    void getLocalHostIP();

private slots:
    /* 客户端连接处理槽函数 */
    void clientConnected();
    /* 开始监听槽函数 */
    void startListen();
    /* 停止监听槽函数 */
    void stopListen();
    /* 清除文本框时的内容 */
    void clearTextBrowser();
    /* 接收到消息 */
    void receiveMessages();
    /* 发送消息 */
    void sendMessages();
    /* 连接状态改变槽函数 */
    void socketStateChange(QAbstractSocket::SocketState);

};
#endif // WIDGET_H

widget.cpp

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    tcpServer = new QTcpServer(this);
    tcpSocket = new QTcpSocket(this);
    ui->spinBox->setRange(10000, 99999);
    ui->pushButton_5->setEnabled(false);
    getLocalHostIP();
    /* 信号槽连接 */
    connect(ui->pushButton, SIGNAL(clicked()),this, SLOT(startListen()));
    connect(ui->pushButton_5, SIGNAL(clicked()),this, SLOT(stopListen()));
    connect(ui->pushButton_6, SIGNAL(clicked()),this, SLOT(clearTextBrowser()));
    connect(ui->pushButton_4, SIGNAL(clicked()),this, SLOT(sendMessages()));
    connect(tcpServer, SIGNAL(newConnection()),this, SLOT(clientConnected()));
}

Widget::~Widget()
{
    delete ui;
}
/* 新的客户端连接 */
void Widget::clientConnected()
{
    /* 获取客户端的套接字 */
    tcpSocket = tcpServer->nextPendingConnection();
    /* 客户端的 ip 信息 */
    QString ip = tcpSocket->peerAddress().toString();
    /* 客户端的端口信息 */
    quint16 port = tcpSocket->peerPort();
    /* 在文本浏览框里显示出客户端的连接信息 */
    ui->textBrowser->append("客户端已连接");
    ui->textBrowser->append("客户端 ip 地址:" + ip);
    ui->textBrowser->append("客户端端口:" + QString::number(port));
    
    connect(tcpSocket, SIGNAL(readyRead()),this, SLOT(receiveMessages()));
    connect(tcpSocket,SIGNAL(stateChanged(QAbstractSocket::SocketState)),this,
            SLOT(socketStateChange(QAbstractSocket::SocketState)));
}
void Widget::getLocalHostIP()
{
    QList<QNetworkInterface> list= QNetworkInterface::allInterfaces();
 
    /* 遍历 list */
    foreach (QNetworkInterface interface, list) {
 
        /* QNetworkAddressEntry 类存储 IP 地址子网掩码和广播地址 */
        QList<QNetworkAddressEntry> entryList= interface.addressEntries();
   
        /* 遍历 entryList */
        foreach (QNetworkAddressEntry entry, entryList) {
            /* 过滤 IPv6 地址,只留下 IPv4 */
            if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol) {
            
                ui->comboBox->addItem(entry.ip().toString());
                /* 添加到 IP 列表中 */
                IPlist<<entry.ip();
             }
        }
     }
}
/* 开始监听槽函数 */
void Widget::startListen()
{
    /* 需要判断当前主机是否有IP项 */
    if (ui->comboBox->currentIndex() != -1) {
        qDebug()<<"start listen"<<endl;
        tcpServer->listen(IPlist[ui->comboBox->currentIndex()],ui->spinBox->value());

        /* 设置按钮与下拉列表框的状态 */
        ui->pushButton->setEnabled(false);
        ui->pushButton_5->setEnabled(true);
        ui->comboBox->setEnabled(false);
        ui->spinBox->setEnabled(false);

        /* 在文本浏览框里显示出服务端 */
        ui->textBrowser->append("服务器IP地址:"+ ui->comboBox->currentText());
        ui->textBrowser->append("正在监听端口:"+ ui->spinBox->text());
    }
}
/* 停止监听槽函数 */
void Widget::stopListen()
{
    qDebug()<<"stop listen"<<endl;
    /* 停止监听 */
    tcpServer->close();

    /* 如果是连接上了也应该断开,如果不断开客户端还能继续发送信息,
     * 因为socket未断开,还在监听上一次端口 */
    if (tcpSocket->state() == tcpSocket->ConnectedState)
        tcpSocket->disconnectFromHost();

    /* 设置按钮与下拉列表框的状态  */
    ui->pushButton_5->setEnabled(false);
    ui->pushButton->setEnabled(true);
    ui->comboBox->setEnabled(true);
    ui->spinBox->setEnabled(true);

    /* 将停止监听的信息添加到文本浏览框中 */
    ui->textBrowser->append("已停止监听端口:" + ui->spinBox->text());
}
/* 清除文本框时的内容 */
void Widget::clearTextBrowser()
{
    /* 清除文本浏览器的内容 */
    ui->textBrowser->clear();
}
 /* 接收到消息 */
void Widget::receiveMessages()
{
    /* 读取接收到的消息 */
    QString messages = "客户端:" + tcpSocket->readAll();
    ui->textBrowser->append(messages);
}
/* 发送消息 */
void Widget::sendMessages()
{
    QList <QTcpSocket *> socketList=tcpServer->findChildren<QTcpSocket *>();
    qDebug()<<"tcpSocket 数量: "<<socketList.count()<<endl;

    if(socketList.count()== 0){
        ui->textBrowser->append("请先与客户端连接!");
        return;
    }
    foreach(QTcpSocket *tmpTcpSocket,socketList){
        tmpTcpSocket->write(ui->lineEdit->text().toUtf8());
    }
    ui->textBrowser->append("服务端 "+ui->lineEdit->text());
}
/* 连接状态改变槽函数 */
void Widget::socketStateChange(QAbstractSocket::SocketState state)
{
    switch (state) {
    case QAbstractSocket::UnconnectedState:
        ui->textBrowser->append("scoket状态:客户端断开连接");
        tcpSocket->deleteLater();
        break;
    case QAbstractSocket::ConnectedState:
        ui->textBrowser->append("scoket状态:客户端已连接");
        break;
    case QAbstractSocket::ConnectingState:
        ui->textBrowser->append("scoket状态:ConnectingState");
        break;
    case QAbstractSocket::HostLookupState:
        ui->textBrowser->append("scoket状态:HostLookupState");
        break;
    case QAbstractSocket::ClosingState:
        ui->textBrowser->append("scoket状态:ClosingState");
        break;
    case QAbstractSocket::ListeningState:
        ui->textBrowser->append("scoket状态:ListeningState");
        break;
    case QAbstractSocket::BoundState:
        ui->textBrowser->append("scoket状态:BoundState");
        break;
    default:
        break;
    }
}

 TCP 客户端应用实例

本例大体流程:
首先获取本地 IP 地址。创建一个 tcpSocket 套接字,然后用 tcpSocket 套接字使用 connectToHost 函数连接服务端的主机 IP 地址和端口,即可相互通信。

 widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QTcpServer>
#include <QTcpSocket>
#include <QNetworkInterface>
#include <QDebug>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private:
    Ui::Widget *ui;
    /* 通信套接字 */
    QTcpSocket *tcpSocket;

    /* 存储本地的ip列表地址 */
    QList<QHostAddress> IPlist;

    /* 获取本地的所有ip */
    void getLocalHostIP();
private slots:
    /* 连接 */
    void toConnect();

    /* 断开连接 */
    void toDisConnect();

    /* 已连接 */
    void connected();

    /* 已断开连接 */
    void disconnected();

    /* 清除文本框时的内容 */
    void clearTextBrowser();

    /* 接收到消息 */
    void receiveMessages();

    /* 发送消息 */
    void sendMessages();

    /* 连接状态改变槽函数 */
    void socketStateChange(QAbstractSocket::SocketState);
};
#endif // WIDGET_H

widget.cpp

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    /* tcp套接字 */
    tcpSocket = new QTcpSocket(this);
    /* 设置端口号的范围,注意不要与主机的已使用的端口号冲突 */
    ui->spinBox->setRange(10000, 99999);
    /* 设置断开连接状态不可用 */
    ui->pushButton_2->setEnabled(false);

    /* 获取本地 ip */
    getLocalHostIP();
    /* 信号槽连接 */
    connect(ui->pushButton, SIGNAL(clicked()),this, SLOT(toConnect()));
    connect(ui->pushButton_2, SIGNAL(clicked()),this, SLOT(toDisConnect()));
    connect(ui->pushButton_3, SIGNAL(clicked()),this, SLOT(clearTextBrowser()));
    connect(ui->pushButton_4, SIGNAL(clicked()),this, SLOT(sendMessages()));
    connect(tcpSocket, SIGNAL(connected()),this, SLOT(connected()));
    connect(tcpSocket, SIGNAL(disconnected()),this, SLOT(disconnected()));
    connect(tcpSocket, SIGNAL(readyRead()),this, SLOT(receiveMessages()));
    connect(tcpSocket,SIGNAL(stateChanged(QAbstractSocket::SocketState)),this,
           SLOT(socketStateChange(QAbstractSocket::SocketState)));
}

Widget::~Widget()
{
    delete ui;
}
void Widget::toConnect()
{
    /* 如果连接状态还没有连接 */
    if (tcpSocket->state() != tcpSocket->ConnectedState) {
        /* 指定IP地址和端口连接 */
        tcpSocket->connectToHost(IPlist[ui->comboBox->currentIndex()],ui->spinBox->value());
    }
}

void Widget::toDisConnect()
{
    /* 断开连接 */
    tcpSocket->disconnectFromHost();

    /* 关闭socket*/
    tcpSocket->close();
}

void Widget::connected()
{
    /* 显示已经连接 */
    ui->textBrowser->append("已经连上服务端");

    /* 设置按钮与下拉列表框的状态 */
    ui->pushButton->setEnabled(false);
    ui->pushButton_2->setEnabled(true);
    ui->comboBox->setEnabled(false);
    ui->spinBox->setEnabled(false);
}

void Widget::disconnected()
{
    /* 显示已经断开连接 */
    ui->textBrowser->append("已经断开服务端");

    /* 设置按钮与下拉列表框的状态  */
    ui->pushButton_2->setEnabled(false);
    ui->pushButton->setEnabled(true);
    ui->comboBox->setEnabled(true);
    ui->spinBox->setEnabled(true);
}

/* 获取本地IP */
void Widget::getLocalHostIP()
{
    // /* 获取主机的名称 */
    // QString hostName = QHostInfo::localHostName();

    // /* 主机的信息 */
    // QHostInfo hostInfo = QHostInfo::fromName(hostName);

    // /* ip列表,addresses返回ip地址列表,注意主机应能从路由器获取到
    // * IP,否则可能返回空的列表(ubuntu用此方法只能获取到环回IP) */
    // IPlist = hostInfo.addresses();
    // qDebug()<<IPlist<<endl;

    // /* 遍历IPlist */
    // foreach (QHostAddress ip, IPlist) {
    //      if (ip.protocol() == QAbstractSocket::IPv4Protocol)
    //          comboBox->addItem(ip.toString());
    //    }

    /* 获取所有的网络接口,
     * QNetworkInterface类提供主机的IP地址和网络接口的列表 */
    QList<QNetworkInterface> list = QNetworkInterface::allInterfaces();

    /* 遍历list */
    foreach (QNetworkInterface interface, list) {

        /* QNetworkAddressEntry类存储IP地址子网掩码和广播地址 */
        QList<QNetworkAddressEntry> entryList = interface.addressEntries();

        /* 遍历entryList */
        foreach (QNetworkAddressEntry entry, entryList) {
            /* 过滤IPv6地址,只留下IPv4 */
            if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol) {
                ui->comboBox->addItem(entry.ip().toString());
                /* 添加到IP列表中 */
                IPlist<<entry.ip();
            }
        }
    }
}

/* 清除文本浏览框里的内容 */
void Widget::clearTextBrowser()
{
    /* 清除文本浏览器的内容 */
    ui->textBrowser->clear();
}

/* 客户端接收消息 */
void Widget::receiveMessages()
{
    /* 读取接收到的消息 */
    QString messages = tcpSocket->readAll();
    ui->textBrowser->append("服务端:" + messages);
}

/* 客户端发送消息 */
void Widget::sendMessages()
{
    if(NULL == tcpSocket)
        return;

    if(tcpSocket->state() == tcpSocket->ConnectedState) {
        /* 客户端显示发送的消息 */
        ui->textBrowser->append("客户端:" + ui->lineEdit->text());

        /* 发送消息 */
        tcpSocket->write(ui->lineEdit->text().toUtf8().data());
    }
}

/* 客户端状态改变 */
void Widget::socketStateChange(QAbstractSocket::SocketState state)
{
    switch (state) {
    case QAbstractSocket::UnconnectedState:
        ui->textBrowser->append("scoket状态:与服务端未连接");
        tcpSocket->deleteLater();
        break;
    case QAbstractSocket::ConnectedState:
        ui->textBrowser->append("scoket状态:与服务端已连接");
        break;
    case QAbstractSocket::ConnectingState:
        ui->textBrowser->append("scoket状态:ConnectingState");
        break;
    case QAbstractSocket::HostLookupState:
        ui->textBrowser->append("scoket状态:HostLookupState");
        break;
    case QAbstractSocket::ClosingState:
        ui->textBrowser->append("scoket状态:ClosingState");
        break;
    case QAbstractSocket::ListeningState:
        ui->textBrowser->append("scoket状态:ListeningState");
        break;
    case QAbstractSocket::BoundState:
        ui->textBrowser->append("scoket状态:BoundState");
        break;
    default:
        break;
    }
}

运行结果:

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/721351.html

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

相关文章

智慧旅游卡APP小程序开发方案

旅游业的蓬勃发展&#xff0c;旅游卡作为一种便捷的旅游支付方式越来越受到人们的喜爱。智慧旅游卡APP小程序开发方案是一种利用微信小程序实现的在线购买旅游卡、查询旅游信息、预约旅游服务等功能的旅游卡APP。下面将详细介绍智慧旅游卡APP小程序的开发方案。 一、智慧旅…

【CDC 2023 Cooperative Aerial Robots Inspection Challenge】

CDC 2023 Cooperative Aerial Robots Inspection Challenge 合作空中机器人检查挑战赛 Install the CARIC packagesRun the flight testThe benchmark designThe UAV fleet CDC 2023 Cooperative Aerial Robots Inspection Challenge 网址 Install the CARIC packages Once t…

旅游卡APP开发解决方案

旅游业的不断发展&#xff0c;旅游卡成为了人们出行时必不可少的一项工具。旅游卡APP开发解决方案旨在为用户提供更加便捷、高效的旅游卡购买和使用体验。下面将详细介绍旅游卡APP开发解决方案的几个方面。 一、旅游卡APP开发解决方案的技术方面 旅游卡APP开发解决方案…

Python pyecharts实时画图自定义可视化经纬度热力图

目录 背景基于pyecharts内置经纬度的热力图基于自定义经纬度的热力图pyecharts库缺点不同地图坐标系区别 WGS-84 - 世界大地测量系统GCJ-02 - 国测局坐标BD-09 - 百度坐标系 背景 在业务数据统计分析中基本都会涉及到各省区的分析&#xff0c;数据可视化是数据分析的一把利器…

事件机制(事件流、事件委托、事件类型)

HTML DOM 允许 JavaScript 对 HTML 事件作出反应。JavaScript 能够在事件发生时执行&#xff0c;比如当用户点击某个 HTML 元素时。 JavaScript与HTML之间的交互是通过事件实现的。事件就是文档或浏览器窗口中发生的一些特定的交互瞬间。 目录 事件是由三部分组成 执行事件的步…

儿童硅胶勺子LFGB标准检测是什么?

食品级硅胶制品的德国食品接触材料测试LFGB标准检测哪些项目&#xff1f; Sensory test感官测试一味道和气味对于与食品接触的整体产品 extractable components in ethanol(Ethanol as compulsory simulant)(must perform)10%酒精萃取法(必做) Extractable component in deioni…

官网能为企业营销增长贡献哪些力量?

官网是门面&#xff0c;同时也具备营销价值&#xff0c;可以带来流量转化成销售线索&#xff0c;影响公司营收&#xff0c;B2B业务是复杂的&#xff0c;客户在选择你之前会长期的研究你的官网&#xff0c;了解你的产品及服务、成功案例、甚至是品牌的创始团队信息&#xff0c;来…

SpringCloud学习1

SpringCloud是分布式微服务架构的 一些列解决方案的集合&#xff0c;SpringBoot是一门单独的技术 ​​​​​​

小心 MybatisPlus 的一个坑与面试题

昨天测试说有个 xx 功能用不了&#xff0c;扔给我一个截图&#xff0c;说有报错&#xff1a; 报错信息就是&#xff1a;Transaction rolled back because it has been marked as rollback-only&#xff0c;很好理解&#xff1a;事务被回滚了&#xff0c;因为它已经被标记了只能…

HttpRunner自动化之请求中带有 headers 的接口和发送POST请求

headers 可通过headers 添加头部信息&#xff0c;如下图 # 发送请求头headers的接口 - config:name: 百度接口用例base_url: https://www.baidu.com- test:name: 发送百度接口的头部信息request:url: /smethod: GETheaders:Accept: text/html,application/xhtmlxml,applicati…

LNMT(linux下nignx+mysql+tomcat(中间件)应用)部署应用、及各服务介绍、部署开源站点jpress

目录 一、环境准备 二、tomcat1和tomcat2服务器&#xff0c;安装配置tomcat 1.tomcat服务器介绍 2.JDK软件介绍 3.查看JDK是否安装 4.tomcat1和tomcat2服务器&#xff0c;安装JDK1.8.0_191&#xff08;JDK必须和nginx版本相适应&#xff0c;不然一直报错&#xff09; 5.安…

【LeetCode】HOT 100(21)

题单介绍&#xff1a; 精选 100 道力扣&#xff08;LeetCode&#xff09;上最热门的题目&#xff0c;适合初识算法与数据结构的新手和想要在短时间内高效提升的人&#xff0c;熟练掌握这 100 道题&#xff0c;你就已经具备了在代码世界通行的基本能力。 目录 题单介绍&#…

源启数据资产管理平台助力金融机构加速数据资产化过程

自2000年左右&#xff0c;金融行业开始做数据管理。从数据仓库到数据治理、数据应用&#xff0c;再到后来的大数据&#xff0c;以及今天的数据管理。我们把这个时期总结成数据资产化时代&#xff0c;或者叫国产化时代。 为什么有两个名字&#xff1f;数据资产化时代是因为国家…

关于PID闭环控制中上位机与下位机通讯代码的解析分享(一)

下位机接收数据代码&#xff08;以STM32单片机为例&#xff09;与上位机发送数据C#代码分享 1、下位机代码&#xff1a; /*** brief 接收的数据处理* param void* return -1&#xff1a;没有找到一个正确的命令.*/ int8_t receiving_process(void) {uint8_t frame_data[…

虚幻引擎程序化资源生成框架PCG 之 常用撒点方法小结

PCG真好玩&#xff0c;门槛很低&#xff0c;天花板很高 文章目录 前言1. 基本撒点1.1 Landscape上撒点1.2 使用射线检测在地表面撒点1.3 使用曲线撒点1.3.1 沿曲线撒点1.3.2 在闭合曲线内部撒点 1.4 在StaticMesh表面撒点 2. 进阶撒点2.1 在闭合曲线内部放射状撒点2.2 在Mesh表…

MedCalc v22.009 医学ROC曲线统计分析软详细图文教程

简介 MedCalc是一款医学 ROC 曲线统计软件&#xff0c;用于ROC曲线分析的参考软件&#xff0c;医学工作者设计的医学计算器&#xff0c;功能齐全。它可以帮助医生快速作出普通的医学计算&#xff0c;从而对症下药。提供超过76种常用的规则和方法&#xff0c;包括&#xff1a;病…

《消失的她》豆瓣短评数据分析

《消失的她》豆瓣短评数据分析 文章目录 《消失的她》豆瓣短评数据分析一、前言二、数据加载和预处理三、探索性数据分析1、查看评论的评价分布2、查看评论点赞数的分布3、查看评论的地理分布 四、情感分析 一、前言 最近爆火的电影《消失的她》你们有没有去看过呢&#xff1f…

开源站点(jpress)部署

第三阶段基础 时 间&#xff1a;2023年7月5日 参加人&#xff1a;全班人员 内 容&#xff1a; 开源站点部署&#xff08;jpress&#xff09; 服务器设置&#xff1b;单台服务器&#xff0c;安装tomcat和mariadb 环境配置&#xff1a; 1、关闭防火墙 systemctl stop fir…

【大数据之Hive】二十、Hive之调优相关配置及Explain查看执行计划

1 Yarn资源配置 需要调整Yarn的参数与CPU、内存等资源有关 &#xff08;1&#xff09;yarn.nodemanager.resource.memory-mb   设置一个NodeManager节点分配给容器Container使用的内存&#xff0c;取决于NodeManager所在节点的总内存容量和该节点运行的其他服务的数量&#x…

搭建高性能数据库集群之二:MySQL读写分离(基于mycat2-1.22)

一、概述 读写分离是常见的一种数据库架构&#xff0c;一般是由 1 主多从构成&#xff0c;特殊场景下也会存在多主多从的架构。 无论哪一种架构&#xff0c;对于应用程序来说都是多个数据源&#xff0c;增加了代码的复杂性。如果配合 mycat&#xff0c;则可以实现屏蔽数据库复…