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

聊聊TCP协议的粘包、拆包以及http是如何解决的?

目录

一、粘包与拆包是什么?

二、粘包与拆包为什么发生?

三、遇到粘包、拆包怎么办?

解决方案1:固定数据大小

解决方案2:自定义请求协议

解决方案3:特殊字符结尾

 四、HTTP如何解决粘包问题的?

4.1、读取请求行/请求头、响应行/响应头

4.2、 怎么读取body数据呢?

4.2.1、 Content-Length 描述

4.2.2、 chunked描述

4.2.3 优/缺点


TCP的粘包和拆包问题往往出现在基于TCP协议的通讯中,比如RPC框架、Netty等。

一、粘包与拆包是什么?

TCP在接受数据的时候,有一个滑动窗口来控制接受数据的大小,这个滑动窗口你就可以理解为一个缓冲区的大小。缓冲区满了就会把数据发送。数据包的大小是不固定的,有时候比缓冲区大有时候小。
如果一次请求发送的数据量比较小,没达到缓冲区大小,TCP则会将多个请求合并为同一个请求进行发送,这就形成了粘包问题;
如果一次请求发送的数据量比较大,超过了缓冲区大小,TCP就会将其拆分为多次发送,这就是拆包,也就是将一个大的包拆分为多个小包进行发送。

 

二、粘包与拆包为什么发生?

1.TCP会发生粘包问题:TCP 是面向连接的传输协议,TCP 传输的数据是以流的形式,而流数据是没有明确的开始结尾边界,所以 TCP 也没办法判断哪一段流属于一个消息;TCP协议是流式协议;所谓流式协议,即协议的内容是像流水一样的字节流,内容与内容之间没有明确的分界标志,需要认为手动地去给这些协议划分边界。
粘包时:发送方每次写入数据 < 接收方套接字(Socket)缓冲区大小。
拆包时:发送方每次写入数据 > 接收方套接字(Socket)缓冲区大小。

2.UDP不会发生粘包问题:UDP具有保护消息边界,在每个UDP包中就有了消息头(UDP长度、源端口、目的端口、校验和)。

粘包拆包问题在数据链路层、网络层以及传输层都有可能发生。日常的网络应用开发大都在传输层进行,由于UDP有消息保护边界,不会发生粘包拆包问题,因此粘包拆包问题只发生在TCP协议中

三、遇到粘包、拆包怎么办?

先用简单的代码来演示一下粘包和拆包问题

