从零开始学习Netty - 学习笔记 -Netty入门【自定义编码器解码器】

自定义编码器解码器

通过一个简单案例,我们自己去实现一个编码器 和解码器

实现思路

  1. 编码器(Encoder)实现
    • 在编码器中,实现了 ByteToMessageCodec<Message> 类,并覆盖了 encode() 方法来处理消息的编码。
    • encode() 方法中,首先写入协议的标识、版本号、序列化算法类型、消息类型、请求序号等信息。然后,将消息对象转换为字节数组,并写入到输出的 ByteBuf 中。
  2. 解码器(Decoder)实现
    • 创建了一个继承自 ByteToMessageCodec<Message> 的解码器,并实现了 decode() 方法来处理消息的解码。
    • decode() 方法中,从输入的 ByteBuf 中读取协议的标识、版本号、序列化算法类型、消息类型、请求序号等信息。然后,根据序列化算法类型,你反序列化字节数组为消息对象,并将解码后的消息放入到解码器的输出列表中。
  3. 消息类(Message)设计
    • 定义了一个抽象的 Message 类作为所有消息的基类,并规定了消息类型常量和消息类型与消息类的映射关系。
    • 每个具体的消息类都继承自 Message 类,并实现了 getMessageType() 方法以及其他必要的属性和方法。
  4. 测试程序
    • 编写测试程序,使用 EmbeddedChannel 来测试编码和解码器的功能。
    • 在测试程序中,你写入一个登录请求消息,并通过编码器进行编码,然后通过解码器进行解码,最后验证解码后的消息是否正确。

登录请求消息

/**
 * 登录请求消息
 * 这里我们不再使用继承的方式,而是使用组合的方式 
 * 这样做的好处是,我们可以更加灵活的控制消息的格式
 */
@Data
@ToString(callSuper = true)
public class LoginRequestMessage extends Message {
    private String username;
    private String password;
    private String nickname;

    public LoginRequestMessage() {
    }

    public LoginRequestMessage(String username, String password, String nickname) {
        this.username = username;
        this.password = password;
        this.nickname = nickname;
    }

    @Override
    public int getMessageType() {
        return LoginRequestMessage;
    }
}

登录响应消息

/**
 * 登录响应消息
 * 
 */
@Data
@ToString(callSuper = true)
public class LoginResponseMessage extends AbstractResponseMessage {
    @Override
    public int getMessageType() {
        return LoginResponseMessage;
    }
}

抽象Message消息类

/**
 * 这里的 Message 是一个抽象类,它是所有消息的基类,所有的消息都需要继承自它。
 * 这里定义了一些消息类型的常量,以及一个静态的 Map 对象,用来存储消息类型和消息类的映射关系。
 * 这样就可以通过消息类型来获取消息类,这样就可以根据消息类型来创建对应的消息对象。
 * 这里还定义了一个抽象方法 getMessageType,用来获取消息类型。
 * 这样我们就可以通过消息对象来获取消息类型,然后根据消息类型来获取消息类,这样就可以根据消息类型来创建对应的消息对象。
 */
@Data
public abstract class Message implements Serializable {

    public static Class<?> getMessageClass(int messageType) {
        return messageClasses.get(messageType);
    }

    private int sequenceId;

    private int messageType;

    public abstract int getMessageType();

    // 登录请求消息
    public static final int LoginRequestMessage = 0;
    // 登录响应消息
    public static final int LoginResponseMessage = 1;
    
    private static final Map<Integer, Class<?>> messageClasses = new HashMap<>();

    static {
        messageClasses.put(LoginRequestMessage, LoginRequestMessage.class);
        messageClasses.put(LoginResponseMessage, LoginResponseMessage.class);
   
    }
}

**继承ByteToMessageCodec 用来处理编解码 **

