从零开始学习Netty - 学习笔记 -Netty入门【协议设计和解析】

2.协议设计和解析

协议
在计算机中,协议是指一组规则和约定,用于在不同的计算机系统之间进行通信和数据交换。计算机协议定义了数据传输的格式、顺序、错误检测和纠正方法,以及参与通信的各个实体的角色和责任。计算机协议可以在各种不同的层次上操作,包括物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。

以下是一些常见的计算机协议:

  1. 传输层协议:例如TCP (Transmission Control Protocol) 和UDP (User Datagram Protocol),用于在网络上可靠地传输数据。
  2. 网络层协议:例如IP (Internet Protocol),负责在网络上寻址和路由数据包。
  3. 应用层协议:例如HTTP (Hypertext Transfer Protocol)、FTP (File Transfer Protocol)、SMTP (Simple Mail Transfer Protocol) 等,用于支持特定的应用程序和服务。
  4. 数据链路层协议:例如Ethernet、PPP (Point-to-Point Protocol) 等,用于在物理网络之间传输数据帧。

2.1.redis协议

Redis 使用一种简单而有效的文本协议进行通信,这种协议被称为 RESP(REdis Serialization Protocol)。RESP 是一种二进制安全的协议,它可以将多种类型的数据结构序列化为字节流进行传输,并且允许客户端和服务器之间进行高效的通信。

下面是 RESP 协议的一些基本规则:

  1. 简单字符串(Simple Strings):以 “+” 开头,后面跟着字符串内容和回车换行符 “\r\n”。例如:+OK\r\n 表示一个成功的响应。
  2. 错误消息(Errors):以 “-” 开头,后面跟着错误消息内容和回车换行符 “\r\n”。例如:-ERR unknown command 'foobar'\r\n 表示一个错误的响应。
  3. 整数(Integers):以 “:” 开头,后面跟着整数内容和回车换行符 “\r\n”。例如::1000\r\n 表示整数 1000。
  4. 批量字符串(Bulk Strings):以 “$” 开头,后面跟着字符串的长度(以字节为单位)、字符串内容和回车换行符 “\r\n”。例如:$6\r\nfoobar\r\n 表示一个长度为 6 的字符串 “foobar”。
  5. 数组(Arrays):以 “*” 开头,后面跟着数组的长度和数组的元素,每个元素都可以是任意 RESP 类型。例如:*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n 表示一个包含两个元素的数组,分别是字符串 “foo” 和字符串 “bar”。

在实际的通信中,客户端发送命令给 Redis 服务器,并等待服务器的响应。客户端发送的命令遵循 RESP 协议的格式,而服务器返回的响应也是 RESP 格式的。

这种简单而灵活的 RESP 协议使得 Redis 能够高效地处理各种数据类型和命令,并在性能和易用性之间找到了平衡。

代码

package com.hrfan.java_se_base.netty.protocol;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LoggingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.invoke.MethodHandles;
import java.nio.charset.Charset;

/**
 * @author 13723
 * @version 1.0
 * 2024/3/3 0:03
 */
public class TestRedisProtocol {
	private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