package com.cjian.socket.stickBagAndUnpack;import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;/*** @Author: cjian* @Date: 2023/6/5 10:18* @Des:*/
public class Server {// 字节数组的长度private static final int BYTE_LENGTH = 20;public static void main(String[] args) throws IOException {// 创建 Socket 服务器ServerSocket serverSocket = new ServerSocket(8888);// 获取客户端连接Socket clientSocket = serverSocket.accept();// 得到客户端发送的流对象InputStream is = clientSocket.getInputStream();while (true) {// 循环获取客户端发送的信息byte[] bytes = new byte[BYTE_LENGTH];// 读取客户端发送的信息try {int count = is.read(bytes, 0, BYTE_LENGTH);if (count > 0) {// 成功接收到有效消息并打印System.out.println("接收到客户端的信息是:" + new String(bytes));}count = 0;} catch (Exception e) {// ignore}}}
}
package com.cjian.socket.stickBagAndUnpack;import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;/*** @Author: cjian* @Date: 2023/6/5 10:20* @Des:*/
public class Client {public static void main(String[] args) throws IOException {// 创建 Socket 客户端并尝试连接服务器端Socket socket = new Socket("127.0.0.1", 8888);// 发送的消息内容final String message = "Hi,ChenJian.";// 使用输出流发送消息OutputStream os = socket.getOutputStream();// 给服务器端发送 10 次消息for (int i = 0; i < 10; i++) {// 发送消息os.write(message.getBytes());}}
}

通过结果我们可以看出,服务器端有时发生了粘包问题,因为客户端发送了 10 次固定的“Hi,ChenJian.”的消息,正确的结果应该是服务器端也接收到了 10 次固定消息“Hi,ChenJian.”才对,但实际执行结果并非如此,不够长度的还使用了空格字符填充!

对于粘包和拆包问题,常见的解决方案有四种:

1、客户端在发送数据包的时候,每个包都固定长度,比如1024个字节大小,如果客户端发送的数据长度不足1024个字节,则通过补充空格的方式补全到指定长度

2、客户端在每个包的末尾使用固定的分隔符,例如\r\n,如果一个包被拆分了,则等待下一个包发送过来之后找到其中的\r\n,然后对其拆分后的头部部分与前一个包的剩余部分进行合并,这样就得到了一个完整的包;

3、将消息分为头部和消息体,在头部中保存有当前整个消息的长度,只有在读取到足够长度的消息之后才算是读到了一个完整的消息;

4、通过自定义协议进行粘包和拆包的处理。

解决方案1:固定数据大小

package com.cjian.socket.stickBagAndUnpack;import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;/*** @Author: cjian* @Date: 2023/6/5 14:27* @Des:*/
public class Server1 {private static final int BYTE_LENGTH = 1024;  // 字节数组长度(收消息用)public static void main(String[] args) throws IOException {ServerSocket serverSocket = new ServerSocket(8888);// 获取到连接Socket clientSocket = serverSocket.accept();InputStream inputStream = clientSocket.getInputStream();while (true) {byte[] bytes = new byte[BYTE_LENGTH];try {// 读取客户端发送的信息int count = inputStream.read(bytes, 0, BYTE_LENGTH);if (count > 0) {// 接收到消息打印System.out.println("接收到客户端的信息是:" + new String(bytes).trim());}count = 0;} catch (Exception e) {// ignore}}}
}
package com.cjian.socket.stickBagAndUnpack;import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;/*** @Author: cjian* @Date: 2023/6/5 14:28* @Des:*/
public class Client1 {private static final int BYTE_LENGTH = 1024;  // 字节长度public static void main(String[] args) throws IOException {Socket socket = new Socket("127.0.0.1", 8888);final String message = "Hi,ChenJian."; // 发送消息OutputStream outputStream = socket.getOutputStream();// 将数据组装成定长字节数组byte[] bytes = new byte[BYTE_LENGTH];int idx = 0;for (byte b : message.getBytes()) {bytes[idx] = b;idx++;}// 给服务器端发送 10 次消息for (int i = 0; i < 10; i++) {outputStream.write(bytes, 0, BYTE_LENGTH);}}
}

优缺点分析

从以上代码可以看出,虽然这种方式可以解决粘包问题,但这种固定数据大小的传输方式,当数据量比较小时会使用空字符来填充,所以会额外的增加网络传输的负担,因此不是理想的解决方案。

解决方案2:自定义请求协议

这种解决方案的实现思路是将请求的数据封装为两部分:消息头(发送的数据大小)+消息体(发送的具体数据),它的格式如下图所示:

 

定义一个消息封装类:

消息的封装类中提供了两个方法:一个是将消息转换成消息头 + 消息体的方法,另一个是读取消息头的方法,具体实现代码如下

package com.cjian.socket.stickBagAndUnpack.customprotocol;import java.io.IOException;
import java.io.InputStream;
import java.text.NumberFormat;/*** @Author: cjian* @Date: 2023/6/5 14:42* @Des:*/
public class SocketUtils {// 消息头存储的长度(占 8 字节)static final int HEAD_SIZE = 8;/*** 将协议封装为:协议头 + 协议体** @param context 消息体(String 类型)* @return byte[]*/public byte[] toBytes(String context) {// 协议体 byte 数组byte[] bodyByte = context.getBytes();int bodyByteLength = bodyByte.length;// 最终封装对象byte[] result = new byte[HEAD_SIZE + bodyByteLength];// 借助 NumberFormat 将 int 转换为 byte[]NumberFormat numberFormat = NumberFormat.getNumberInstance();numberFormat.setMinimumIntegerDigits(HEAD_SIZE);numberFormat.setGroupingUsed(false);// 协议头 byte 数组byte[] headByte = numberFormat.format(bodyByteLength).getBytes();// 封装协议头System.arraycopy(headByte, 0, result, 0, HEAD_SIZE);// 封装协议体System.arraycopy(bodyByte, 0, result, HEAD_SIZE, bodyByteLength);return result;}/*** 获取消息头的内容(也就是消息体的长度)** @param inputStream* @return*/public int getHeader(InputStream inputStream) throws IOException {int result = 0;byte[] bytes = new byte[HEAD_SIZE];inputStream.read(bytes, 0, HEAD_SIZE);// 得到消息体的字节长度result = Integer.valueOf(new String(bytes));return result;}}
package com.cjian.socket.stickBagAndUnpack.customprotocol;import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;/*** @Author: cjian* @Date: 2023/6/5 15:00* @Des:*/
public class CustomServer {public static void main(String[] args) throws IOException {// 创建 Socket 服务器端ServerSocket serverSocket = new ServerSocket(8888);// 获取客户端连接Socket clientSocket = serverSocket.accept();// 获取客户端发送的消息对象InputStream inputStream = clientSocket.getInputStream();while (true) {// 获取消息头(也就是消息体的长度)try {int bodyLength = SocketUtils.getHeader(inputStream);// 消息体 byte 数组byte[] bodyByte = new byte[bodyLength];// 每次实际读取字节数int readCount = 0;// 消息体赋值下标int bodyIndex = 0;// 循环接收消息头中定义的长度while (bodyIndex < bodyLength &&(readCount = inputStream.read(bodyByte, bodyIndex, bodyLength)) != -1) {bodyIndex += readCount;}bodyIndex = 0;// 成功接收到客户端的消息并打印System.out.println("接收到客户端的信息:" + new String(bodyByte));} catch (IOException ioException) {System.out.println(ioException.getMessage());break;}}}
}
package com.cjian.socket.stickBagAndUnpack.customprotocol;import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Random;/*** @Author: cjian* @Date: 2023/6/5 14:46* @Des:*/
public class CustomClient {public static void main(String[] args) throws IOException {// 启动 Socket 并尝试连接服务器Socket socket = new Socket("127.0.0.1", 8888);// 发送消息合集(随机发送一条消息)final String[] message = {"Hi,Chenjian.", "Hi,LiXi~", "江苏省南京市雨花台区."};// 创建协议封装对象OutputStream outputStream = socket.getOutputStream();// 给服务器端发送 10 次消息for (int i = 0; i < 10; i++) {// 随机发送一条消息String msg = message[new Random().nextInt(message.length)];// 将内容封装为:协议头+协议体byte[] bytes = SocketUtils.toBytes(msg);// 发送消息outputStream.write(bytes, 0, bytes.length);outputStream.flush();}}
}

从上述结果可以看出,消息通讯正常,客户端和服务器端的交互中并没有出现粘包问题。

优缺点分析

此解决方案虽然可以解决粘包问题,但消息的设计和代码的实现复杂度比较高,所以也不是理想的解决方案

解决方案3:特殊字符结尾

以特殊字符结尾就可以知道流的边界了,它的具体实现是:使用 Java 中自带的 BufferedReader 和 BufferedWriter,也就是带缓冲区的输入字符流和输出字符流,通过写入的时候加上 \n 来结尾,读取的时候使用 readLine 按行来读取数据,这样就知道流的边界了,从而解决了粘包的问题。

服务器端实现代码如下:

package com.cjian.socket.stickBagAndUnpack.specialchar;import com.cjian.socket.stickBagAndUnpack.customprotocol.SocketUtils;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;/*** @Author: cjian* @Date: 2023/6/5 15:00* @Des:*/
public class SpecialCharServer {public static void main(String[] args) throws IOException {// 创建 Socket 服务器端ServerSocket serverSocket = new ServerSocket(8888);// 获取客户端连接Socket clientSocket = serverSocket.accept();while (true) {try {// 获取客户端发送的消息流对象BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));while (true) {// 按行读取客户端发送的消息String msg = bufferedReader.readLine();if (msg != null) {// 成功接收到客户端的消息并打印System.out.println("接收到客户端的信息:" + msg);}}} catch (IOException ioException) {System.out.println(ioException.getMessage());break;}}}
}

客户端代码:

package com.cjian.socket.stickBagAndUnpack.specialchar;import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.Socket;/*** @Author: cjian* @Date: 2023/6/5 15:36* @Des:*/
public class SpecialCharClient {public static void main(String[] args) throws IOException {// 启动 Socket 并尝试连接服务器Socket socket = new Socket("127.0.0.1", 8888);final String message = "Hi,ChenJian."; // 发送消息BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));// 给服务器端发送 10 次消息for (int i = 0; i < 10; i++) {// 注意:结尾的 \n 不能省略,它表示按行写入bufferedWriter.write(message + "\n");// 刷新缓冲区(此步骤不能省略)bufferedWriter.flush();}}
}

优缺点分析

以特殊符号作为粘包的解决方案的最大优点是实现简单,但存在一定的局限性,比如当一条消息中间如果出现了结束符就会造成半包的问题,所以如果是复杂的字符串要对内容进行编码和解码处理,这样才能保证结束符的正确性。