public class MessageCodec extends ByteToMessageCodec<Message> {
	private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());


	/**
	 * 编码
	 *
	 * @param channelHandlerContext
	 * @param message
	 * @param out
	 * @throws Exception
	 */
	@Override
	protected void encode(ChannelHandlerContext channelHandlerContext, Message message, ByteBuf out) throws Exception {
		// 拿到ByteBuf 往里面写入数据
		// 1.魔数,就相当于一个标识,用来标识这是一个自定义的协议(4个字节的魔数)
		out.writeBytes(new byte[]{1, 2, 3, 4});
		// 2.版本号(1个字节)
		out.writeByte(1);
		// 3.序列化算法(先使用JDK作为序列化算法) 使用1个字节来表示序列化算法 jdk 0 json 1
		out.writeByte(0);
		// 4.指令类型(1个字节)
		out.writeByte(message.getMessageType());
		// 5.请求序号(4个字节)
		out.writeInt(message.getSequenceId());
		// 写个无意义的字节,是为了对其,填充字节,填充到8的倍数
		out.writeByte(0xff);
		// 6.获取内容的字节数组
		// 将Java对象转为字节属猪
		// 创建一个字节数组输出流
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		// 创建一个对象输出流
		ObjectOutputStream oos = new ObjectOutputStream(bos);
		// 将对象写入字节数组输出流
		oos.writeObject(message);
		// 获取字节数据
		byte[] bytes = bos.toByteArray();
		// 7.获取字节长度
		out.writeInt(bytes.length);
		// 8.写入内容
		out.writeBytes(bytes);

	}


	/**
	 * 解码
	 *
	 * @param channelHandlerContext
	 * @param in
	 * @param list
	 * @throws Exception
	 */
	@Override
	protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf in, List<Object> list) throws Exception {
		// 1.魔数,就相当于一个标识,用来标识这是一个自定义的协议(4个字节的魔数)
		int magicNum = in.readInt();
		// 2.版本号(1个字节)
		byte version = in.readByte();
		// 3.序列化算法(先使用JDK作为序列化算法) 使用1个字节来表示序列化算法 jdk 0 json 1
		byte serializeType = in.readByte();
		// 4.指令类型(1个字节)
		byte messageType = in.readByte();
		// 5.请求序号(4个字节)
		int sequenceId = in.readInt();
		// 6.读取无意义的字节
		in.readByte();
		// 7.获取内容的字节数组
		int length = in.readInt();
		byte[] bytes = new byte[length];
		in.readBytes(bytes, 0, length);
		// 8.反序列化
		if (serializeType == 0) {
			// jdk bytes数据 用流包装了一下
			ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
			Message message = (Message) ois.readObject();
			// 解析出来的消息为
			logger.error("{}.{},{},{},{},{}", magicNum, version, serializeType, messageType, sequenceId, length);
			logger.error("解析出来的消息为:{}", message);
			// 将解析出来的消息放入到list中,交给下一个handler处理 不然下一个handler无法处理
			list.add(message);
		} else if (serializeType == 1) {
			// json
		}
	}
}

测试程序的编码

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

	public static void main(String[] args) {
		// 创建一个EmbeddedChannel
		EmbeddedChannel channel = new EmbeddedChannel(
				new LoggingHandler(),
				new MessageCodec());
		// 写入一个对象 看看能不能编码
		LoginRequestMessage message = new LoginRequestMessage("zhangsan", "123", "张三");
		channel.writeOutbound(message);
	}
}

通过打印消息信息,我们可以看到我们自定编码器,编码后的数据。

image-20240305223157308

测试程序的解码

public class EmbeddedChannelTest {
	private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