	public static void main(String[] args) {
		// 测试redis协议
		NioEventLoopGroup worker = new NioEventLoopGroup();
		try {
			Bootstrap bootstrap = new Bootstrap();
			bootstrap.channel(NioSocketChannel.class);
			bootstrap.group(worker);
			bootstrap.handler(new ChannelInitializer<SocketChannel>() {
				@Override
				protected void initChannel(SocketChannel channel) throws Exception {
					channel.pipeline().addLast(new LoggingHandler());
					channel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
						/**
						 * 连接一旦建立就发送命令
						 * @param ctx
						 * @throws Exception
						 */

						@Override
						public void channelActive(ChannelHandlerContext ctx) throws Exception {
							// 如果redis 有密码 需要先发送一个auth命令
							//
							// 发送 AUTH 命令进行认证
							// String authCommand = "*2\r\n$4\r\nauth\r\n$5\r\n12345\r\n";
							// ByteBuf authBuffer = ctx.alloc().buffer();
							// authBuffer.writeBytes(authCommand.getBytes());
							// ctx.writeAndFlush(authBuffer);
							// 发送一个连接建立的命令
							// redis 协议是一种文本协议 以 \r\n 作为结束符 以$开头的是长度  以*开头的是数组
							// 例如 *3\r\n$3\r\nset\r\n$4\r\nname\r\n$8\r\nhrfan\r\n
							// 表示一个数组 有三个元素  第一个元素是set 第二个元素是name 第三个元素是hrfan
							// 也就是执行 set name hrfan
							String command = "*3\r\n$3\r\nset\r\n$4\r\nname\r\n$5\r\nhrfan\r\n";
							ByteBuf buffer = ctx.alloc().buffer();
							buffer.writeBytes(command.getBytes());
							ctx.writeAndFlush(buffer);
						}

						@Override
						public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
							super.channelRead(ctx, msg);
							// redis 接收到结果  肯定会返回信息 +OK\r\n
							ByteBuf byteBuf = (ByteBuf) msg;
							String string = byteBuf.toString(Charset.defaultCharset());
							logger.info("redis 返回的结果是:{}", string);
						}
					});
				}
			});
			// 和redis建立连接
			ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6379).sync();
			channelFuture.channel().closeFuture().sync();
		} catch (InterruptedException e) {
			logger.error("client error !",e);
		}finally {
			worker.shutdownGracefully();
		}
	}
}

image-20240303011539551

image-20240303011603698

2.2.HTTP协议

HTTP(Hypertext Transfer Protocol,超文本传输协议)是一种用于传输超媒体文档(例如 HTML)的应用层协议,是互联网上数据传输的基础。

HTTP的特点:

  1. 无连接
    • HTTP 协议是无连接的,即每个请求都是独立的,服务器处理完请求后即断开连接,因此每个请求需要单独建立连接和断开连接,无法复用连接,导致了额外的开销。
  2. 无状态
    • HTTP 协议是无状态的,即服务器不会保存客户端的请求信息,每个请求之间没有关联,服务器无法知道当前请求与之前的请求是否相关。
    • 为了实现状态保持,引入了 Cookie 和 Session 机制。
  3. 简单快速
    • HTTP 协议基于请求-响应模型,简单易懂,通信速度较快。
    • 由于 HTTP 协议的简单性,使得它被广泛应用于 Web 数据传输。
  4. 灵活性
    • HTTP 协议允许传输任意类型的数据对象,不限于文本数据,也可以传输图片、视频、音频等多媒体数据。
  5. 无安全性
    • HTTP 协议是明文传输的,数据传输过程中不对数据进行加密处理,容易被窃听、篡改或伪造,因此不适合传输敏感数据。

HTTP请求/响应的基本结构:

  1. 请求结构
    • 请求行:包括请求方法(GET、POST 等)、请求 URI 和 HTTP 版本号。
    • 请求头部:包括客户端信息、请求资源信息、支持的压缩方法等。
    • 请求正文:传输请求相关的数据。
  2. 响应结构
    • 状态行:包括 HTTP 版本号、状态码和状态描述。
    • 响应头部:包括服务器信息、响应时间、响应内容类型等。
    • 响应正文:包含响应的实际数据。

HTTP的方法(请求方式):

  1. GET:用于请求指定的资源。
  2. POST:用于提交数据,常用于提交表单数据。
  3. PUT:用于上传指定的 URI 表示的内容。
  4. DELETE:用于删除指定的资源。
  5. HEAD:与 GET 类似,但服务器只返回响应头部,不返回实际内容。
  6. OPTIONS:用于请求目标资源所支持的通信选项。
  7. TRACE:用于测试目的,回显服务器收到的请求,主要用于诊断。

