Qt TCP网络编程基本教程

news2025/2/23 23:21:28

首先介绍一下TCP:(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。相比而言UDP,就是开放式、无连接、不可靠的传输层通信协议。 下面,我一次进行客户端和服务器端的QT实现。

我的开发环境是:QT Creator 5.7。

先看下效果图:

一:客户端编程

QT提供了QTcpSocket类,可以直接实例化一个客户端,可在help中索引如下:

The QTcpSocket class provides a TCP socket. More...
Header      #include <QTcpSocket>
qmake       QT += network
Inherits:   QAbstractSocket
Inherited By:   QSslSocket

从这里,我们可以看到,必须要在.pro文件中添加QT += network才可以进行网络编程,否则是访问不到<QTcpSocket>头文件的。 客户端读写相对简单,我们看一下代码头文件:

本文福利,费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QT嵌入式开发,Quick模块等等)↓↓↓↓↓↓见下面↓↓文章底部点击费领取↓↓

#ifndef MYTCPCLIENT_H
#define MYTCPCLIENT_H

#include <QMainWindow>
#include <QTcpSocket>
#include <QHostAddress>
#include <QMessageBox>
namespace Ui {
class MyTcpClient;
}

class MyTcpClient : public QMainWindow
{
    Q_OBJECT

public:
    explicit MyTcpClient(QWidget *parent = 0);
    ~MyTcpClient();

private:
    Ui::MyTcpClient *ui;
    QTcpSocket *tcpClient;

private slots:
    //客户端槽函数
    void ReadData();
    void ReadError(QAbstractSocket::SocketError);

    void on_btnConnect_clicked();
    void on_btnSend_clicked();
    void on_pushButton_clicked();
};

#endif // MYTCPCLIENT_H

我们在窗口类中,定义了一个私有成员QTcpSoket *tcpClient。

1) 初始化QTcpSocket
在构造函数中,我们需要先对其进行实例化,并连接信号与槽函数:

CSDN QT大纲:Qt开发必备技术栈学习路线和资料

//初始化TCP客户端
    tcpClient = new QTcpSocket(this);   //实例化tcpClient
    tcpClient->abort();                 //取消原有连接
    connect(tcpClient, SIGNAL(readyRead()), this, SLOT(ReadData()));
    connect(tcpClient, SIGNAL(error(QAbstractSocket::SocketError)), \
            this, SLOT(ReadError(QAbstractSocket::SocketError)));

2)建立连接 和 断开连接

tcpClient->connectToHost(ui->edtIP->text(), ui->edtPort->text().toInt());
    if (tcpClient->waitForConnected(1000))  // 连接成功则进入if{}
    {
        ui->btnConnect->setText("断开");
        ui->btnSend->setEnabled(true);
    }

a)建立TCP连接的函数:void connectToHost(const QHostAddress &address, quint16 port, OpenMode openMode = ReadWrite)是从QAbstractSocket继承下来的public function,同时它又是一个virtual function。作用为:Attempts to make a connection to address on port port。

b)等待TCP连接成功的函数:bool waitForConnected(int msecs = 30000)同样是从QAbstractSocket继承下来的public function,同时它又是一个virtual function。作用为:Waits until the socket is connected, up to msecs milliseconds. If the connection has been established, this function returns true; otherwise it returns false. In the case where it returns false, you can call error() to determine the cause of the error.

上述代码中,edtIP, edtPort是ui上的两个lineEditor,用来填写服务器IP和端口号。btnConnect是“连接/断开”复用按钮,btnSend是向服务器发送数据的按钮,只有连接建立之后,才将其setEnabled。

tcpClient->disconnectFromHost();
  if (tcpClient->state() == QAbstractSocket::UnconnectedState \
       || tcpClient->waitForDisconnected(1000))  //已断开连接则进入if{}
  {
            ui->btnConnect->setText("连接");
            ui->btnSend->setEnabled(false);
  }

a)断开TCP连接的函数:void disconnectFromHost()是从QAbstractSocket继承的public function,同时它又是一个virtual function。作用为:Attempts to close the socket. If there is pending data waiting to be written, QAbstractSocket will enter ClosingState and wait until all data has been written. Eventually, it will enter UnconnectedState and emit the disconnected() signal.