 四、HTTP如何解决粘包问题的?

http请求报文格式
1)请求行:以\r\n结束;
2)请求头:以\r\n结束;
3)\r\n;
3)数据;

http响应报文格式
1)响应行:以\r\n结束;
2)响应头:以\r\n结束;
3)\r\n;
4)数据;

4.1、读取请求行/请求头、响应行/响应头

1、遇到第一个\r\n表示读取请求行或响应行结束;
2、遇到\r\n\r\n表示读取请求头或响应头结束;


4.2、 怎么读取body数据呢?

  • HTTP协议通常使用Content-Length来标识body的长度。在服务器端,需要先申请对应长度的buffer,然后再赋值。

 

  • 如果需要一边生产数据一边发送数据,就需要使用"Transfer-Encoding: chunked" 来代替Content-Length,也就是对数据进行分块传输。

4.2.1、 Content-Length 描述

  1. http server接收数据时,发现header中有Content-Length属性,则读取Content-Length的值,确定需要读取body的长度。
  2. http server发送数据时,根据需要发送byte的长度,在header中增加Content-Length项,其中value为byte的长度,然后将byte数据当做body发送到客户端。

4.2.2、 chunked描述

  1. http server接收数据时,发现header中有Transfer-Encoding: chunked,则会按照chunked协议分批读取数据。
  2. http server发送数据时,如果需要分批发送到客户端,则需要在header中加上Transfer-Encoding:chunked,然后按照chunked协议分批发送数据。