HTTP状态码:

  1. 1xx(信息):请求已接收,继续处理。
  2. 2xx(成功):请求已成功被服务器接收、理解、并接受。
  3. 3xx(重定向):需要客户端采取进一步的操作才能完成请求。
  4. 4xx(客户端错误):请求包含语法错误或无法完成请求。
  5. 5xx(服务器错误):服务器在处理请求的过程中发生了错误。

HTTP持久连接:

HTTP/1.1 引入了持久连接(Persistent Connection)机制,使得可以在同一连接上发送和接收多个 HTTP 请求和响应,减少了连接建立和断开的开销,提高了性能。

/**
 * @author 13723
 * @version 1.0
 * 2024/3/3 0:03
 */
public class TestHttpProtocol {
	private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

	public static void main(String[] args) {
		// 测试HTTP协议
		NioEventLoopGroup boss = new NioEventLoopGroup();
		NioEventLoopGroup worker = new NioEventLoopGroup();

		try {
			ServerBootstrap bootstrap = new ServerBootstrap();
			bootstrap.channel(NioServerSocketChannel.class);
			bootstrap.group(boss,worker);
			bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
				@Override
				protected void initChannel(SocketChannel channel) throws Exception {
					channel.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
					channel.pipeline().addLast(new HttpServerCodec());
					// 对编解码的请求结果进行处理
					channel.pipeline().addLast(new ChannelInboundHandlerAdapter(){
						@Override
						public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
							super.channelRead(ctx, msg);
							// 此时打开浏览器 输入localhost:8080 会看到请求的信息
							logger.error("获取的信息:{}",msg);
						}
					});
				}
			});
			// 建立和http之间的连接
			ChannelFuture channelFuture = bootstrap.bind(9999).sync();
			channelFuture.channel().closeFuture().sync();
		} catch (InterruptedException e) {
			logger.error("client error !",e);
		}finally {
			worker.shutdownGracefully();
			boss.shutdownGracefully();
		}
	}
}

image-20240304212730612

但是这个信息是一个http请求的信息 但是http请求 会分为请求头和请求体
会默认发送两次请求 一次是请求头 一次是请求体
所以我们需要对请求头和请求体进行处理
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    super.channelRead(ctx, msg);
    // 此时打开浏览器 输入localhost:8080 会看到请求的信息
    logger.error("获取的信息:{}",msg);
    // 但是这个信息是一个http请求的信息 但是http请求 会分为请求头和请求体
    // 会默认发送两次请求 一次是请求头 一次是请求体
    // 所以我们需要对请求头和请求体进行处理
    if (msg instanceof HttpRequest){
        HttpRequest request = (HttpRequest) msg;
        logger.error("请求头:{}",request.headers());
    }else if (msg instanceof HttpContent){
        HttpContent content = (HttpContent) msg;
        ByteBuf buf = content.content();
        logger.error("请求体:{}",buf.toString(Charset.defaultCharset()));
    }
}

image-20240304213137183

还可以使用 添加指定处理器 处理特定的内容

SimpleChannelInboundHandler 它可根据消息的类型进行选择处理,例如我们只关心HttpRequest类型的消息,Netty会自动帮你进行转换 你不需要进行类型转换

// 对请求头和请求体进行处理 我们还可以使用SimpleChannelInboundHandler
// 它可根据消息的类型进行选择处理,例如我们只关心HttpRequest类型的消息
// 他会自动帮你进行转换 你不需要进行类型转换
channel.pipeline().addLast(new SimpleChannelInboundHandler<HttpRequest>() {
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpRequest httpRequest) throws Exception {
        logger.error("请求信息:{}",httpRequest);
        // 向浏览器返回响应
        // netty提供一个响应对象
        // 符合http协议的响应对象 第一个参数 时http协议的版本 第二个参数是响应的状态码
        DefaultFullHttpResponse response = new DefaultFullHttpResponse(httpRequest.protocolVersion(), HttpResponseStatus.OK);

        // 向浏览器写入一些内容
        byte[] bytes = "<h1>hello world</h1>".getBytes();
        response.content().writeBytes(bytes);
        // 设置响应头 否则浏览器会一直等待 告知箱体
        response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH,bytes.length);
        // 设置响应头的类型
        response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/html;charset=utf-8");
        // 写入响应
        channelHandlerContext.writeAndFlush(response);
    }
});