b)等待TCP断开连接函数:bool waitForDisconnected(int msecs = 30000),同样是从QAbstractSocket继承下来的public function,同时它又是一个virtual function。作用为:Waits until the socket has disconnected, up to msecs milliseconds. If the connection has been disconnected, this function returns true; otherwise it returns false. In the case where it returns false, you can call error() to determine the cause of the error.

3)读取服务器发送过来的数据
readyRead()是QTcpSocket从父类QIODevice中继承下来的信号:This signal is emitted once every time new data is available for reading from the device’s current read channel。
readyRead()对应的槽函数为:

void MyTcpClient::ReadData()
{
    QByteArray buffer = tcpClient->readAll();
    if(!buffer.isEmpty())
    {
        ui->edtRecv->append(buffer);
    }
}

readAll()是QTcpSocket从QIODevice继承的public function,直接调用就可以读取从服务器发过来的数据了。我这里面把数据显示在textEditor控件上(ui>edtRecv)。由此完成了读操作。

error(QAbstractSocket::SocketError)是QTcpSocket从QAbstractSocket继承的signal, This signal is emitted after an error occurred. The socketError parameter describes the type of error that occurred.连接到的槽函数定义为:

void MyTcpClient::ReadError(QAbstractSocket::SocketError)
{
    tcpClient->disconnectFromHost();
    ui->btnConnect->setText(tr("连接"));
    QMessageBox msgBox;
    msgBox.setText(tr("failed to connect server because %1").arg(tcpClient->errorString()));
  8 }

这段函数的作用是:当错误发生时,首先断开TCP连接,再用QMessageBox提示出errorString,即错误原因。

4)向服务器发送数据

QString data = ui->edtSend->toPlainText();
    if(data != "")
    {
        tcpClient->write(data.toLatin1()); //qt5去除了.toAscii()
    }

定义一个QString变量,从textEditor(edtSend)中获取带发送数据,write()是QTcpSocket从QIODevice继承的public function,直接调用就可以向服务器发送数据了。这里需要注意的是:toAscii()到qt5就没有了,这里要写成toLatin1()。

至此,通过4步,我们就完成了TCP Client的程序开发。

二:服务器端编程

服务器段编程相比于客户端要繁琐一些,因为对于客户端来说,只能连接一个服务器。而对于服务器来说,它是面向多连接的,如何协调处理多客户端连接就显得尤为重要。

前言:编程过程中遇到的问题 和 解决的方法

遇到的问题:每个新加入的客户端,服务器给其分配一个SocketDescriptor后,就会emit newConnection()信号,但分配好的SocketDecriptor并没有通过newConnection()信号传递,所以用户得不到这个客户端标识SocketDescriptor。同样的,每当服务器收到新的消息时,客户端会emit readReady()信号,然而readReady()信号也没有传递SocketDescriptor, 这样的话,服务器端即使接收到消息,也不知道这个消息是从哪个客户端发出的。

解决的方法:

1. 通过重写[virtual protected] void QTcpServer::incomingConnection(qintptr socketDescriptor),获取soketDescriptor。自定义TcpClient类继承QTcpSocket,并将获得的soketDescriptor作为类成员。 这个方法的优点是:可以获取到soketDescriptor,灵活性高。缺点是:需要重写函数、自定义类。

2. 在newConnection()信号对应的槽函数中,通过QTcpSocket *QTcpServer::nextPendingConnection()函数获取 新连接的客户端:Returns the next pending connection as a connected QTcpSocket object. 虽然仍然得不到soketDescriptor,但可以通过QTcpSocket类的peerAddress()和peerPort()成员函数获取客户端的IP和端口号,同样是唯一标识。 优点:无需重写函数和自定义类,代码简洁。缺点:无法获得SocketDecriptor,灵活性差。

本文介绍第二种方法:

QT提供了QTcpServer类,可以直接实例化一个客户端,可在help中索引如下:

1 The QTcpServer class provides a TCP-based server. More...
2 Header:     #include <QTcpServer> 
3 qmake:      QT += network
4 Inherits:       QObject

从这里,我们可以看到,必须要在.pro文件中添加QT += network才可以进行网络编程,否则是访问不到<QTcpServer>头文件的。