	public static void main(String[] args) throws Exception {
		// 创建一个EmbeddedChannel
		// 为了防止半包 还是需要配 LengthFieldBasedFrameDecoder  否则一旦序列化或者反序列化的字节数组过大,就会出现半包问题
		EmbeddedChannel channel = new EmbeddedChannel(
				// 参数1:最大长度 参数2:长度域的偏移量 参数3:长度域的长度 参数4:长度域的补偿 参数5:长度域的调整
				new LengthFieldBasedFrameDecoder(1024, 12, 4, 0, 0),
				new LoggingHandler(),
				new MessageCodec());
		// 写入一个对象 看看能不能编码
		LoginRequestMessage message = new LoginRequestMessage("zhangsan", "123", "张三");
		channel.writeOutbound(message);



		// 测试解码
		ByteBuf buf = ByteBufAllocator.DEFAULT.buffer();
		// 此时我们还需要再调用一次encode方法,因为我们在writeOutbound的时候,会调用encode方法
		new MessageCodec().encode(null, message, buf);

		// 入站
		channel.writeInbound(buf);
	}
}

image-20240305224338961

image-20240305224426233

  • 通过自定义编码器和解码器,我们可以更灵活地控制消息的格式和传输方式,以满足特定的通信需求。
  • 在编码器中,负责将消息对象编码成字节数组,并添加协议标识、版本号、序列化算法类型等头部信息。
  • 在解码器中,负责从字节数组中解析出消息对象,并根据协议头部信息进行反序列化。
  • 使用消息类的继承和映射关系,可以根据消息类型动态地创建对应的消息对象,从而实现更加灵活的消息处理。
  • 通过测试程序验证编码器和解码器的功能,可以保证通信协议的正确性和稳定性。

@Sharable

@Sharable 是 Netty 中的一个注解,用于标识一个 ChannelHandler 是否可以被多个 Channel 共享。在 Netty 中,每个 Channel 都有一个对应的 ChannelPipeline,而 ChannelPipeline 中包含了一系列的 ChannelHandler,用于处理进入和离开 Channel 的事件。

当一个 ChannelHandler 被标记为 @Sharable 时,表示该 Handler 是线程安全的,可以被多个 Channel 共享使用。这意味着同一个实例可以被多个 ChannelPipeline 所共享,从而节省了资源并且减少了对象的创建开销。

使用 @Sharable 的关键是确保编写的 ChannelHandler 是无状态的或者线程安全的。这意味着它不依赖于 ChannelHandler 实例的状态,而是依赖于传入的事件的内容。

以下是 @Sharable 注解的一些特点和注意事项:

  1. 线程安全性: 标记为 @Sharable 的 ChannelHandler 应该是线程安全的,因为它可能被多个 Channel 共享并在多个线程上同时调用。
  2. 状态无关性: @Sharable 的 ChannelHandler 应该是状态无关的,即不应该依赖于 ChannelHandler 实例的状态。它应该根据传入的事件内容进行处理。
  3. 共享性: 由于标记为 @Sharable 的 ChannelHandler 可以被多个 Channel 共享,因此它们应该是轻量级的,并且不应该包含 Channel 相关的状态或信息。
  4. 生命周期管理: 在使用 @Sharable 的 ChannelHandler 时,需要注意其生命周期管理。因为它们可能会被多个 Channel 共享,所以需要确保适当地处理资源的初始化和释放。
  5. 避免副作用: @Sharable 的 ChannelHandler 应该尽量避免副作用,即不要修改外部状态或进行与业务无关的操作。

通过在 MessageCodec 类上添加 @Sharable 注解,确保了该编解码器在多个 EventLoopGroup 中可以被安全地共享使用。

MessageCodec 类

  • MessageCodec 类继承自 MessageToMessageCodec<ByteBuf, Message>,这是 Netty 提供的用于编解码处理的抽象类。
  • 使用 @Sharable 注解标记 MessageCodec,表示该编解码器是线程安全的,并且可以在多个 Channel 之间共享使用。
  • encode() 方法中,将 Java 对象编码为字节数组,并将结果添加到输出列表中。
  • decode() 方法中,从字节缓冲区中读取数据并进行解码,最后将解码后的消息对象添加到输出列表中。