image-20240304214047954

image-20240304214058259

2.3.自定义协议

自定义协议要素

  • 魔改 (Magic Number)
    • 用于第一时间判定是否是有效数据包,通常是一个固定的字节序列或者数字,用来标识该数据包是符合自定义协议的。
    • 例如,可以是一个特定的字节序列,如 0x7E 0x7E。
  • 版本号 (Protocol Version)
    • 用于支持协议的升级,可以在协议中包含一个字段来表示协议的版本号。
    • 这样可以在协议升级时识别和处理不同版本的协议。
  • 序列化算法 (Serialization Algorithm)
    • 用于消息正文的序列化和反序列化,可以支持多种序列化算法,如 JSON、Protobuf、Hessian、JDK 自带的序列化等。
    • 这样可以根据需求选择最适合的序列化算法来进行数据的编码和解码。
  • 指令类型 (Instruction Type)
    • 用来表示消息的类型,与业务相关,包括登录、注册、单聊、群聊等操作。
    • 可以使用一个字段来标识不同的指令类型,以便在接收方根据指令类型进行相应的业务处理。
  • 请求序号 (Request Sequence Number)
    • 用于实现双工通信和提供异步能力,每个请求都有一个唯一的序号。
    • 接收方在处理请求后,可以通过该序号将响应与请求进行关联。
  • 消息正文长度和消息正文
    • 消息正文长度字段用于表示消息正文的长度,以便在解析消息时可以正确地读取到消息的内容。
    • 消息正文则是实际的数据内容,根据指令类型和业务需求可以是不同格式的数据,例如文本、二进制、结构化数据等。

定义一个简单的自定义协议,协议由两部分组成:消息类型和消息内容。消息类型用一个字节表示,消息内容是一个字符串。

消息格式:消息类型字节消息类型字节 消息内容长度字节消息内容长度字节 消息内容消息内容

  1. MessageType (消息类型):一个字节,0表示心跳消息,1表示业务消息。
  2. MessageContentLength (消息内容长度):4个字节,表示消息内容的长度。
  3. MessageContent (消息内容):消息内容的字节数组。

编码器

1.自定义消息信息的枚举类型
  • MessageType 枚举定义了两种消息类型:心跳消息和业务消息,分别用 0 和 1 表示。
public enum MessageType {
    HEARTBEAT((byte) 0),
    BUSINESS((byte) 1);

    private final byte value;

    MessageType(byte value) {
        this.value = value;
    }

    public byte getValue() {
        return value;
    }

    public static MessageType valueOf(byte value) {
        for (MessageType type : values()) {
            if (type.value == value) {
                return type;
            }
        }
        throw new IllegalArgumentException("Invalid MessageType value: " + value);
    }
}
2.自定义协议消息类
  • MyProtocolMessage 类表示一个自定义协议消息,包括消息类型和消息内容。
public class MyProtocolMessage {
    private MessageType type;
    private String content;

    public MyProtocolMessage(MessageType type, String content) {
        this.type = type;
        this.content = content;
    }

    public MessageType getType() {
        return type;
    }

    public String getContent() {
        return content;
    }
}
3.自定义协议的编码器
  • 继承自 Netty 的 MessageToByteEncoder 类,负责将 MyProtocolMessage 编码成字节流。
  • 将消息类型、消息内容长度和消息内容依次写入 ByteBuf 中。
public class MyProtocolEncoder extends MessageToByteEncoder<MyProtocolMessage> {