我们看一下代码头文件:

 1 #ifndef MYTCPSERVER_H
 2 #define MYTCPSERVER_H
 3 
 4 #include <QMainWindow>
 5 #include <QTcpServer>
 6 #include <QTcpSocket>
 7 #include <QNetworkInterface>
 8 #include <QMessageBox>
 9 namespace Ui {
10 class MyTcpServer;
11 }
12 
13 class MyTcpServer : public QMainWindow
14 {
15     Q_OBJECT
16 
17 public:
18     explicit MyTcpServer(QWidget *parent = 0);
19     ~MyTcpServer();
20 
21 private:
22     Ui::MyTcpServer *ui;
23     QTcpServer *tcpServer;
24     QList<QTcpSocket*> tcpClient;
25     QTcpSocket *currentClient;
26 
27 private slots:
28     void NewConnectionSlot();
29     void disconnectedSlot();
30     void ReadData();
31 
32     void on_btnConnect_clicked();
33     void on_btnSend_clicked();
34     void on_btnClear_clicked();
35 };
36 
37 #endif // MYTCPSERVER_H

值得注意的是,在服务端编写时,需要同时定义服务器端变量QTcpServer *tcpServer和客户端变量 QList<QTcpSocket*> tcpClient。tcpSocket QList存储了连接到服务器的所有客户端。因为QTcpServer并不是QIODevice的子类,所以在QTcpServer中并没有任何有关读写操作的成员函数,读写数据的操作全权交由QTcpSocket处理。

1)初始化QTcpServer

1     tcpServer = new QTcpServer(this);
2     ui->edtIP->setText(QNetworkInterface().allAddresses().at(1).toString());   //获取本地IP
3     ui->btnConnect->setEnabled(true);
4     ui->btnSend->setEnabled(false);
5 
6     connect(tcpServer, SIGNAL(newConnection()), this, SLOT(NewConnectionSlot()));

通过QNetworkInterface().allAddresses().at(1)获取到本机IP显示在lineEditor上(edtIP)。

介绍如下: [static] QList<QHostAddress> QNetworkInterface::allAddresses() This convenience function returns all IP addresses found on the host machine. It is equivalent to calling addressEntries() on all the objects returned by allInterfaces() to obtain lists of QHostAddress objects then calling QHostAddress::ip() on each of these.: 每当新的客户端连接到服务器时,newConncetion()信号触发,NewConnectionSlot()是用户的槽函数,定义如下:

1 void MyTcpServer::NewConnectionSlot()
2 {
3     currentClient = tcpServer->nextPendingConnection();
4     tcpClient.append(currentClient);
5     ui->cbxConnection->addItem(tr("%1:%2").arg(currentClient->peerAddress().toString().split("::ffff:")[1])\
6                                           .arg(currentClient->peerPort()));
7     connect(currentClient, SIGNAL(readyRead()), this, SLOT(ReadData()));
8     connect(currentClient, SIGNAL(disconnected()), this, SLOT(disconnectedSlot()));
9 }

通过nextPendingConnection()获得连接过来的客户端信息,取到peerAddress和peerPort后显示在comboBox(cbxConnection)上,并将客户端的readyRead()信号连接到服务器端自定义的读数据槽函数ReadData()上。将客户端的disconnected()信号连接到服务器端自定义的槽函数disconnectedSlot()上。

2)监听端口 与 取消监听

1      bool ok = tcpServer->listen(QHostAddress::Any, ui->edtPort->text().toInt());
2      if(ok)
3      {
4          ui->btnConnect->setText("断开");
5          ui->btnSend->setEnabled(true);
6      }

a)监听端口的函数:bool QTcpServer::listen(const QHostAddress &*address* = QHostAddress::Any, quint16 *port* = 0),该函数的作用为:Tells the server to listen for incoming connections on address *address* and port *port*. If port is 0, a port is chosen automatically. If address is QHostAddress::Any, the server will listen on all network interfaces. Returns true on success; otherwise returns false.

 1      for(int i=0; i<tcpClient.length(); i++)//断开所有连接
 2      {
 3          tcpClient[i]->disconnectFromHost();
 4          bool ok = tcpClient[i]->waitForDisconnected(1000);
 5          if(!ok)
 6          {
 7              // 处理异常
 8          }
 9          tcpClient.removeAt(i);  //从保存的客户端列表中取去除
10      }
11      tcpServer->close();     //不再监听端口