/**
 * 继承ByteToMessageCodec 用来处理编解码
 * 1.编码:将Java对象转为字节数组
 * 2.解码:将字节数组转为Java对象
 * 加上注解 @Sharable 必须和LengthFieldBasedFrameDecoder一起使用,确保接收的数据是完整的,否则会出现半包问题。
 * @author 13723
 * @version 1.0
 * 2024/3/5 21:45
 */
@ChannelHandler.Sharable
public class MessageCodec extends MessageToMessageCodec<ByteBuf,Message> {
	private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());


	/**
	 * 编码
	 * @param channelHandlerContext
	 * @param message
	 * @param list
	 * @throws Exception
	 */
	@Override
	protected void encode(ChannelHandlerContext channelHandlerContext, Message message, List<Object> list) throws Exception {
		ByteBuf out = channelHandlerContext.alloc().buffer();
		// 拿到ByteBuf 往里面写入数据
		// 1.魔数,就相当于一个标识,用来标识这是一个自定义的协议(4个字节的魔数)
		out.writeBytes(new byte[]{1, 2, 3, 4});
		// 2.版本号(1个字节)
		out.writeByte(1);
		// 3.序列化算法(先使用JDK作为序列化算法) 使用1个字节来表示序列化算法 jdk 0 json 1
		out.writeByte(0);
		// 4.指令类型(1个字节)
		out.writeByte(message.getMessageType());
		// 5.请求序号(4个字节)
		out.writeInt(message.getSequenceId());
		// 写个无意义的字节,是为了对其,填充字节,填充到8的倍数
		out.writeByte(0xff);
		// 6.获取内容的字节数组
		// 将Java对象转为字节属猪
		// 创建一个字节数组输出流
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		// 创建一个对象输出流
		ObjectOutputStream oos = new ObjectOutputStream(bos);
		// 将对象写入字节数组输出流
		oos.writeObject(message);
		// 获取字节数据
		byte[] bytes = bos.toByteArray();
		// 7.获取字节长度
		out.writeInt(bytes.length);
		// 8.写入内容
		out.writeBytes(bytes);

		list.add(out);
	}


	/**
	 * 解码 我们能保证完整的数据包,所以我们不需要考虑半包问题,因为我们能保证上一个处理器是黏包半包处理器
	 * @param channelHandlerContext
	 * @param in
	 * @param list
	 * @throws Exception
	 */
	@Override
	protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf in, List<Object> list) throws Exception {
		// 1.魔数,就相当于一个标识,用来标识这是一个自定义的协议(4个字节的魔数)
		int magicNum = in.readInt();
		// 2.版本号(1个字节)
		byte version = in.readByte();
		// 3.序列化算法(先使用JDK作为序列化算法) 使用1个字节来表示序列化算法 jdk 0 json 1
		byte serializeType = in.readByte();
		// 4.指令类型(1个字节)
		byte messageType = in.readByte();
		// 5.请求序号(4个字节)
		int sequenceId = in.readInt();
		// 6.读取无意义的字节
		in.readByte();
		// 7.获取内容的字节数组
		int length = in.readInt();
		byte[] bytes = new byte[length];
		in.readBytes(bytes, 0, length);
		// 8.反序列化
		if (serializeType == 0) {
			// jdk bytes数据 用流包装了一下
			ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
			Message message = (Message) ois.readObject();
			// 解析出来的消息为
			logger.error("{}.{},{},{},{},{}", magicNum, version, serializeType, messageType, sequenceId, length);
			logger.error("解析出来的消息为:{}", message);
			// 将解析出来的消息放入到list中,交给下一个handler处理 不然下一个handler无法处理
			list.add(message);
		} else if (serializeType == 1) {
			// json
		}
	}
}

