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

TCP网络套接字

一、创建套接字
#include <sys/types.h>  
#include <sys/socket.h>  int socket(int domain, int type, int protocol);

参数:

  • domain:指定使用的协议族。常见的取值有AF_INET(IPv4)和AF_INET6(IPv6)。这个参数决定了地址的格式和类型。
  • type:指定套接字的类型。常见的取值有SOCK_STREAM(流套接字,提供有序的、可靠的、双向的和基于连接的字节流,通常使用TCP协议)和SOCK_DGRAM(数据报套接字,支持无连接的、不可靠的和使用固定大小缓冲区的数据报服务,通常使用UDP协议)。
  • protocol:指定协议编号。通常可以设置为0,让系统根据domain和type自动选择合适的协议。

返回值:

  • 成功:socket函数成功执行时,返回一个非负整数,续的socket编程中用于标识和操作该套接字即套接字的文件描述符。这个描述符在后。
  • 失败:如果socket函数调用失败,将返回-1,并设置相应的errno以指示错误原因。
二、绑定
#include <sys/types.h>  
#include <sys/socket.h>  int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

bind函数的作用是将一个套接字与一个具体的地址(包括IP地址和端口号)绑定。这样,当套接字进行通信时,就可以使用这个指定的地址作为通信的源地址。

参数:

  • sockfd:标识一个已经创建但尚未绑定的套接字的文件描述符。
  • addr:指向一个包含地址信息的结构体的指针。对于IPv4,通常使用struct sockaddr_in;对于IPv6,则使用struct sockaddr_in6。
  • addrlen:addr结构体的大小,通常可以使用sizeof操作符获取。

