TCP_SOCKET编程实现

文章目录

  • 与UDP_SOCKET的区别
  • 第一代Tcp_Server
  • Tcp_Client
  • 第二代Tcp_Server
  • 第三代Tcp_server
  • 多线程版本Tcp_Server
  • 线程池版的Tcp_Server
    • 使用inet_ntop来解决线程安全问题
  • 业务逻辑编写
  • 总结
  • 补充说明&&业务代码完成
    • ping的真实作用
    • Translate编写
    • Transform业务代码
  • 整体总结

与UDP_SOCKET的区别

与udp大同小异
多了一些步骤
udp是不可靠的, 不连接的协议
tcp是面向连接的
c/s

谁来建立这个链接?

是client主动建立链接
如手机上的抖音打开, 淘宝打开等
服务器是在一直等待链接的到来, 好比餐厅老板等待客人到来

所以与udp的区别

  1. 设置socket为监听状态
    函数接口:
    在这里插入图片描述
    参数2后续解释

第一代Tcp_Server

tcp初始化操作, 都是固定套路
初始化代码实现:

  void Init(){// 1. 创建socket, file fd, 本质是文件_sockfd = socket(AF_INET, SOCK_STREAM, 0);//创建tcp套接字, 第一个参数不变, 第二个参数是tcp, 面向字节流if (_sockfd < 0){lg.LogMessage(Fatal, "create socket error, errno code: %d, error string: %s\n", errno, strerror(errno));exit(Fatal);}lg.LogMessage(Debug, "create socket success, sockfd: %d\n", _sockfd);// 2. 填充网络信息并绑定struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_addr.s_addr = htonl(INADDR_ANY);//必须是0, 表示任意local.sin_family = AF_INET;//IPv4的网络套接字local.sin_port = htons(_port);local.sin_zero[0] = 0;//填充剩余部分, 必须置零if(bind(_sockfd, CONV(&local), sizeof(local) != 0)){lg.LogMessage(Fatal, "bind socket error, errno code: %d, error string: %s\n", errno, strerror(errno));exit(Bind_Err);}lg.LogMessage(Debug, "bind socket success, sockfd: %d\n", _sockfd);// 3.设置socket为监听状态if(listen(_sockfd, default_backlog) != 0){lg.LogMessage(Fatal, "listen socket error, errno code: %d, error string: %s\n", errno, strerror(errno));exit(Listen_Err);}lg.LogMessage(Debug, "listen socket success, sockfd: %d\n", _sockfd);
  1. tcp_server获取链接
    在这里插入图片描述

后两个参数是输入输出型, 更强调输出,
这个参数相当于udp的recvfrom

在这里插入图片描述

他的返回值成功会返回一个非0的文件描述符
udp里面, sockfd只有一个

  1. 但是在tcp这里, 会新增一个文件描述符
    eg: 门口拉客的 = 旧的sockfd
    店内传菜的 = 新创建的sockfd
    使用旧的sockfd和client进行获取链接
    使用新的sockfd和client进行通信
    这样看来, 旧的sockfd只用于listen
    新的sockfd才是真的sockfd

  2. udp是面向数据报, 而tcp是面向字节流, 这和文件, 管道的特性一模一样
    Start启动完成

 void Start(){_isrunning = true;while(_isrunning){// 4. 获取链接struct sockaddr_in peer;socklen_t len = sizeof(peer);int sockfd = accept(_listensock, CONV(&peer), &len);// 进行获取链接if(sockfd < 0){lg.LogMessage(Warning, "accept socket error, errno code: %d, error string: %s\n", errno, strerror(errno));continue;// 获取失败则继续获取}lg.LogMessage(Debug, "accept socket success, get a new sockfd: %d\n", sockfd);// 5. 提供服务Service(sockfd);close(sockfd);} }// 这个sockfd表示连接是全双工的void Service(int sockfd){char buffer[1024];while(true){ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);if(n > 0){buffer[n] = 0;std::cout << "client say# " << buffer << std::endl;std::string echo_string = "server echo# ";echo_string += buffer;write(sockfd, echo_string.c_str(), echo_string.size());}else if(n == 0)// read如果为0, 文件中表示读到了结尾, 这边表示对端关闭了连接(与管道的情况一模一样){lg.LogMessage(Info, "client quit...\n");break;}else{lg.LogMessage(Error, "read error, errno code: %d, error string: %s\n", errno, strerror(errno));break;}}}
  1. 查看结果
    在这里插入图片描述
    tcp_client编写
    与udp的差别, tcp在创建了sockfd之后, 只需要建立连接

Tcp_Client

与udp的差别, tcp在创建了sockfd之后, 只需要建立连接

  1. connect连接
    在这里插入图片描述

  2. 文件流进行消息读写

 // 2. client必须要ip和port, 但是不需要显示绑定, client 系统随机端口// tcp发起链接的时候, 被OS自动进行本地绑定// 建立连接connectstruct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);// server.sin_addr.s_addr = inet_addr(serverip.c_str());// 换一个接口 ipv4转二进制 p当成process n是网络 -- 不太准确, 但是好记inet_pton(AF_INET, serverip.c_str(), &server.sin_addr); // 1. IPV4--> 四字节ip 2. 进程转网络序列int n = connect(sockfd, CONV(&server), sizeof(server)); // 这里自动进行bindif (n < 0){cerr << "connect error" << endl;return 2;}// 3. 发送数据// 与服务器进行持续通信的循环while (true){// 用户输入的消息string message;// 提示用户输入信息cout << "Please Enter# ";// 读取用户输入的整行文本getline(cin, message);// 尝试向服务器发送消息ssize_t n = write(sockfd, message.c_str(), message.size());// 检查消息是否成功发送if (n > 0){// 用于接收服务器响应的缓冲区char buffer[1024];// 从服务器读取响应, sockfd读写都用, 更加说明是全双工ssize_t m = read(sockfd, buffer, sizeof(buffer) - 1);// 检查是否接收到响应if (m > 0){// 确保响应字符串以空字符结尾buffer[m] = 0;// 打印服务器的响应std::cout << "get a echo message# " << buffer << std::endl;}// 检查服务器是否关闭连接else if (m == 0){// 服务器关闭时打印信息并退出循环std::cout << "server close!" << std::endl;break;}}// 发送消息时发生错误else{// 打印错误信息并退出循环cerr << "write error" << endl;break;}}close(sockfd);

进行测试:

在这里插入图片描述

结果说明
1. 运行后查看netstat -ntp 有两个服务, 不是一个吗?
原因是, 现在client和server在一台机器上, 所有有两个, 将来如果是在一台机器上, 那就只有一个
在这里插入图片描述2. 这样的新创建的client因为是单进程, 所以新建的client 会阻塞, 发的消息也会阻塞, 当旧的client关闭, 新的client会瞬间显示阻塞的信息, 这个阻塞的极限是多少, 由我们服务器的缓冲区决定
在这里插入图片描述

可以看到, 消息被输出后, 一部分成功输出, 另一部分write失败, 导致溢出到shell中输出
3. 服务器断线重连实现:

a. 将上述的工作放到visitServer函数中
然后, 进行断线重连的操作

int count = 1;while(count <= ReTry_count){bool result = visitServer(serverip, serverport);if(result) break;else{// 重连操作sleep(1);cout << "reconnecting..., count: " << count++ << endl;}}if(count > ReTry_count) {cout << "reconnect failed" << endl;}

b. 有时client端会在创建后, server退出, 此时client重连, 让他实现从1次重连开始
在这里插入图片描述

像这样,就需要修改代码
只需让他多加一个参数, 在这个函数内返回false时, 让count = 1即可, 这个意义不太大, 有了解即可, 因为现实情况是远比当前复杂, 有可能是客户端断线…等等
在这里插入图片描述

  1. tcp_server启动有时候会出现绑定失败的情况
    在这里插入图片描述

原理先不解释, 先说解决方式
setsockopt
在这里插入图片描述

写在server创建listensockfd那里
在这里插入图片描述

第二代Tcp_Server

多进程版本的相互通信
对于一个父进程, 他有他自己的文件描述符表, 且表中的每个信息指向它对应的struct file, 创建子进程之后, 会有多个表中的信息指向同一个struct file, 所以可以像管道那样, 对于父子进程关闭不需要的文件fd
在这里插入图片描述

第三代Tcp_server

信号版本的Tcp_server
在linux 中, 如果对SIGCHLD设置为SIG_IGN, 则会自动回收子进程, 不会wait阻塞住
所以开局设置signal
在这里插入图片描述

然后创建子进程方面默认执行
在这里插入图片描述

因为设置了信号机制, 所以也不用wait

子进程的退出会自动OS回收, 如果不设置信号, 单纯使用上述的代码, 会引起僵尸进程问题

每次创建进程都会消耗时间, 推荐使用进程池来使用, 这边就不进行讲述
问题:
先创建的子进程, 再进行的链接, 进程看不到sockfd,怎么办?
在父子进程之间传递文件描述符的实现, 一个比较老的功能, 可以通过这个解决问题

多线程版本Tcp_Server

相比多进程, 更加的方便, 也不需要文件描述符的传递, 也不需要关闭所谓的fd, 主子线程共享文件描述符表
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

完成之后, 为什么有报错?

在这里插入图片描述

主要是因为类型不匹配导致的
可以考虑使用std::bind来适配成员函数的调用格式,但是更推荐重构成员函数为静态函数或全局函数
传参
在这里插入图片描述

定义实现
在这里插入图片描述

封装
在这里插入图片描述

结果演示

在这里插入图片描述
每加一个线程, 文件描述符表也会增加
链接来了, 才创建线程, 还是慢, 所以使用线程池, 且加上client的信息, 通过accept函数获取, 后两个参数也有输出功能

线程池版的Tcp_Server

先加client信息的处理
使用InetAddr.hpp获取属性
相关代码改动

在这里插入图片描述

在这里插入图片描述
完成之后, 进行验证
在这里插入图片描述

相关问题
在这里插入图片描述

使用inet_ntop来解决线程安全问题

这个转IP的函数返回值是一个char*, 未来的操作可能会有线程安全问题

在这里插入图片描述
他会在函数内部申请一个静态的内存地址, 然后返回, 所以他是一个可重入函数, 存在线程安全问题
应当减少使用, 推荐使用inet_ntop, 这个函数允许我们自己维护一个缓冲区, 而不是函数内部自己去申请
可以有效避免线程安全问题
在这里插入图片描述
在这里插入图片描述
多线程修改:
防止命名冲突, 将threadpool放在ThreadNs里面, 然后再TcpServer的构造函数里面
在这里插入图片描述

下面进行修改代码, 让server能被所有线程就进行访问
往后的任务都是线程池中的线程来接受任务, 进行处理, 而不是创建新的线程
下面实现代码让线程来执行任务

这个任务可以使用Task类来进行处理, 但是也可以使用function来进行处理
这样的方式比较简单
在这里插入图片描述
下面用这个包装器来完成任务
未来获取新的文件文件描述符信息等后, 可以直接进行如下构建
在这里插入图片描述再这样修改完成之后, 因为线程池中的线程数目是固定的, 所以我们的client链接数目有限, 当超过限制的client数目时, 就会出现, 连不上的情况. 同时, 为别人提供的服务是死循环式对我们的server端的压力是有点大的, 所以在一般我们很少去这样写代码

现在对他进行调整, 让它变得实际有用一点, 而不是像现在这样进行通信
也就是说, 不能让我们的服务像是死循环这样一直执行, 而是实现一个业务逻辑
这边的策略是实现一个英译汉的功能,要利用I/O, 同时引入unordered_map
定义回调函数(后期可能进行修改)
在这里插入图片描述
现在只需要Tcp_server进行IO就可以了
然后构建业务逻辑

业务逻辑编写

在这里插入图片描述
在这里存放所有的任务
接下来给Tcp_Server一个register功能
在这里插入图片描述
Read方式是进行读取用户第一次输入必须是输入他想要的功能
在 ping Translate Transform之间三选一
read看是读取了哪个结果
在这里插入图片描述进行一个Routine 的路由功能, 实现read到对应的内容给type, 好实现对应的功能, 对应的功能是放在funcs中的
在这里插入图片描述然后, 线程在启动执行的时候, 将对应的sockfd 的功能和Routine进行绑定

在这里插入图片描述
然后, 服务器创建出来之后(这一步在Main.cc文件中), 执行注册服务
在这里插入图片描述
注册之后, 执行对应的服务, 以便调用时执行对应的回调函数

这个register进行添加对应的name和方法到map
然后在进行Start的时候, 将要执行的方法和对应的用户输入名称之间的匹配调用, 此时Ping之类的函数才算是被真正的执行

Ping函数要做的事就是之前在Tcp_server里面, server方法要做的事

在这里插入图片描述然后在修改Tcp_Client

在这里插入图片描述
完成代码进行执行查看, 可以正常执行
server-log
在这里插入图片描述

client-log
在这里插入图片描述
现在补充其他的路由信息,进行基本的完善
在这里插入图片描述
在这里插入图片描述具体功能后续进行补齐

现在ping的作用事是想实现一下一次ping之后的结果, 所以还要进行修改代码
在这里插入图片描述然后因为之前是将Sockfd的关闭放在tcp_server的ThreadData类的析构函数中的, 但是现在已经不需要这个类了, 所以还要进行文件描述符的关闭操作, 这个操作可以放在这个回调函数执行完毕的后面

在这里插入图片描述
下面继续执行代码

在这里插入图片描述
可以看到在一次运行结束之后, 这个client会进行退出, 主要是因为当期的ping服务已经变成了短服务,只需要执行一次, 而不是像之前那样的死循环

现在进行总结一下

总结

未来服务可能部署在云服务器上面, 如何在未来的某一时刻知道这个服务器是否健康的呢?

可以定期(30s)给服务器发送一个最小服务请求, 如果得到回复, 那么这个服务就是正常的

这个机制, 我们称为心跳机制, 我们可以通过client->服务器, 同时也可以反向的得到server->client
而这个编写的Ping, 其实就是对心跳的一个响应机制, 用于检测服务器是否是正常的

补充说明&&业务代码完成

ping的真实作用

在这里插入图片描述Interact是进行交互功能, 它可以进行心跳机制的检测, 读消息是out, 发消息是in, 以此完成交互
在这里插入图片描述
下面进行Translate

Translate编写

首先, 我们到此为止,所有的代码都是网络
在这里插入图片描述
下面是业务代码, 顺便完成Translate
为了方便, 可以将单词加载在resource的dict.txt文件内, 进行文件操作

在这里插入图片描述

然后构建Translate.hpp业务逻辑代码
在这里插入图片描述
定义正常的常变量
在这里插入图片描述
字典路径, 当前字典, 当前字典读取的内容
然后是构造函数和加载字典的方法
在这里插入图片描述然后是内部函数

在这里插入图片描述
现在可以DEBUG一下, 看看有没有读取成功
推荐使用条件编译
在这里插入图片描述
但是这个是.hpp文件, 所以不能这样进行条件编译操作
可以创建一个test.cc来进行debug在这里插入图片描述
在这里插入图片描述
说明我们在Translate的构造函数, 读取字典函数, private变量都没有问题

然后进行分割功能
在这里插入图片描述做完分割, 那么可以对这个Debug进行修改

在这里插入图片描述
在这里插入图片描述
未来的加载词库, 只需要往dict.txt中加就行了. 当然更推荐将这个Translate类改为单例模式, 这边就不做处理

那么如何将这个Translate服务于网络进行结合呢?
那就到了Translate调用
首先定义全局变量(改为单例最好)

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
所以未来服务首先都是进行注册的

Transform业务代码

在这里插入图片描述
在这里插入图片描述
将1按照OP的格式转化为result存到1里面
在这里插入图片描述
代码完成, 对于defaultserver的部分修改就不演示

整体总结

  1. IO类函数, write/read其实他们已经做了转网络序列的处理
  2. udp :数据包 tcp: 面向字节流
    a. 区别:
    1). TCp代码中我们使用read/write目前是有BUG的(下节讲解), TCP要进行正确读写, 必须结合用户协议
    2). udp-----数据和数据是有边界的, sendto只发了一次, 对端对应对端, recvfrom一次----面向数据报
    3). tcp------write->1,10, 100->接收方可能一次收完, 也可能是很多次, 但无论是多少次,都和对方发送无关—面向字节流
    b.udp就好比是一封信件, 只能一封一封去读
    c. tcp是全读, 然后进行手动分割
  3. 网络服务在真正运行时, 必须在Linux中以守护进程(精灵进程)的形式进行运行

要完成上述工作, 应当利用守护进程, 下篇讲解~~~~~

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

胤娲科技:机械臂「叛逃」记——自由游走,再悄然合体

夜深人静&#xff0c;你正沉浸在梦乡的前奏&#xff0c;突然意识到房间的灯还亮着。此刻的你&#xff0c;是否幻想过有一只无形的手&#xff0c;轻盈地飘过&#xff0c;帮你熄灭那盏碍眼的灯&#xff1f; 又或者&#xff0c;你正窝在沙发上&#xff0c;享受电视剧的紧张刺激&am…

C语言 | Leetcode C语言题解之第466题统计重复个数

题目&#xff1a; 题解&#xff1a; #include <stdlib.h> #include <stdio.h> #include <stdbool.h> #include <string.h> #include <math.h> #include <limits.h>#define MMAX(a, b) ((a) > (b)? (a) : (b)) #define MMIN(a,…

打卡第六天 P10287 [GESP样题 七级] 最长不下降子序列

今天是我打卡第六天&#xff0c;做个普及/提高−题吧(#^.^#) 原题链接&#xff1a;[GESP样题 七级] 最长不下降子序列 - 洛谷 题目描述 输入格式 输出格式 输出一行一个整数表示答案。 输入输出样例 输入 #1 5 4 2 10 6 3 1 5 2 2 3 3 1 1 4 输出 #1 3 输入 #2 6 11 …

微服务——分布式事务

目录 分布式事务 1.1分布式事务的特性 1.2分布式事务应用背景 ​编辑 1.3.认识Seata 1.4部署TC服务 1.4.1.准备数据库表 1.4.2.准备配置文件 1.4.3.Docker部署 1.5.微服务集成Seata 1.5.1.引入依赖 1.5.2.改造配置 1.5.3.添加数据库表 ​编辑1.6.XA模式 1.6.1.两…

JavaScript函数基础(通俗易懂篇)

10.函数 10.1 函数的基础知识 为什么会有函数&#xff1f; 在写代码的时候&#xff0c;有一些常用的代码需要书写很多次&#xff0c;如果直接复制粘贴的话&#xff0c;会造成大量的代码冗余&#xff1b; 函数可以封装一段重复的javascript代码&#xff0c;它只需要声明一次&a…

精品WordPress主题/响应式个人博客主题Kratos

Kratos 是一款专注于用户阅读体验的响应式 WordPress 主题&#xff0c;整体布局简洁大方&#xff0c;针对资源加载进行了优化。 Kratos主题基于Bootstrap和Font Awesome的WordPress一个干净&#xff0c;简单且响应迅速的博客主题&#xff0c;Vtrois创建和维护&#xff0c; 主…

LeetCode 刷题基础 -- 模板原型Ⅰ

模板原型 - 基础篇 学习网站一、进制转换二、二分查找① 查找指定元素② 查找第一个大于等于 x 值的序列下标③ 查找第一个大于 x 值的序列下标④ 单峰序列 三、双指针① 两数之和② 序列合并③ 集合求交④ 集合求并 四、其他高效技巧与算法① 区间和② 01 对③ 左小数 五、数学…

BLE MESH学习1-基于沁恒CH582学习

BLE MESH学习1-基于沁恒CH582学习 一、BLE mesh说明 mesh组网可以实现相比点对点模式更远的距离、更灵活的网络形式、更可靠的连接和更多的设备加入。BLE mesh在IoT中的传感器和控制具有重要意义。我的目的也是IoT领域&#xff0c;实现自己的传感器读取、开关控制等类似米家智…

知识改变命运 数据结构【java对象的比较】

0&#xff1a;前言 在基本数据类型中&#xff0c;我们可以直接使用号比较是否相等&#xff0c;还记的学堆哪里时候&#xff0c;插入一个数据&#xff0c;就会与其他数据进行比较&#xff0c;当时我们传入的是Integer类型&#xff0c;在Integer类里面已经实现了compare。 如果…

西门子S7-1200博途软件项目的下载

S7-1200的CPU本体上集成了PROFINET通信口&#xff0c;通过这个通信口可以实现CPU与编程设备的通信。 此外&#xff0c;S7-1200 可以通过连接CM1243-5扩展模块&#xff0c;然后电脑通过PC ADAPTER USB A2电缆、或者电脑上的CP卡&#xff08;例如CP5612&#xff09;通过PROFIBUS…

手写mybatis之SQL执行器的定义和实现

前言 所有系统的设计和实现&#xff0c;核心都在于如何解耦&#xff0c;如果解耦不清晰最后直接导致的就是再继续迭代功能时&#xff0c;会让整个系统的实现越来越臃肿&#xff0c;稳定性越来越差。而关于解耦的实践在各类框架的源码中都有非常不错的设计实现&#xff0c;所以阅…

陪伴系统,会成为女性向游戏的下一个争夺点吗?

乙游提供给女性玩家的只有恋爱感吗&#xff1f; 一般来说&#xff0c;对于乙女游戏的概括常常以为玩家提供“恋爱陪伴感”为主&#xff0c;恋爱很好理解&#xff0c;通过与多位男主角的剧情互动来模拟在真实恋爱中的情感交互&#xff0c;当下乙游都将重点放在了营造恋爱感上。…

55页可编辑PPT | 制造企业数字化转型顶层规划案例

基于集团的战略和运营特点&#xff0c;数字化转型应如何考虑&#xff1f; 在集团的战略和运营特点基础上进行数字化转型&#xff0c;需要实现业务多元化&#xff0c;整合资源和流程&#xff0c;推动国际化拓展&#xff0c;实施差异化战略&#xff0c;并通过数据驱动决策&#…

Vue工程化结构环境安装及搭建教程 : 之nvm

vue需要的环境&#xff1a; node.js : Node.js和Vue.js通常会一起使用。Node.js作为后端服务器&#xff0c;处理服务器端的逻辑和数据访问&#xff0c;而Vue.js则负责前端用户界面的构建和交互。通过Ajax通信&#xff0c;Vue.js应用程序向Node.js服务器发送请求&#xff0c;并…

【Ubuntu】git

文章目录 1.配置SSH key2. 基础知识操作命令1分支branch 如果对git命令使用不熟悉&#xff0c;推荐一个非常棒的git在线练习工具 Learn Git Branching。 https://m.runoob.com/git/git-basic-operations.html 1.配置SSH key ssh-keygen -t rsa -C "YOUR EMAIL"完成…

PDF无法导出中文

font/SIMSUN.TTC with Identity-H is not recognized. 查看BaseFont源码发现".ttc," 改为"SIMSUN.TTC,a"提示数字转换异常 改为"SIMSUN.TTC,11"提示数字索引必须介于0和1之间 改为0或1结果正常 BaseFont baseFont BaseFont.createFont("/U…

迎接国庆旅游热潮,火山引擎数据飞轮助力景区数智化升级

随着人们生活水平的提高和旅游消费观念的转变&#xff0c;国庆假期成为人们出行旅游的黄金时段。同程旅行发布的报告显示&#xff0c;北京、杭州、重庆、上海、南京、成都等城市仍是 “十一” 假期国内热门的目的地&#xff0c;而一些新兴的宝藏旅游目的地如新疆阿勒泰、云南迪…

四.python核心语法

目录 1.序列 1.1. 索引 1.2. 切片 1.3. 总结 2.加法和乘法 2.1. 加法 2.2. 乘法 3.常用函数 3.1.sum()函数 3.2.max()函数和min()函数 3.3.len()函数 4. list 列表 [ ] 基本操作 4.1. 列表的定义 4.2. 列表的创建&#xff08;list()函数&#xff09; 4.3. 列表的…

RabbitMQ概述

什么是MQ MQ (message queue)消息队列 MQ从字⾯意思上看,本质是个队列,FIFO先⼊先出&#xff0c;只不过队列中存放的内容是消息(message).消息可以⾮常简单,⽐如只包含⽂本字符串,JSON等,也可以很复杂,⽐如内嵌对象 RabbitMQ是MQ的一种实现,是Rabbit 企业下的⼀个消息队列产…

Python 如何使用 scikit-learn 进行模型训练

如何使用 scikit-learn 进行模型训练 一、简介 在现代的数据科学和机器学习领域&#xff0c;Python 已经成为最流行的编程语言之一。而其中最流行的机器学习库之一就是 scikit-learn。scikit-learn 提供了许多方便的工具和函数来实现常见的机器学习任务&#xff0c;包括数据预…