测试

  • EmbeddedChannelTest 类是用于测试自定义编解码器的示例。

  • main 方法中,创建了一个 Netty 服务器并绑定到本地的 8080 端口。

  • ChannelInitializer 中,配置了 LengthFieldBasedFrameDecoderLoggingHandler 和自定义的 MessageCodec

  • 这样配置后,服务器将能够正确地解析传入的消息,并将其交给 MessageCodec 处理。

  • 如果 MessageCodec 类没有使用 @Sharable 注解标记,并且试图将其添加到多个 ChannelPipeline 中,就会抛出异常。这是因为 Netty 要求每个 ChannelHandler 默认情况下是不可共享的,除非显式地使用 @Sharable 注解进行标记。

    如果你尝试将一个不带 @Sharable 注解的 ChannelHandler 添加到多个 ChannelPipeline 中,Netty 将会在运行时抛出 ChannelPipelineException 异常,提示你不能重复地添加同一个 ChannelHandler 实例到不同的 ChannelPipeline 中。

  • 如果 MessageCodec 类中的 encodedecode 方法是无状态的,那么它们也是线程安全的,即使 MessageCodec 没有被标记为 @Sharable

    无状态意味着 encodedecode 方法不依赖于实例的状态,并且对于相同的输入始终产生相同的输出。在这种情况下,即使 MessageCodec 实例被多个 ChannelPipeline 共享,也不会引发线程安全问题。

    因此,如果你的 MessageCodec 类中的 encodedecode 方法是无状态的,它们仍然可以被安全地共享在多个 ChannelPipeline 中,即使没有使用 @Sharable 注解。

public class EmbeddedChannelTest {
	private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

	public static void main(String[] args) throws Exception {
		NioEventLoopGroup boos = new NioEventLoopGroup();
		NioEventLoopGroup worker = new NioEventLoopGroup();
		LoggingHandler loggingHandler = new LoggingHandler(LogLevel.DEBUG);
		// 自定义编解码器 这里是无状态的,因为没有成员变量 所有线程安全
		MessageCodec messageCodec = new MessageCodec();
		try {
			ServerBootstrap serverBootstrap = new ServerBootstrap();
			serverBootstrap.group(boos, worker)
					.channel(NioServerSocketChannel.class)
					.handler(loggingHandler)
					.childHandler(new ChannelInitializer<SocketChannel>() {
						@Override
						protected void initChannel(SocketChannel channel) throws Exception {
							channel.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 12, 4, 0, 0));
							channel.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
							channel.pipeline().addLast(messageCodec);
						}
					});
					// 测试编解码器
			ChannelFuture sync = serverBootstrap.bind(8080).sync();
			sync.channel().closeFuture().sync();
		}catch (Exception e){
			logger.error("serverBootstrap error",e);
			e.printStackTrace();
		}finally {
			boos.shutdownGracefully();
			worker.shutdownGracefully();
		}
	}
}

// 此时再次启动就不会报错了

image-20240305233047746

image-20240305233106489

image-20240305233315292

image-20240305233403394

  • EmbeddedChannelTest22 类是另一个测试示例,使用 EmbeddedChannel 来测试编解码器的功能。
  • main 方法中,创建了一个 EmbeddedChannel,并添加了 LengthFieldBasedFrameDecoderMessageCodecLoggingHandler
  • 然后,写入一个登录请求消息,并使用 readOutbound() 方法读取处理后的消息。
public class EmbeddedChannelTest22 {
	private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

	public static void main(String[] args) throws Exception {
		// 测试编解码器
		EmbeddedChannel channel = new EmbeddedChannel(
				new LengthFieldBasedFrameDecoder(1024, 12, 4, 0, 0),
				new MessageCodec(),
				new LoggingHandler(LogLevel.DEBUG));
		// 准备数据
		LoginRequestMessage message = new LoginRequestMessage("zhangsan", "123", "张三");
		// 写数据
		channel.writeOutbound(message);
		// 读数据
		channel.readOutbound();

	}
}

image-20240305231732292

消息也是能够正常读取的

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

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

相关文章

【HarmonyOS】Dev Eco Studio4.0安装教程

目录 下载安装开发工具配置 下载 Dev Eco Studio4.0下载连接→https://developer.huawei.com/consumer/cn/next/deveco-studio/ 安装 点击Next 选择安装目录。点击Next 勾选创建桌面快捷方式和环境变量&#xff0c;点击Next 点击Install&#xff0c;安装 等待安装 选…