chunked协议具体如下图:

 

1、主要包含三部分: chunk,last-chunk和trailer。如果分多次发送,则chunk有多份。

2、 chunk主要包含大小和数据,大小表示这个这个chunk包的大小,使用16进制标示。其中chunk之间的分隔符为CRLF。

3、通过last-chunk来标识chunk发送完成。一般读取到last-chunk(内容为0)的时候,代表chunk发送完成。

4、trailer表示增加header等额外信息,一般情况下header是空。通过CRLF来标识整个chunked数据发送完成。

4.2.3 优/缺点

优点

1、假如body的长度是10K,对于Content-Length则需要申请10K连续的buffer,而对于Transfer-Encoding:chunked可以申请1k的空间,然后循环使用10次。节省了内存空间的开销。

2、如果内容的长度不可知,则可使用chunked方式能有效的解决Content-Length的问题

3、http服务器压缩可以采用分块压缩,而不是整个块压缩。分块压缩可以一边进行压缩,一般发送数据,来加快数据的传输时间。

缺点

1、chunked协议解析比较复杂。

2、在http转发的场景下(比如nginx)难以处理,比如如何对分块数据进行转发。

相关文章:

聊聊TCP协议的粘包、拆包以及http是如何解决的?

目录 一、粘包与拆包是什么&#xff1f; 二、粘包与拆包为什么发生&#xff1f; 三、遇到粘包、拆包怎么办&#xff1f; 解决方案1&#xff1a;固定数据大小 解决方案2&#xff1a;自定义请求协议 解决方案3&#xff1a;特殊字符结尾 四、HTTP如何解决粘包问题的&#xf…...

一百二十、Kettle——用kettle把Hive数据同步到ClickHouse

一、目标 用kettle把hive数据同步到clickhouse&#xff0c;简单运行、直接全量导入数据 工具版本&#xff1a;kettle&#xff1a;8.2 Hive:3.1.2 ClickHouse21.9.5.16 二、前提 &#xff08;一&#xff09;kettle连上hive &#xff08;二&#xff09;kettle连上cli…...

PyTorch 提示和技巧:从张量到神经网络