b)断开客户端与服务器连接的函数:disconnectFromHost()和waitForDisconnected()上文已述。断开连接之后,要将其从QList tcpClient中移除。服务器取消监听的函数:tcpServer->close()。

 1     //由于disconnected信号并未提供SocketDescriptor,所以需要遍历寻找
 2     for(int i=0; i<tcpClient.length(); i++)
 3     {
 4         if(tcpClient[i]->state() == QAbstractSocket::UnconnectedState)
 5         {
 6             // 删除存储在combox中的客户端信息
 7             ui->cbxConnection->removeItem(ui->cbxConnection->findText(tr("%1:%2")\
 8                                   .arg(tcpClient[i]->peerAddress().toString().split("::ffff:")[1])\
 9                                   .arg(tcpClient[i]->peerPort())));
10             // 删除存储在tcpClient列表中的客户端信息
11              tcpClient[i]->destroyed();
12              tcpClient.removeAt(i);
13         }
14     }

c)若某个客户端断开了其与服务器的连接,disconnected()信号被触发,但并未传递参数。所以用户需要遍历tcpClient list来查询每个tcpClient的state(),若是未连接状态(UnconnectedState),则删除combox中的该客户端,删除tcpClient列表中的该客户端,并destroy()。

3)读取客户端发送过来的数据

 1     // 客户端数据可读信号,对应的读数据槽函数
 2     void MyTcpServer::ReadData()
 3     {
 4         // 由于readyRead信号并未提供SocketDecriptor,所以需要遍历所有客户端
 5         for(int i=0; i<tcpClient.length(); i++)
 6         {
 7             QByteArray buffer = tcpClient[i]->readAll();
 8             if(buffer.isEmpty())    continue;
 9 
10             static QString IP_Port, IP_Port_Pre;
11             IP_Port = tr("[%1:%2]:").arg(tcpClient[i]->peerAddress().toString().split("::ffff:")[1])\
12                                          .arg(tcpClient[i]->peerPort());
13 
14             // 若此次消息的地址与上次不同,则需显示此次消息的客户端地址
15             if(IP_Port != IP_Port_Pre)
16                 ui->edtRecv->append(IP_Port);
17 
18             ui->edtRecv->append(buffer);
19 
20             //更新ip_port
21             IP_Port_Pre = IP_Port;
22         }
23     }

这里需要注意的是,虽然tcpClient产生了readReady()信号,但readReady()信号并没有传递任何参数,当面向多连接客户端时,tcpServer并不知道是哪一个tcpClient是数据源,所以这里遍历tcpClient列表来读取数据(略耗时,上述的解决方法1则不必如此)。 读操作由tcpClient变量处理:tcpClient[i]->readAll();

4)向客户端发送数据

1     //全部连接
2     if(ui->cbxConnection->currentIndex() == 0)
3     {
4         for(int i=0; i<tcpClient.length(); i++)
5             tcpClient[i]->write(data.toLatin1()); //qt5除去了.toAscii()
6     }

a)向当前连接的所有客户端发数据,遍历即可。

 1     //指定连接
 2     QString clientIP = ui->cbxConnection->currentText().split(":")[0];
 3     int clientPort = ui->cbxConnection->currentText().split(":")[1].toInt();
 4     for(int i=0; i<tcpClient.length(); i++)
 5     {
 6         if(tcpClient[i]->peerAddress().toString().split("::ffff:")[1]==clientIP\
 7                         && tcpClient[i]->peerPort()==clientPort)
 8         {
 9             tcpClient[i]->write(data.toLatin1());
10             return; //ip:port唯一,无需继续检索
11         }
12     }

b)在comboBox(cbxConnction)中选择指定连接发送数据:通过peerAddress和peerPort匹配客户端,并发送。写操作由tcpClient变量处理:tcpClient[i]->write()。

至此,通过4步,我们就完成了TCP Server的程序开发

本文福利,费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QT嵌入式开发,Quick模块等等)↓↓↓↓↓↓见下面↓↓文章底部点击费领取↓↓

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

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

相关文章