Node.js最准确历史版本下载(以下载Node.js16.17.1版本为例)

先进入官网:Node.js https://nodejs.org/en 括号中LTS代表稳定版本. 嫌其他冗余博客帖子多&#xff0c;找起来费眼睛,可以到/release下载:Node.js,在blog后面加/release https://nodejs.org/en/blog/release/ 点击next翻页,跟上面同样的步骤

做分析用什么工具

做分析用什么工具 导读 数据分析是数据辅助决策的最后一公里&#xff0c;是最终的数据可视化展示与探索分析的部分&#xff0c;选择使用最适合的数据展示方式&#xff0c;可以帮助分析人员大大提升分析效率。 问题&#xff1a; ● 纠结选择哪个工具 ● 纠结从哪里学起&#x…

Java异常的介绍与处理

一、异常与错误(Error)的区别&#xff1a; 异常&#xff08;Exception&#xff09;和错误&#xff08;Error&#xff09;都是指程序执行过程中的问题&#xff0c;但它们之间存在一些重要的区别。 异常&#xff08;Exception&#xff09;&#xff1a; 异常表示程序在执行期间可能…

Memcached介绍和详解

Memcached介绍 Memcached是一个高性能的分布式内存对象缓存系统&#xff0c;它通过在内存中缓存数据来减少数据库负载&#xff0c;加快动态Web应用程序的响应速度。 以下是Memcached的一些关键特点和作用&#xff1a; 分布式缓存&#xff1a;Memcached是分布式的&#xff0c;不…

Android高级工程师面试实战,三幅图给你弄懂EventBus核心原理