张量和梯度 我们将深入探讨使用 PyTorch 构建自己的神经网络必须了解的 2 个基本概念&#xff1a;张量和梯度。 张量 张量是 PyTorch 中的中央数据单元。它们是类似于数组的数据结构&#xff0c;在功能和属性方面与 Numpy 数组非常相似。它们之间最重要的区别是 PyTorch 张量…...

第五期:字符串的一些有意思的操作

文章目录 1. 替换空格2. 字符串的左旋转3. 答案代码3.1 替换空格3.2 字符串的左旋转 PS&#xff1a;每道题解题方法不唯一&#xff0c;欢迎讨论&#xff01;每道题后都有解析帮助你分析做题&#xff0c;答案在最下面&#xff0c;关注博主每天持续更新。 1. 替换空格 题目描述 请…...

使用Anaconda3结合vscode来实现django项目的建立(绝好的介绍)20230608

问题:如何使用Anaconda3结合vscode来实现django项目的建立&#xff1f; 回答: 知识背景 Anaconda3的安装包默认会安装最新版本的Python解释器。如果您想在安装时指定Python解释器的版本&#xff0c;您需要下载对应版本的Anaconda3。例如&#xff0c;如果您想使用Python 3.7&…...

【软件测试】软件测试的基本概念和开发模型

1. 前言 在进行软件测试的学习之前,我们要了解软件测试一些基本概念. 这些基本概念将帮助我们更加明确工作的目标以及软件测试到底要做什么. 2. 软件测试的基本概念 软件测试的基本概念有3个,分别是需求,测试用例和BUG. 2.1 需求 这里的需求还可以分为 用户需求和软件需求,用户…...

接口测试 —— 接口测试定义

1、接口测试概念 &#xff08;重点&#xff09; 接口测试是测试系统组件间接口的一种测试&#xff0c;它界于单元测试与系统测试中间。 接口测试主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点。 测试的重点是要检查数据的交换&#xff0c;传递和控制管理过…...

2015 年一月联考逻辑真题

2015 年一月联考逻辑真题 真题&#xff08;2015-26&#xff09; 26.晴朗的夜晚我们可以看到满天星斗&#xff0c;其中有些是自身发光的恒星&#xff0c;有些是自身不发光但可以反射附近恒星光的行星。恒星尽管遥远&#xff0c;但是有些可以被现有的光学望远镜“看到”。和恒星不…...

基于GD32的定时器不完全详解--定时、级联

SysTick 定时器 SysTick 是一个 24 位的倒计数定时器&#xff0c;当计到 0 时&#xff0c;将从 RELOAD 寄存器中自动重装载定时初值。只要不把它在 SysTick 控制及状态寄存器中的使能位清除&#xff0c; 就永不停息。 该定时器的介绍在MCU的手册中一般不会介绍&#xff0c;因为…...

Clion开发STM32之ESP8266系列(四)

前言 上一篇: Clion开发STM32之ESP8266系列(三) 本篇主要内容 实现esp8266需要实现的函数串口3中断函数的自定义&#xff08;这里没有使用HAL提供的&#xff09;封装esp8266服务端的代码和测试 正文 主要修改部分 核心配置头文件(添加一些宏定义) sys_core_conf.h文件中…...

降本增效,StarRocks 在同程旅行的实践

作者&#xff1a;周涛 同程旅行数据中心大数据研发工程师 同程旅行是中国在线旅游行业的创新者和市场领导者。作为一家一站式平台&#xff0c;同程旅行致力于满足用户旅游需求&#xff0c;秉持 "让旅行更简单、更快乐" 的使命&#xff0c;主要通过包括微信小程序、AP…...

INTP型人格适合选择哪些专业?

INTP人格内倾理性人格、具有强烈的好奇心、创造性和独立性的特点。他们善于独立思考和寻找问题的本质&#xff0c;并对抽象的想法和理论感兴趣。 INTP人格的人具有很强的逻辑思维和分析能力&#xff0c;他们的思维方式非常系统&#xff0c;追求完美和准确。因此他们适合选择需…...

【LeetCode热题100】打卡第16天:组合总和

文章目录 组合总和⛅前言&#x1f512;题目&#x1f511;题解 组合总和 ⛅前言 大家好&#xff0c;我是知识汲取者&#xff0c;欢迎来到我的LeetCode热题100刷题专栏&#xff01; 精选 100 道力扣&#xff08;LeetCode&#xff09;上最热门的题目&#xff0c;适合初识算法与数…...