STM32f103 SMO滑膜观测器的FOC驱动DIY

小时候玩航模了解到无刷电机&#xff0c;又从方波控制了解到FOC控制&#xff0c;在接触了一些开源项目的时候&#xff0c;感叹做的真好&#xff0c;不论是方波的还是Foc的启动都是那么丝滑&#xff0c;软件做的虽然好&#xff0c;但在这几年电机驱动芯片和部分型号的单片机价格…

【878. 第 N 个神奇数字】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 一个正整数如果能被 a 或 b 整除&#xff0c;那么它是神奇的。 给定三个整数 n , a , b &#xff0c;返回第 n 个神奇的数字。因为答案可能很大&#xff0c;所以返回答案 对 109 7 取模 后的值。 示…

亚马逊气候友好型承诺所有认证

【亚马逊气候友好型承诺所有认证】 亚马逊与广泛的外部认证合作&#xff0c;包括政府机构、非营利组织和独立实验室&#xff0c;以帮助我们区分更具可持续性的产品。亚马逊专注于信誉良好、透明且专注于保护自然世界的认证。我们将定期评估和重新评估认证环境&#xff0c;以确保…

若依框架的暴力破解漏洞

文章目录 漏洞描述漏洞复现修复建议漏洞描述 由于图片验证码未做好前后台的统一校验,可重复利用验证码,以及无登录错误次数限制等,导致攻击者可对账号与密码进行的穷举测试,从而获取网站登录访问权限 漏洞复现 第一步 抓包获取登录数据包,默认口令Admin123 第二步 发…

win10系统下使用openvino部署yolov5模型

文章目录前言一、环境1、硬件2、软件二、YOLO模型三、新建Qt项目1、pro文件2、main.cpp四、效果五、后记前言 上一篇介绍过使用onnxruntime实现模型推理部署&#xff0c;但在我的机器上视频效果仍不理想&#xff0c;本篇介绍使用openvino完成模型推理部署。   openvino是Inte…

Windows OpenGL ES 图像透明度

目录 一.OpenGL ES 图像透明度 1.原始图片2.效果演示 二.OpenGL ES 图像透明度源码下载三.猜你喜欢 零基础 OpenGL ES 学习路线推荐 : OpenGL ES 学习目录 >> OpenGL ES 基础 零基础 OpenGL ES 学习路线推荐 : OpenGL ES 学习目录 >> OpenGL ES 特效 零基础 Open…

linux下gcc编程11-window下clion编译调试nginx+集成lua-nginx-module+安装开源x-waf

nginx模块 nginx作为项目的7层代理入口&#xff0c;对于http请求的过滤&#xff0c;如sql注入&#xff0c;xss攻击等过滤功能较弱&#xff0c;研究了下开源的一些waf&#xff0c;完全开源的https://github.com/xsec-lab/x-waf&#xff0c;利用lua来过滤请求&#xff0c;同时拥…

TuckER 论文笔记

Modeling Relation Paths for Representation Learning of Knowledge Bases- Introduction- Background- Algorithm- Experiment- Conclusion- CodeIvana Balazevic, Carl Allen, Timothy M.Hospedales - Introduction TuckERuckER是一个相对简单但功能强大的线性模型&#xf…

网络营销中 SEO 的作用

与其有时间去阅读各种SEO知识&#xff0c;不如多做一些实际的测试和练习。在百度官方发布的网页质量白皮书中&#xff0c;其实重点介绍了网页速度对SEO优化的影响&#xff0c;前面也出现了一种叫做闪电算法的算法&#xff0c;对于移动排名1.5秒内加载首屏即可打开的网页&#x…

2022亚马逊云科技re:Invent科创风尚,抢占下一个万亿赛道

新风向&#xff1a;重塑科技创投格局 面向未来增长&#xff0c;聚焦投资风向&#xff0c;演绎全新技术。11月28日至12月2日&#xff0c;2022亚马逊云科技re:Invent即将重磅来袭&#xff0c;在美国拉斯维加斯再度盛启。改变世界的全新云技术、不同领域的优选实践&#xff0c;都…

【仿真建模】第一课:AnyLogic入门基础教程 - 行人库入门讲解