    @Override
    protected void encode(ChannelHandlerContext ctx, MyProtocolMessage msg, ByteBuf out) throws Exception {
        // 写入消息类型
        out.writeByte(msg.getType().getValue());

        // 获取消息内容的字节数组
        byte[] contentBytes = msg.getContent().getBytes(StandardCharsets.UTF_8);

        // 写入消息内容长度
        out.writeInt(contentBytes.length);

        // 写入消息内容
        out.writeBytes(contentBytes);
    }
}
4.自定义协议的解码器
  • 继承自 Netty 的 ByteToMessageDecoder 类,负责将字节流解码成 MyProtocolMessage 对象。
  • 读取字节流中的消息类型和消息内容长度,然后读取对应长度的字节流作为消息内容。
  • 构造 MyProtocolMessage 对象并加入到解码器的输出列表中。
public class MyProtocolDecoder extends ByteToMessageDecoder {

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        // 至少需要5个字节来解码
        if (in.readableBytes() < 5) {
            return;
        }

        // 标记当前读取位置
        in.markReaderIndex();

        // 读取消息类型
        byte messageType = in.readByte();

        // 读取消息内容长度
        int contentLength = in.readInt();

        // 如果可读字节数小于消息内容长度,说明消息不完整,重置读取位置
        if (in.readableBytes() < contentLength) {
            in.resetReaderIndex();
            return;
        }

        // 读取消息内容
        byte[] contentBytes = new byte[contentLength];
        in.readBytes(contentBytes);
        String content = new String(contentBytes, StandardCharsets.UTF_8);

        // 构造消息对象
        MyProtocolMessage message = new MyProtocolMessage(MessageType.valueOf(messageType), content);
        out.add(message);
    }
}
5.测试
  • 使用 Netty 的 EmbeddedChannel 类模拟了一个通道来进行测试。
  • 测试了编码器和解码器的正确性,包括编码后解码得到的消息与原消息是否相同。
public class MyProtocolTest {
    private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
    public static void main(String[] args) {
        // 创建一个嵌入式通道,并添加编码器和解码器
        // 指定日志级别为DEBUG,可以看到编码后的字节
        EmbeddedChannel channel = new EmbeddedChannel(new MyProtocolEncoder(), new MyProtocolDecoder(),new LoggingHandler(LogLevel.DEBUG));


        // 构造一个业务消息
        MyProtocolMessage message = new MyProtocolMessage(MessageType.BUSINESS, "Hello, Netty!");

        // 写入消息到通道
        channel.writeOutbound(message);

        // 读取通道中的字节
        ByteBuf encoded = channel.readOutbound();

        // 打印编码后的字节
        logger.error("编码后的字节:Encoded Message: {}",encoded);

        // 写入编码后的字节到通道
        channel.writeInbound(encoded.retain());

        // 读取通道中的解码后的消息
        MyProtocolMessage decodedMessage = channel.readInbound();

        // 打印解码后的消息
        logger.error("解码后的字节:Decoded Message: {}",decodedMessage.getContent());

        // 关闭通道
        channel.finish();
    }
}

image-20240304221744643

自定义协议的优点:

  1. 灵活性
    • 自定义协议可以根据实际业务需求进行设计,灵活地定义消息格式和通信规则,使得通信双方能够更好地适应特定的业务场景。
  2. 性能优化
    • 自定义协议可以针对特定的业务需求进行优化,可以选择合适的数据格式和编码方式,减少通信数据量,提高通信效率。
  3. 安全性
    • 自定义协议可以设计加密和校验机制,确保通信数据的安全性和完整性,防止数据被篡改或窃取。
  4. 版本控制
    • 自定义协议可以包含版本号,便于协议的升级和兼容,能够保证通信双方在协议更新后仍能正常通信。
  5. 易于调试和维护
    • 自定义协议通常具有明确的结构和语义,易于调试和排查问题,同时也方便日后的维护和扩展。