阿里技术一面-35min 自我介绍 Android 有没有遇到OOM问题(有遇到内存泄漏问题)Handler机制ThreadLocalActivity启动到加载View过程View绘制过程LinearLayout (wrap_content) & TextView (match_parent) 最终结果???OKHttp(1. 为什么选择它&#xff1f; 2. 性能了解不…

阿里云服务器几核几G怎么选择?带宽多少合适?

阿里云服务器配置怎么选择&#xff1f;CPU内存、公网带宽和系统盘怎么选择&#xff1f;个人开发者或中小企业选择轻量应用服务器、ECS经济型e实例&#xff0c;企业用户选择ECS通用算力型u1云服务器、ECS计算型c7、通用型g7云服务器&#xff0c;阿里云服务器网aliyunfuwuqi.com整…

Java 学习和实践笔记(27):Object类的基本特性、toString方法以及IDEA的部分快捷键介绍

Object类基本特性&#xff1a; 1.Object类是所有类的父类&#xff0c;所有的Java对象都拥有Object类的属性和方法&#xff08;注意拥有并不等于可以直接使用&#xff09; 2.如果在类的声明中未使用extends&#xff0c;则默认继承Object类。 toString方法是Object类里定义的一个…

Word中的文档网格线与行距问题

在使用Word编辑文档时&#xff0c;经常会发生以下动图展示的这种情况&#xff1a; 上面的动图里&#xff0c;将文字大小放大到某个字号时&#xff0c;单倍行距的间距突然增加很多。造成这种情况的原因是文档中定义了网格线&#xff0c;并且设置了对齐到网格线。如果取消文档中…

[清爽快捷] Ubuntu上多个版本的cuda切换

做到真正的一行代码搞定&#xff0c;只需要修改对应软链接&#xff0c;就可以轻松实现快捷切换cuda 查看已安装的cuda版本有哪些 一般如果我们都是使用默认位置安装cuda的话&#xff0c;那么其安装路径都是/usr/local。如果要查看该目录下已经安装有哪些版本的cuda&#xff0c…

Git分布式管理-头歌实验本地版本库

一、本地版本库创建 任务描述 本地Git操作三部曲是“修改-添加-提交”&#xff0c;即先要在本地仓库进行添加、删除或编辑等修改&#xff0c;然后将本地所做的修改添加至暂存区。添加至暂存区的这些本地修改&#xff0c;并未提交到本地仓库&#xff0c;需要执行提交命令才能将暂…

SPSS26安装后无法启动,提示:应用程序的并行配置不正确

以下的解决方法供参考&#xff1a; 1、安装jdk并配置 2、 找到安装目录\Statistics\26\VC9下的vcredist_x64.exe&#xff0c;打开安装并选择“repair”&#xff0c;安装完成后重启&#xff0c;一般可以成功。 3、若还不行&#xff0c;安装较新的C运行库&#xff0c;再试试。 …

勒索病毒攻击新玩法,先盗数据再勒索

2019年是勒索病毒团伙针对企业进行勒索攻击爆发的一年&#xff0c;全球多个国家的政府组织机构、企事业单位都成为了勒索病毒团伙攻击的目标&#xff0c;勒索病毒也成为了网络安全最大的网络安全威胁&#xff0c;新的勒索病毒不断涌现&#xff0c;旧的勒索病毒不断变种&#xf…

1.1 深度学习和神经网络

首先要说的是&#xff1a;深度学习的内容&#xff0c;真的不难。你要坚持下去。 神经网络 这就是一个神经网络。里面的白色圆圈就是神经元。神经元是其中最小的单位。 神经网络 单层神经网络&#xff1a; 感知机 &#xff08;双层神经网络&#xff09; 全连接层&#xff1a; …

DHCP自动获取IP地址实验(华为)

思科设备参考&#xff1a;DHCP自动获取IP地址实验&#xff08;思科&#xff09; 一&#xff0c;实验目的 路由器搭载DHCP&#xff0c;让PC通过DHCP自动获取IP地址 二&#xff0c;不划分vlan--全局地址池 实验拓扑 配置命令 Router <Huawei>system-view [Huawei]ip po…

十三、类的继承、访问级别

类的继承与访问控制 类的继承 使用sealed修饰的类&#xff0c;是私有类&#xff0c;不能作为基类使用C#中一个类&#xff0c;只能有一个父类&#xff0c;但是可以实现多个接口子类的访问级别不能超过父类的访问级别 using System; using System.Collections.Generic; using S…

HDFS简介与部署以及故障排错(超简单)

文章目录 一、HDFS介绍1、简介2、结构模型3、文件写入过程4、文件读取过程5、文件块的存放6、存储空间管理机制6.1 文件删除和恢复删除6.2 复制因子配置6.3 文件命名空间6.4 数据复制机制 二、环境搭建&#xff08;单机版&#xff09;1、修改主机名2、配置ssh免密登录3、Hadoop…

HTML表单标签,web前端开发新技术

1、ant-design的使用总结及常用组件和他们的基本用法? ant-design为React&#xff0c;Angular和Vue都提供了组件&#xff0c;同时为PC和移动端提供了常用的基础组件。ant-design提供的demo非常的丰富并且样式能够基本的覆盖开发需求。antd的Demo因为是多人编写的&#xff0c;…

(vue)适合后台管理系统开发的前端框架

(vue)适合后台管理系统开发的前端框架 1、D2admin 开源地址&#xff1a;https://github.com/d2-projects/d2-admin 文档地址&#xff1a;https://d2.pub/zh/doc/d2-admin/ 效果预览&#xff1a;https://d2.pub/d2-admin/preview/#/index 开源协议&#xff1a;MIT 2、vue-el…

自动化测试基础——Pytest框架之YAML详解以及Parametrize数据驱动

文章目录 一、YAML详解1.YAML作用2.YAML语法结构3.YAML数据类型3.1.对象3.2.数组3.3.标量 4.YAML的引用5.YAML类型转换 二、YAML的读写与清空1.YAML的读2.YAML的写3.YAML的清空 三、pytest的parametrize简单数据驱动四、pytest的parametrize结合yaml实现数据驱动五、解决pytest…