tinkerCAD案例:1.戒子环

基本戒指 在本课中&#xff0c;您将学习使用圆柱形状制作戒指。来吧&#xff01; 说明 将圆柱体拖动到工作平面上并使其成为孔。 圆柱体应缩放以适合其制造手指。 在本例中&#xff0c;我们将使用 17mm 作为直径&#xff0c;但请根据您的需要随意调整尺寸。 将“圆柱”形状拖…...

RPC接口测试技术-Tcp 协议的接口测试

【摘要】 首先明确 Tcp 的概念&#xff0c;针对 Tcp 协议进行接口测试&#xff0c;是指基于 Tcp 协议的上层协议比如 Http &#xff0c;串口&#xff0c;网口&#xff0c; Socket 等。这些协议与 Http 测试方法类似&#xff08;具体查看接口自动化测试章节&#xff09;&#xf…...

MyBatis Plus基本用法-SpringBoot框架

依赖 使用 Mybatis Plus 框架时&#xff0c;需要添加以下依赖&#xff1a; <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>latest-version</version> </dependency…...

指针--指针变量的定义和初始化

存放变量的地址需要一种特殊类型的变量&#xff0c;这种特殊的数据类型就是指针&#xff08;Pointer&#xff09;。 具有指针类型的变量&#xff0c;称为指针变量&#xff0c;它时专门用于存储变量的地址值和变量。 其定义形式如下&#xff1a; 类型关键字 * 指针变量名&#x…...

Web基本概念

一、前言 World Wide Web的简称&#xff0c;是一个由许多互相链接的超文本组成的系统&#xff0c;通过互联网访问 &#xff08;为用户提供信息&#xff09; 静态网页 仅适用于不能经常更改内容的网页&#xff1b; 动态网页 网络编程技术创建的页面&#xff1b;通过在传统的静态…...

Niagara—— Texture Sample 与 Particle Subuv 区别

目录 一&#xff0c;Texture Sample 二&#xff0c;Particle Subuv 一&#xff0c;Texture Sample 此节点是最基本的采样节点&#xff0c;依据UV坐标来采样Texture&#xff1b; MipValueMode&#xff0c;设置采样的Mipmap Level&#xff1b; None&#xff0c;根据当前Texture…...

如何在食品行业运用IPD?

食品是我国重要的民生产业之一&#xff0c;是保障和满足人民群众不断增长消费需求的重要支撑。食品指各种供人食用或者饮用的成品和原料以及按照传统既是食品又是药品的物品&#xff0c;包括加工食品&#xff0c;半成品和未加工食品&#xff0c;不包括烟草或只作药品用的物质。…...

Vim 调用外部命令学习笔记

Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...

Leetcode 3576. Transform Array to All Equal Elements

Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接&#xff1a;3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到&#xf…...

在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:

在 HarmonyOS 应用开发中&#xff0c;手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力&#xff0c;既支持点击、长按、拖拽等基础单一手势的精细控制&#xff0c;也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档&#xff0c…...

Go 语言接口详解

Go 语言接口详解 核心概念 接口定义 在 Go 语言中&#xff0c;接口是一种抽象类型&#xff0c;它定义了一组方法的集合&#xff1a; // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的&#xff1a; // 矩形结构体…...

Linux简单的操作

ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...

什么是库存周转?如何用进销存系统提高库存周转率?

你可能听说过这样一句话&#xff1a; “利润不是赚出来的&#xff0c;是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业&#xff0c;很多企业看着销售不错&#xff0c;账上却没钱、利润也不见了&#xff0c;一翻库存才发现&#xff1a; 一堆卖不动的旧货…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具

文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

剑指offer20_链表中环的入口节点

链表中环的入口节点 给定一个链表&#xff0c;若其中包含环&#xff0c;则输出环的入口节点。 若其中不包含环&#xff0c;则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...

代理篇12|深入理解 Vite中的Proxy接口代理配置

在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...

招商蛇口 | 执笔CID,启幕低密生活新境

作为中国城市生长的力量&#xff0c;招商蛇口以“美好生活承载者”为使命&#xff0c;深耕全球111座城市&#xff0c;以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子&#xff0c;招商蛇口始终与城市发展同频共振&#xff0c;以建筑诠释对土地与生活的…...