自定义协议的缺点和注意事项:

  1. 复杂性增加
    • 自定义协议的设计和实现需要对网络通信有深入的理解,不当的设计可能导致协议过于复杂,增加开发和维护的难度。
  2. 兼容性问题
    • 协议的升级和演化可能会导致与旧版本的不兼容,需要谨慎处理版本控制和协议演化的问题,以确保新旧版本的兼容性。
  3. 安全风险
    • 自定义协议的安全性需要开发者自行考虑和实现,不恰当的安全机制可能会导致数据泄露和安全漏洞。
  4. 性能折衷
    • 自定义协议的设计需要兼顾性能和灵活性,有时需要在性能和灵活性之间进行权衡和折衷,选择合适的方案。
  5. 协议文档和规范
    • 自定义协议需要有清晰的文档和规范,以确保通信双方都能正确理解和实现协议,避免因为误解或者实现不一致导致通信失败。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【PCL】 (十六)点云距离图可视化

&#xff08;十六&#xff09;点云距离图可视化 以下代码实现点云及其对应距离图的可视化。 数据样例&#xff1a;sphere100.pcd range_image_visualization.cpp #include <iostream>#include <pcl/range_image/range_image.h> #include <pcl/io/pcd_io.h&g…

C++11常用知识分享(二)【可变参数模板 || lambda表达式 || 包装器】

目录 一&#xff0c;可变参数模板 1. 递归方法展开参数包 2. 逗号表达式展开参数包 3&#xff0c;可变参数模板优势 二&#xff0c;lambda表达式 1. lambda表达式语法 2. 注意点 三&#xff0c;包装器 1. bind(了解) 嗨&#xff01;收到一张超美的风景图&#xff0c;希…

C向C++的一个过渡

思维导图 输入输出&#xff0c;以及基础头文件 在c语言中我们常用scanf("%d",&n);和printf("%d\n",n);来输出一些变量和常量&#xff0c;在C中我们可以用cin;和cout;来表示输入输出。 在C语言中输入输出有头文件&#xff0c;在C也有头文件&#xff0…

#WEB前端(CCS选择器)

1.实验&#xff1a;CCS选择器 2.IDE&#xff1a;VSCODE 3.记录&#xff1a; 子代选择器、后代选择器、相邻兄弟选择器、类选择器、伪元素选择器&#xff08;鼠标悬停&#xff09;、ID选择器、调用选择器&#xff08;全选&#xff09; 4.代码&#xff1a; <!DOCTYPE html…

Vue.js 实用技巧:深入理解 Vue.set 方法

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

JMeter常用函数整理

"_csvRead"函数 csvRead函数是从外部读取参数&#xff0c;csvRead函数可以从一个文件中读取多个参数。 下面具体讲一下如何使用csvread函数&#xff1a; 1.新建一个csv或者text文件&#xff0c;里面保存要读取的参数&#xff0c;每个参数间用逗号相隔。每行表示每一组…

MATLAB:Image Processing Toolbox工具箱入门实战

目录 1.基本图像导入、处理和导出 2.实战项目一&#xff1a;利用imfindcircles()函数检测和测量图像中的圆形目标 1.基本图像导入、处理和导出 Basic Image Import, Processing, and Export- MATLAB & SimulinkThis example shows how to read an image into the worksp…

BUUCTF---[极客大挑战 2019]Http1

1.题目描述&#xff0c;在地址框输入下面的网址 2.来到页面&#xff0c;ctrlu查看源码&#xff0c;仔细观察会看到一个.php的跳转页面 3.点进去页面提示It doesnt come from https://Sycsecret.buuoj.cn 4.页面提示它不是来源于这个网址&#xff0c;我们需要用bp抓包对数据进行…

从0到1全流程使用 segment-anything

从0到1全流程使用 segment-anything 一、安装 anaconda 一、下载 anaconda 二、以管理员身份运行安装 1、勾选 Just Me 2、统一安装路径(后续 python 等包也安装至此目录) 3、勾选 add to path 然后安装即可。 三、修改 Anaconda 默认路径及默认缓存路径 Anaconda 默认下…