返回值:

  • 成功时,bind函数返回0。
  • 失败时,返回-1,并设置相应的errno以指示错误原因。
    void Init(){// 创建tcp套接字_listensockfd = socket(AF_INET, SOCK_STREAM, 0);if (_listensockfd < 0){LOG(FATAL, "sockfd create fall!\n");exit(SOCKET_ERROR);}LOG(INFO, "create sockfd success,sockfd:%d\n", _listensockfd);// bindstruct sockaddr_in local;local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;int n = ::bind(_listensockfd, (struct sockaddr *)&local, sizeof(local));if (n < 0){LOG(FATAL, "bind false!\n");exit(BIND_ERROR);}LOG(INFO, "bind success!\n");}
三、将套接字设置为监听状态

对于UDP来说绑定完就可以正常进行读取发送数据了,因为UDP协议是无连接的,但是TCP协议是可连接的,所以我们需要先将套接字设置为监听状态

int listen(int sockfd, int backlog);

参数: 

  • sockfd:这是需要设置为监听状态的套接字的文件描述符。这个套接字之前应该已经通过 socket函数创建,并通过bind函数绑定到了一个特定的IP地址和端口上。
  • backlog:这个参数定义了内核应该为相应套接字排队的最大连接数。这个值至少为0,但实际的最大值取决于系统。如果设置为0,则系统会根据其配置来决定一个合适的值。这个值并不是限制同时连接客户端的数量,而是限制在套接字处于listen状态时,尚未被accept调用的连接请求队列的最大长度。

返回值:

  • 成功时,函数返回0。
  • 失败时,返回-1,并设置错误码。
    void Init(){// 创建tcp套接字_listensockfd = socket(AF_INET, SOCK_STREAM, 0);if (_listensockfd < 0){LOG(FATAL, "sockfd create fall!\n");exit(SOCKET_ERROR);}LOG(INFO, "create sockfd success,sockfd:%d\n", _listensockfd);// bindstruct sockaddr_in local;local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;int n = ::bind(_listensockfd, (struct sockaddr *)&local, sizeof(local));if (n < 0){LOG(FATAL, "bind false!\n");exit(BIND_ERROR);}LOG(INFO, "bind success!\n");// listenn = ::listen(_listensockfd, gbacklog);if (n < 0){LOG(FATAL, "listen false!\n");exit(LISEN_ERROR);}LOG(INFO, "listen success!\n");}
四、服务端获取连接

accept函数是网络编程中用于TCP服务器的一个关键系统调用,它用于从完成连接队列中取出下一个已完成连接请求,并创建一个新的套接字来与该客户端进行通信。这个函数通常与 listen函数一起使用,在TCP服务器程序中扮演着接收客户端连接的角色。

#include <sys/socket.h>  int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

参数:

  • sockfd:这是之前通过 socket函数创建的,并且已经通过bind和listen函数准备就绪以接受连接的监听套接字的文件描述符。
  • addr这是一个指向 sockaddr结构的指针,该结构将被填充以表示连接客户端的地址信息。如果调用者对客户端的地址不感兴趣,可以将此参数设置为NULL。
  • addrlen:这是一个指向socklen_t变量的指针,该变量在调用前应该包含 addr缓冲区的大小,在调用后,它将包含实际存储在addr中的地址信息的实际字节数。

返回值:

  • 成功时,返回一个新的套接字文件描述符,该描述符用于与连接的客户端进行通信。
  • 失败时,返回-1,并设置错误码。

注意:到目前为止一共出现了两个套接字,一个是我们用socket函数创建的,另一个是accept返回的,其中我们创建的套接字是用来监听的,所以我们才把他命名为listensockfd,而accept返回的套接字是我们用来进行数据接收发送的。

    void Start(){_isrunning = true;while (_isrunning){struct sockaddr_in client;memset(&client, 0, sizeof(client));socklen_t len = sizeof(client);int sockfd = accept(_listensockfd, (struct sockaddr *)&client, &len);if (sockfd < 0){LOG(ERROR, "accept false!\n");continue;}LOG(INFO, "connect a link! sockfd:%d\n", sockfd);InetAddr addr(client);//通过accept返回的套接字我们就可以进行网络通信了Service(sockfd, addr);}}

同样我们实现的Service函数就是实现一个简单的回显函数

    void Service(int sockfd, InetAddr addr){while (true){char buffer[1024];int n = read(sockfd, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = 0;LOG(DEBUG, "[%s]#%s\n", addr.AddrStr().c_str(), buffer);std::string echo_message = "[udpserver echo]#";echo_message += buffer;n = write(sockfd, echo_message.c_str(), sizeof(echo_message));if (n < 0){LOG(ERROR, "server write false!\n");}}else if (n == 0) // 读到文件结尾{LOG(INFO, "%s quit!\n", addr.AddrStr().c_str());break;}else{LOG(INFO, "read error!\n");break;}}::close(sockfd);}
五、读取发送数据

由于tcp协议是面向字节流的,所以我们可以直接使用read、write向文件描述符读取发送数据,而为了与UDP协议的函数相类似,OS还提供了recv和send函数

recv

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

 参数:

  • sockfd/s:socket文件描述符或套接字描述符,它指定了要从中读取数据的socket。
  • buf:指向缓冲区的指针,用于存储接收到的数据。这个缓冲区应该足够大,以存储预期的数据量。
  • len:指定了buf缓冲区的大小,即函数最多可以读取的字节数。
  • flags:用于指定接收操作的行为,这个参数通常是0,表示阻塞读取

返回值:

  • 如果成功,recv返回实际读取的字节数,该值可能小于请求读取的字节数(例如,如果数据不足或对方关闭了连接)。
  • 如果连接被对方正常关闭,并且已经读取了所有可用的数据,recv将返回0。
  • 如果出现错误,recv 将返回-1

send

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
六、客户端

客户端首先需要创建一个套接字,并将服务端的ip地址端口号等信息录入sockaddr_in结构体中,与UDP套接字的使用一样,客户端不需显示的调用bind函数,其次由于TCP协议是需要连接的,所以客户端需要先调用connect函数与服务端构建联系

#include <sys/types.h>  
#include <sys/socket.h>  int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//使用方式:./tcpclient ip地址 端口号
int main(int argc, char *argv[])
{if (argc != 3){std::cerr << "Usage:" << argv[0] << " serverip serverport" << std::endl;exit(0);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);int sockfd = socket(AF_INET, SOCK_STREAM, 0);struct 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());int n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));if (n < 0){std::cerr << "connect socket error" << std::endl;exit(2);}while (true){std::string sendmessage;std::cout << "Please Enter#";std::getline(std::cin, sendmessage);write(sockfd, sendmessage.c_str(), sizeof(sendmessage));char echo_buffer[1024];n = read(sockfd, echo_buffer, sizeof(echo_buffer));if (n > 0){echo_buffer[n] = 0;std::cout << echo_buffer << std::endl;}else{break;}}::close(sockfd);return 0;
}

上述代码其实存在一个问题,由于我们上述的代码是单进程的,而Service业务是一个长服务,他不会主动退出,当第一个客户端进来以后是可以正常执行业务的,但是如果第二个客户端进来以后由于只有一个执行流,第二个客户端无法正常执行业务,只有当第一个客户端执行完退出以后第二个客户端才能正常执行,所以我们继续改进一下

七、多进程版

        我们可以利用fork函数创建出一个子进程,让子进程来执行Service,父进程等待子进程退出后,就可以继续执行循环,接收别的客户端,但是我们还要考虑阻塞等待的问题,我们可以将等待方式设置为非阻塞等待的,但是这种方式有点麻烦。我们知道子进程退出后会给父进程发送SIGNALCHLD信号,最简单的方式就是可以将这个信号设置为忽略,signal(SIGCHLD,SIG_IGN);这里还有另一种方式,我们可以再创建一个孙子进程,并将子进程退出,子进程就直接会被父进程等待,这样的话孙子进程就会变成孤儿进程,被bash管理,让孙子进程执行我们的业务,这样我们就不需要考虑等待的问题了。

        这里还有一个小细节,子进程会继承父进程的文件描述符表,我们知道每一个客户端会对应一个sockfd,而文件描述符表本质就是一个数组,也是有数量大小的,当客户端的数量比较多了的话文件描述符表可能就会被占满,其次父进程不关心业务执行什么,也就是父进程不关心sockfd,所以每当创建一个子进程建议父进程将sockfd关掉,也建议子进程将listenfd也关掉方式误操作,这样不论有多少个客户端,其对应的文件描述符永远是4

    void Start(){_isrunning = true;while (_isrunning){struct sockaddr_in client;memset(&client, 0, sizeof(client));socklen_t len = sizeof(client);int sockfd = accept(_listensockfd, (struct sockaddr *)&client, &len);if (sockfd < 0){LOG(ERROR, "accept false!\n");continue;}LOG(INFO, "connect a link! sockfd:%d\n", sockfd);InetAddr addr(client);// version1:有缺陷,Service是长服务,由于这个代码是单进程的,第二个客户端进来会执行不了// Service(sockfd, addr);// version2: 多进程pid_t id = fork();if (id == 0){// 子进程::close(_listensockfd); // 防止误操作// 由于是阻塞等待,service不退出父进程就不会向后继续执行循环,简单的方式是signal(SIGCHLD,SIG_IGN);,// 也可以这样写,创建一个孙子进程并直接exit这样子进程就变成了孤儿进程交给bash处理,这样我们就不需要管子进程了if (fork() > 0)exit(0);Service(sockfd, addr);exit(0);}::close(sockfd); // 父进程不关心执行什么任务pid_t rid = waitpid(id, nullptr, 0);if (rid > 0){LOG(INFO, "wait success!\n");}}}
八、多线程版

创建一个多线程要求我们在pthread_create时传入一个参数为void*返回值为void*的函数,所以我们可以在类内设计一个静态的Execute函数,而我们想要调用这个函数就需要一个对象,其次我们的业务Service需要传入套接字sockfd和客户端信息Inet_Addr,所以我们可以将这三个元素封装成一个类,将这个类对象作为参数传给Execute

    class ServerData{public:ServerData(int sockfd, InetAddr &addr, TcpServer *td): _sockfd(sockfd), _addr(addr), _td(td){}public:int _sockfd;InetAddr _addr;TcpServer *_td;};
void Start(){_isrunning = true;while (_isrunning){struct sockaddr_in client;memset(&client, 0, sizeof(client));socklen_t len = sizeof(client);int sockfd = accept(_listensockfd, (struct sockaddr *)&client, &len);if (sockfd < 0){LOG(ERROR, "accept false!\n");continue;}LOG(INFO, "connect a link! sockfd:%d\n", sockfd);InetAddr addr(client);// version 3:多线程版pthread_t tid;ServerData* sd=new ServerData(sockfd,addr,this);pthread_create(&tid, nullptr, Excute, sd);}}
九、线程池版

虽然这种方式可以,但是很不建议这样写,因为我们写的线程池中的线程也是有限的,而我们的业务是长服务,这样就会导致如果我们的客户端数量大于线程数量时,有些客户端因为没有执行流可能无法获取正常的业务,所以不建议线程池执行长服务

    void Start(){_isrunning = true;while (_isrunning){struct sockaddr_in client;memset(&client, 0, sizeof(client));socklen_t len = sizeof(client);int sockfd = accept(_listensockfd, (struct sockaddr *)&client, &len);if (sockfd < 0){LOG(ERROR, "accept false!\n");continue;}LOG(INFO, "connect a link! sockfd:%d\n", sockfd);InetAddr addr(client);// version4:线程池// ps:线程池不适合做长服务,因为线程池的线程数量也是有限的,如果客户端数量超过线程数量,再有客户端加进来也得不到服务task_t t = std::bind(&TcpServer::Service, this, sockfd, addr);ThreadPool<task_t>::GetInstance()->Enqueue(t);}}

上述所有代码可以参考:

张得帅c/Linux

相关文章:

TCP网络套接字

一、创建套接字 #include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, int protocol); 参数&#xff1a; domain&#xff1a;指定使用的协议族。常见的取值有AF_INET&#xff08;IPv4&#xff09;和AF_INET6&#xff08;IPv6&a…...

Element学习(axios异步加载数据、案例操作)(5)

1、这次学习的是上次还未完成好的恶element案例&#xff0c;对列表数据的异步加载&#xff0c;并渲染展示。 ——>axios来发送异步请求 &#xff08;1&#xff09; &#xff08;2&#xff09;在vue当中安装axios &#xff08;注意在当前的项目目录&#xff0c;并且安装完之后…...

大数据-65 Kafka 高级特性 分区 Broker自动再平衡 ISR 副本 宕机恢复再重平衡 实测

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…...

html+css+js网页设计 软通动力网站2个页面(带js)首页轮播图+置顶导航

htmlcssjs网页设计 软通动力网站2个页面&#xff08;带js&#xff09;首页轮播图置顶导航 网页作品代码简单&#xff0c;可使用任意HTML编辑软件&#xff08;如&#xff1a;Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及…...

【经验分享】ShardingSphere+Springboot-04:自定义分片算法(COMPLEX/STANDARD)

文章目录 3.4 CLASS_BASED 自定义类分片算法3.4.1 复杂分片自定义算法&#xff08;strategyCOMPLEX &#xff09;3.4.2 STANDARD 标准分片自定义算法## 进阶:star: 自定义算法范围查询优化 3.4 CLASS_BASED 自定义类分片算法 3.4.1 复杂分片自定义算法&#xff08;strategyCOM…...

如何设置RabbitMQ和Redis消息队列系统

设置RabbitMQ和Redis作为消息队列系统时&#xff0c;需要分别进行安装、配置和测试&#xff0c;以确保它们能够正常工作并满足你的应用需求。以下是一个基于这两个系统的设置指南&#xff1a; RabbitMQ的设置 1. 安装Erlang 由于RabbitMQ是用Erlang语言编写的&#xff0c;因…...

白骑士的Matlab教学高级篇 3.3 工具箱与扩展

MATLAB 提供了丰富的工具箱&#xff08;Toolbox&#xff09;和扩展功能&#xff0c;这些工具箱涵盖了各个领域的专业计算需求&#xff0c;如信号处理、图像处理、统计与机器学习等。利用工具箱&#xff0c;用户可以快速实现复杂的计算和分析任务。本文将介绍常用的工具箱及其使…...

bug: 配置flyway.locations多个脚本位置不生效

文章目录 业务场景场景一场景二 业务场景 随着项目版本迭代&#xff0c;数据库结构也会变动。如果一个项目引用其他项目的jar包&#xff0c;并且需要执行对应jar包的flyway脚本&#xff0c;就需要配置flyway.locations 场景一 正常情况下&#xff0c;在一个项目中可以在yml文件…...

8月5日SpringBoot学习笔记

今日内容:搭建mybatis ORM 配置数据源 $#的区别 增删改查 搭建mybatis 在原有maven项目基础配置上进行&#xff1a; pom文件添加依赖 <!-- Mybatis --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-…...

Java学习笔记(二十):反射、动态代理、日志、类加载器、xml、单元测试Junit、注解

目录 一、反射 1.1 反射的概述&#xff1a; 1.2 学习反射到底学什么&#xff1f; 1.3 获取字节码文件对象的三种方式 1.4 字节码文件和字节码文件对象 1.5 获取构造方法 1.6 获取构造方法并创建对象 1.7 获取成员变量 1.8 获取成员变量并获取值和修改值 1.9 获取成员…...

如何快速从文本中找到需要的信息,字典和正则灵活运用

import re #打开文本文件 f open("stock_data.txt",encoding"utf-8") #单独读取第一行数据处理进行分割&#xff0c;末尾换行符去掉 headers f.readline().strip().split(,) print(headers) #定义一个字典&#xff0c;以股标代码做为KEY,每个行做为值 st…...

springboot3整合redis

来源于https://www.bilibili.com/video/BV1UC41187PR/?spm_id_from333.1007.top_right_bar_window_history.content.click&vd_source865f32e12aef524afb83863069b036aa 一、整合redis 1.创建项目文件 2.添加依赖 <dependencies><dependency><groupId>…...

VUE基础快速入门

VUE 和 VUE-Cli VUE 是一种流行的渐进式JavaScript框架&#xff0c;用于构建Web用户界面它具有易学、轻量级、灵活性强、高效率等特点&#xff0c;并且可以与其他库和项目集成是目前最流行的前端框架之一VUE-Cli 称为“VUE脚手架”,它是由VUE官方提供的客户端&#xff0c;专门为…...

用Python实现特征工程之特征提取——数值特征提取、类别特征提取、文本特征提取、时间特征提取

特征提取是特征工程中的关键步骤&#xff0c;它从原始数据中提取有意义的特征&#xff0c;以便机器学习模型能够更好地理解和学习数据。根据数据类型&#xff0c;特征提取可以分为数值特征提取、类别特征提取、文本特征提取和时间特征提取。下面详细讲解每种特征提取方法&#…...

按图搜索新体验:阿里巴巴拍立淘API返回值详解

阿里巴巴拍立淘API是一项基于图片搜索的商品搜索服务&#xff0c;它允许用户通过上传商品图片&#xff0c;系统自动识别图片中的商品信息&#xff0c;并返回与之相关的搜索结果。以下是对阿里巴巴拍立淘API返回值的详细解析&#xff1a; 一、主要返回值内容 商品信息 商品列表…...

vue跨域问题

本地调试 可以通过在vue.config.js中配置devServer来实现跨域请求。 module.exports {publicPath: ./,productionSourceMap: false, // 生产环境是否生成 sourceMap 文件devServer: {proxy: {/bi: {target: http://1.11.113.20:1234/bi, // 后台接口域名ws: false, //…...

【NLP】文本处理的基本方法【jieba分词、命名实体、词性标注】

文章目录 1、本章目标2、什么是分词3、jieba的使用3.1、精确模式分词3.2、全模式分词3.3、搜索引擎模式分词3.4、中文繁体分词3.5、使用用户自定义词典 4、什么是命名实体识别5、什么是词性标注6、小结7、jieba词性对照表⭐ &#x1f343;作者介绍&#xff1a;双非本科大三网络…...

unity 本地使用Json(全套)

提示&#xff1a;文章有错误的地方&#xff0c;还望诸位大神不吝指教&#xff01; 文章目录 前言一、Json是什么&#xff1f;二、创建Json文件1.在线编辑并转实体类&#xff08;C#&#xff09;2.Json文件 三、解析Json并使用四、报错&#xff1a;JsonError&#xff1a;JsonExce…...

java消息队列ActiveMQ

安装 前置条件 activemq的运行依赖于jdk&#xff0c;需要提前安装jdk如果已经安装了jdk&#xff0c;需要根据jdk的版本来选择对应的版本进行安装activemq版本对应在官网上&#xff0c;使用java -version 看jdk的版本注意&#xff1a;jdk和mq的版本不一致会报错&#xff0c;电脑…...

Android SurfaceFlinger——信号同步原理(五十一)

经过前面系列文章的学习,我们的已经理解了 SurfaceFlinger 运行机制以及同步机制,但是SurfaceFlinger 又是以什么方法是把需要刷新的信号发送给 App 进程的。 一、VSync简介 垂直同步(Vertical Synchronization,简称 VSync)是一种用于同步视频信号和显示设备刷新率的技术…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)

说明&#xff1a; 想象一下&#xff0c;你正在用eNSP搭建一个虚拟的网络世界&#xff0c;里面有虚拟的路由器、交换机、电脑&#xff08;PC&#xff09;等等。这些设备都在你的电脑里面“运行”&#xff0c;它们之间可以互相通信&#xff0c;就像一个封闭的小王国。 但是&#…...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度​

一、引言&#xff1a;多云环境的技术复杂性本质​​ 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时&#xff0c;​​基础设施的技术债呈现指数级积累​​。网络连接、身份认证、成本管理这三大核心挑战相互嵌套&#xff1a;跨云网络构建数据…...

django filter 统计数量 按属性去重

在Django中&#xff0c;如果你想要根据某个属性对查询集进行去重并统计数量&#xff0c;你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求&#xff1a; 方法1&#xff1a;使用annotate()和Count 假设你有一个模型Item&#xff0c;并且你想…...

cf2117E

原题链接&#xff1a;https://codeforces.com/contest/2117/problem/E 题目背景&#xff1a; 给定两个数组a,b&#xff0c;可以执行多次以下操作&#xff1a;选择 i (1 < i < n - 1)&#xff0c;并设置 或&#xff0c;也可以在执行上述操作前执行一次删除任意 和 。求…...

(转)什么是DockerCompose?它有什么作用?

一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用&#xff0c;而无需手动一个个创建和运行容器。 Compose文件是一个文本文件&#xff0c;通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...

Linux离线(zip方式)安装docker

目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1&#xff1a;修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本&#xff1a;CentOS 7 64位 内核版本&#xff1a;3.10.0 相关命令&#xff1a; uname -rcat /etc/os-rele…...

【无标题】路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论

路径问题的革命性重构&#xff1a;基于二维拓扑收缩色动力学模型的零点隧穿理论 一、传统路径模型的根本缺陷 在经典正方形路径问题中&#xff08;图1&#xff09;&#xff1a; mermaid graph LR A((A)) --- B((B)) B --- C((C)) C --- D((D)) D --- A A -.- C[无直接路径] B -…...

MySQL 索引底层结构揭秘:B-Tree 与 B+Tree 的区别与应用

文章目录 一、背景知识&#xff1a;什么是 B-Tree 和 BTree&#xff1f; B-Tree&#xff08;平衡多路查找树&#xff09; BTree&#xff08;B-Tree 的变种&#xff09; 二、结构对比&#xff1a;一张图看懂 三、为什么 MySQL InnoDB 选择 BTree&#xff1f; 1. 范围查询更快 2…...

淘宝扭蛋机小程序系统开发:打造互动性强的购物平台

淘宝扭蛋机小程序系统的开发&#xff0c;旨在打造一个互动性强的购物平台&#xff0c;让用户在购物的同时&#xff0c;能够享受到更多的乐趣和惊喜。 淘宝扭蛋机小程序系统拥有丰富的互动功能。用户可以通过虚拟摇杆操作扭蛋机&#xff0c;实现旋转、抽拉等动作&#xff0c;增…...

【前端异常】JavaScript错误处理:分析 Uncaught (in promise) error

在前端开发中&#xff0c;JavaScript 异常是不可避免的。随着现代前端应用越来越多地使用异步操作&#xff08;如 Promise、async/await 等&#xff09;&#xff0c;开发者常常会遇到 Uncaught (in promise) error 错误。这个错误是由于未正确处理 Promise 的拒绝&#xff08;r…...