文章目录一、AnyLogic介绍二、设置2.1 设置中文三、新建项目四、行人库介绍五、创建新行人六、切换3D视角七、增加墙八、行人密度图一、AnyLogic介绍 二、设置 2.1 设置中文 三、新建项目 四、行人库介绍 点击面板&#xff0c;选择第三个图标&#xff0c;就是行人库 行人库分…

react--编程式导航、antd的使用

编程式 1. 借助路由对象中的history 获取&#xff1a; this.props.history.push(/xx/xx) | this.props.history.replace(/xx/xx) 2. 传递sreach参数 this.props.history.push(/xx/xx?xxx100&xx111) 3. 传state参数 this.props.history.push(/xx,{id:1,title:…

web前端期末大作业 HTML+CSS+JavaScript仿唯品会购物商城网页设计实例 企业网站制作

常见网页设计作业题材有 个人、 美食、 公司、 学校、 旅游、 电商、 宠物、 电器、 茶叶、 家居、 酒店、 舞蹈、 动漫、 服装、 体育、 化妆品、 物流、 环保、 书籍、 婚纱、 游戏、 节日、 戒烟、 电影、 摄影、 文化、 家乡、 鲜花、 礼品、 汽车、 其他等网页设计题目, A…

ISO 5659-2塑料 烟生成 第2 部分:单室法测定烟密度试验方法

本标准适用于测定塑料燃烧时所产生烟雾的比光密度&#xff0c;并以最大比光密度为试验结果。它用于评定在规定条件下塑料的发烟性能。 ISO5659-2 建筑材料阻燃防火测试-标准名称&#xff1a; ISO 5659-2: 塑料&#xff0d;生烟性测定&#xff0d;第2部分&#xff1a;单烟箱光…

echarts看板效果图:流光折线图、3d柱状图、3d饼图

前言 现在展厅的大看板是越花里胡哨越好,不过真的挺难做的。好在可以百度找到一些大神的作品进行参考。 下面的内容都是基于echarts 5.3.3 和 vue3 。另外demo都是参考别人的案例。 流光折线图 效果图 代码 <template><div id="demo"></div&g…

从零开始学JAVA(01):配置Java运行环境、实现HelloWorld

一、下载安装JDK 1、下载安装 Oracle | Cloud Applications and Cloud Platform 2、验证&#xff08;控制太输入java、java-version、javac&#xff09; 二、 实现HelloWorld 1、下载 Sublime Text Sublime Text - Text Editing, Done Right 2、编写HelloWorld.java文件 …

数据恢复方法有哪些?如何恢复误删照片

数据恢复方法有哪些&#xff1f;电脑文件数据的误删除&#xff0c;基本每个人都遇到过。当我们还是一个电脑小白的时候&#xff0c;说实话这是非常让人崩溃的事情&#xff0c;不过不用担心&#xff0c;今天小编就以自己的亲身经历告诉大家几种比较好用的数据恢复方法。 删除的文…

PyTorch深度学习基础之Tensor对象及其应用的讲解及实战(附源码 简单易懂 包括分段 映射 矩阵乘法 随机数等等)

觉得有帮助请点赞关注收藏 有问题可评论区留言~~~ Tensor对象是一个维度任意的矩阵&#xff0c;但是一个Tensor中所有元素的数据类型必须一致。torch包含的数据类型和普遍编程语言的数据类型类似&#xff0c;包含浮点型&#xff0c;有符号整型和无符号整形&#xff0c;这些类型…

typora免费安装版教程,支持Windows、Mac、Linux

大家好&#xff0c;我是可乐&#xff0c;本篇文章为大家介绍 Typora快捷键、Typora免费安装教程。 Typora是一款简单易用的Markdown编辑器。 目前 Typora 官方是不提供免费版下载了&#xff0c;需要一次性购买版权&#xff0c;支持正版的可以直接前往官网购买&#xff0c;89 …

深圳CPDA认证|学数据分析,其实就是寻找数据背后的规律

现如今&#xff0c;我们正处在一个互联网发展的时代&#xff0c;大大小小的企业对于数据分析相关岗位的需求正开始逐渐增加&#xff0c;因为所有的企业都有数据&#xff0c;企业需要让数据分析师通过整理、分析企业数据总结出企业目前的发展现状&#xff0c;并且也要为企业做出…