神经网络3-时间卷积神经网络

在深度学习的知识宝库中&#xff0c;卷积神经网络&#xff08;CNN&#xff09;广泛应用于视觉&#xff0c;视频等二维或者多维的图像领域。卷积网络具有深度&#xff0c;可并行等多种优良特性&#xff0c;那么这种技术是否可以应用于解单维度的时间序列问题呢&#xff1f;本文介…

基于Springboot的助农管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的助农管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&…

UTONMOS元宇宙游戏发展趋势是什么?

UTONMOS元宇宙游戏的发展趋势包括以下几个方面&#xff1a; 更加真实的体验&#xff1a;随着技术的进步&#xff0c;UTONMOS元宇宙游戏将提供更加逼真的视觉、听觉和触觉体验&#xff0c;让玩家更加身临其境。 社交互动&#xff1a;UTONMOS元宇宙游戏将越来越注重社交互动&am…

Linux系统宝塔面板搭建Typecho博客并实现公网访问本地网站【内网穿透】

文章目录 前言1. 安装环境2. 下载Typecho3. 创建站点4. 访问Typecho5. 安装cpolar6. 远程访问Typecho7. 固定远程访问地址8. 配置typecho 前言 Typecho是由type和echo两个词合成的&#xff0c;来自于开发团队的头脑风暴。Typecho基于PHP5开发&#xff0c;支持多种数据库&#…

Windows服务器:通过nginx反向代理配置HTTPS、安装SSL证书

先看下效果&#xff1a; 原来的是 http&#xff0c;配置好后 https 也能用了&#xff0c;并且显示为安全链接。 首先需要 SSL证书 。 SSL 证书是跟域名绑定的&#xff0c;还有有效期。 windows 下双击可以查看相关信息。 下载的证书是分 Apache、IIS、Tomcat 和 Nginx 的。 我…

9.10目标和(LC494-M)

算法&#xff1a; 加法的绝对值的集合left 减法的绝对值的集合right nums集合的总和sum 这里的left和right都是绝对值&#xff1a; leftrightsum → rightsum-left left-righttarget → left-(sum-left) target → left (target sum)/2 &#xff0c;target …

最新AI系统ChatGPT网站H5系统源码,支持Midjourney绘画

一、前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧。已支持GPT…

python封装,继承,复写详解

目录 1.封装 2.继承 复写和使用父类成员 1.封装 class phone:__voltage 0.5def __keepsinglecore(self):print("单核运行")def callby5g(self):if self.__voltage > 1:print("5g通话开启")else:self.__keepsinglecore()print("不能开启5g通…

xshell安装java/jdk

1.下载jdk wget https://download.java.net/java/GA/jdk11/13/GPL/openjdk-11.0.1_linux-x64_bin.tar.gz 2.解压jdk安装包 tar -zxvf openjdk-11.0.1_linux-x64_bin.tar.gz 其中第三步 编辑 ~/.bashrc 或 ~/.bash_profile 文件 打开vim文本编辑器 vim ~/.bash_profile export …

TT-100K数据集

TT-100K数据集 TT100K数据集是由清华大学和腾讯联合实验室整理并公布的一个大型交通标志数据集。已整理好由xml格式和txt格式。共6105张图片。 有偿分享。可以加我qq&#xff1a;2638351996。注明来意&#xff01;&#xff01;&#xff01;&#xff01;

深入理解Python递归:注意事项、示例及应用场景

文章目录 一、递归的注意事项二、Python代码示例三、使用场景及代码运行结果四、递归的其他应用场景其他示例 五、总结 递归是编程中的一种强大的技术&#xff0c;它允许函数调用自身来解决问题。在Python中&#xff0c;递归被广泛应用&#xff0c;尤其是在处理数据结构&#x…