Netty测试客户端

package com.coremain;

import com.coremain.handler.ServerListenerHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.util.CharsetUtil; import java.util.Scanner; /**
* @description: 测试
* @author: GuoTong
* @createTime: 2023-05-14 16:46
* @since JDK 1.8 OR 11
**/
public class NettyClientTest { public static void main(String[] args) throws InterruptedException {
// 客户端的线程池
EventLoopGroup workerGroup = new NioEventLoopGroup(8); try {
// 创建Netty客户端端的启动对象
Bootstrap bootstrap = new Bootstrap();
// 使用链式编程来配置参数
bootstrap.group(workerGroup)
.channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//对workerGroup的SocketChannel设置处理器
ChannelPipeline pipeline = ch.pipeline();
// 对于通道加入解码器
pipeline.addLast("decoder", new StringDecoder()); // 对于通道加入加码器
pipeline.addLast("encoder", new StringDecoder()); // 加入事件回调处理器
pipeline.addLast(new ServerListenerHandler());
}
});
System.out.println("基于Netty的客户端接入启动完成....");
ChannelFuture cf = bootstrap.connect("127.0.0.1", 18023).sync();
// 获取连接通道
Channel channel = cf.channel();
System.out.println("+++++++" + channel.localAddress() + "=======");
// 客户端输入扫描器
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
String next = scanner.next();
// 发送到服务端
channel.writeAndFlush(Unpooled.buffer().writeBytes(next.getBytes(CharsetUtil.UTF_8)));
} // 通过sync方法同步等待通道关闭处理完毕,这里会阻塞等待通道关闭完成,内部调用的是Object的wait()方法
cf.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
}
}
}

Netty的Server启动类

package com.coremain.netty;

import com.coremain.handler.NettyServerHTTPHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; /**
* @description: Netty服务启动器
* netty服务端会跟随一起启动。 同时,在springboot关闭前,会先销毁netty服务。
* @author: GuoTong
* @createTime: 2023-05-14 15:13
* @since JDK 1.8 OR 11
**/ @Component
public class NettyServerHTTPRunning { // log4j2的AsyncLogger本身的逻辑采用了缓冲区思想,使用的是disruptor框架来实现一个环形无锁队列。
private static final Logger log = LoggerFactory.getLogger(NettyServerHTTPRunning.class); /**
* 主线程组
*/
private NioEventLoopGroup bossGroup = new NioEventLoopGroup(1); /**
* 工作线程组
*/
private NioEventLoopGroup workerGroup = new NioEventLoopGroup(10); /**
* (http)主要服务端口
*/
@Value("${iot.port1:18024}")
private int iot1; /**
* (http)备用服务端口
*/
@Value("${iot.port2:18025}")
private int iot2; /**
* 启动 netty 服务
*/
@PostConstruct
public void startServer() throws InterruptedException {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
// 三次握手中的A、B队列总和最大值(第二次握手加入A, 第三次握手从A移动到B, accept 后从B取出)
.option(ChannelOption.SO_BACKLOG, 1024)
// 解决端口占用问题, 可以共用服务器端口(即使该端口已被其他端口占用)
.option(ChannelOption.SO_REUSEADDR, true)
// 接收消息缓冲区大小
.option(ChannelOption.SO_RCVBUF, 2048)
// 发送消息缓冲区大小
.option(ChannelOption.SO_SNDBUF, 2048)
// 用于启用或关于Nagle算法。如果要求高实时性,有数据发送时就马上发送,就将该选项设置为true关闭Nagle算法;
// 如果要减少发送次数减少网络交互,就设置为false等累积一定大小后再发送
.option(ChannelOption.TCP_NODELAY, true)
// 用于检测长时间没有数据传输的连接状态,当设置该选项以后,如果在两小时内没有数据的通信时,TCP会自动发送一个活动探测数据报文
.option(ChannelOption.SO_KEEPALIVE, true)
// 当用户调用close()方法的时候,函数返回,在可能的情况下,尽量发送数据,不一定保证全部发送成功
// 使用SO_LINGER可以阻塞close()的调用时间,直到数据完全发送
.option(ChannelOption.SO_LINGER, 2000)
.childHandler(new NettyServerHTTPHandler());
ChannelFuture service1 = serverBootstrap.bind(iot1).sync();
ChannelFuture service2 = serverBootstrap.bind(iot2).sync();
if (service1.isSuccess()) {
log.info("服务1启动成功, port: {}", iot1);
}
if (service2.isSuccess()) {
log.info("服务2启动成功, port: {}", iot2);
} } /**
* 销毁
*/
@PreDestroy
public void destroy() {
bossGroup.shutdownGracefully().syncUninterruptibly();
workerGroup.shutdownGracefully().syncUninterruptibly();
log.info("关闭 Netty 成功");
}
}

Netty的服务端核心类:ServerBootstrap

package com.coremain.config;

import com.coremain.handler.NettyServerHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; /**
* @description: 配置NettyServer
* @author: GuoTong
* @createTime: 2023-05-14 14:54
* @since JDK 1.8 OR 11
**/
@Configuration
@EnableConfigurationProperties
public class NettyConfig { private final NettyProperties nettyProperties; public NettyConfig(NettyProperties nettyProperties) {
this.nettyProperties = nettyProperties;
} /**
* boss线程池-进行客户端连接
*
* @return
*/
@Bean
public NioEventLoopGroup boosGroup() {
return new NioEventLoopGroup(nettyProperties.getBoss());
} /**
* worker线程池-进行业务处理
*
* @return
*/
@Bean
public NioEventLoopGroup workerGroup() {
return new NioEventLoopGroup(nettyProperties.getWorker());
} /**
* 服务端启动器,监听客户端连接
*
* @return
*/
@Bean
public ServerBootstrap serverBootstrap() {
ServerBootstrap serverBootstrap = new ServerBootstrap()
// 指定使用的线程组
.group(boosGroup(), workerGroup())
// 指定使用的通道
.channel(NioServerSocketChannel.class)
// 三次握手中的A、B队列总和最大值(第二次握手加入A, 第三次握手从A移动到B, accept 后从B取出)
.option(ChannelOption.SO_BACKLOG, 1024)
// 指定连接超时时间
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, nettyProperties.getTimeout())
// 支持长连接
.option(ChannelOption.SO_KEEPALIVE, true)
// 接收消息缓冲区大小
.option(ChannelOption.SO_RCVBUF, 2048)
// 发送消息缓冲区大小
.option(ChannelOption.SO_SNDBUF, 2048)
// 指定worker处理器
.childHandler(new NettyServerHandler());
return serverBootstrap;
}
}

Netty的通道处理器 ChannelInitializer

package com.coremain.handler;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import org.springframework.stereotype.Component; /**
* @description: Netty服务端回调处理
* @ChannelHandler.Sharable。这个注解的作用是标注一个Handler实例可以被多个通道安全地共享。
* @author: GuoTong
* @createTime: 2023-05-14 14:57
* @since JDK 1.8 OR 11
**/
@ChannelHandler.Sharable
@Component
public class NettyServerHandler extends ChannelInitializer<SocketChannel> { @Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// 数据分割符
String delimiterStr = "##@##";
ByteBuf delimiter = Unpooled.copiedBuffer(delimiterStr.getBytes());
ChannelPipeline pipeline = socketChannel.pipeline();
// 使用自定义处理拆包/沾包,并且每次查找的最大长度为1024字节
pipeline.addLast(new DelimiterBasedFrameDecoder(1024, delimiter));
// 将上一步解码后的数据转码为Message实例
pipeline.addLast(new MessageUTF8DecodeHandler());
// 对发送客户端的数据进行编码,并添加数据分隔符
pipeline.addLast(new MessageUTF8EncodeHandler(delimiterStr));
// 对数据进行最终处理
pipeline.addLast(new ServerListenerHandler());
}
}

Netty回调处理器SimpleChannelInboundHandler

package com.coremain.handler;

import com.coremain.handler.bean.MessageEnum;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.SocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component; import java.time.LocalDateTime; /**
* @description: 数据处理器,针对不同类型数据分类处理 在处理不同接收数据时使用了枚举类型
* @ChannelHandler.Sharable。这个注解的作用是标注一个Handler实例可以被多个通道安全地共享。
* @author: GuoTong
* @createTime: 2023-05-14 15:07
* @since JDK 1.8 OR 11
**/
@ChannelHandler.Sharable
@Component
public class ServerListenerHandler extends SimpleChannelInboundHandler<MessageStrUTF8> { private static final Logger log = LoggerFactory.getLogger(ServerListenerHandler.class); /**
* 当客户端主动链接服务端的链接后,这个通道就是活跃的了。也就是客户端与服务端建立了通信通道并且可以传输数据
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
SocketChannel channel = (SocketChannel) ctx.channel();
String currentData = LocalDateTime.now().format(CommonUtilHandler.dateTimeFormatter);
//通知客户端链接建立成功
String str = "通知客户端链接建立成功" + " " + currentData + " " + channel.localAddress().getHostString() + "\r\n";
ctx.writeAndFlush(str);
} /**
* 设备接入连接时处理
*
* @param ctx
*/
@Override
public void handlerAdded(ChannelHandlerContext ctx) {
log.info("有新的连接:[{}]", ctx.channel().id().asLongText());
} /**
* 数据处理
*
* @param ctx
* @param msg
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, MessageStrUTF8 msg) {
// 获取消息实例中的消息体
String content = msg.getContent();
// 对不同消息类型进行处理
MessageEnum type = MessageEnum.getStructureEnum(msg);
String currentData = LocalDateTime.now().format(CommonUtilHandler.dateTimeFormatter);
switch (type) {
case CONNECT:
// TODO 心跳消息处理
case STATE:
// TODO 设备状态
default:
log.info(currentData + type.content + " 消息内容" + content);
}
} /**
* 设备下线处理
*
* @param ctx
*/
@Override
public void handlerRemoved(ChannelHandlerContext ctx) {
log.info("设备下线了:{}", ctx.channel().id().asLongText());
} /**
* 当客户端主动断开服务端的链接后,这个通道就是不活跃的。也就是说客户端与服务端的关闭了通信通道并且不可以传输数据
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
log.info("客户端断开链接{}", ctx.channel().localAddress().toString());
} /**
* 设备连接异常处理
*
* @param ctx
* @param cause
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
// 打印异常
log.info("异常:{}", cause.getMessage());
// 关闭连接
ctx.close();
} }

maven依赖


<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--整合web模块-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--导入log4j2日志依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!--og4j2的AsyncLogger本身的逻辑采用了缓冲区思想,使用的是disruptor框架来实现一个环形无锁队列。-->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>${disruptor-version}</version>
</dependency>
<!--Netty-->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.68.Final</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastJson-version}</version>
</dependency>
</dependencies>

项目结构

日志配置文件 log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--status:Log4j2内部日志的输出级别,设置为TRACE对学习Log4j2非常有用 -->
<!--monitorInterval:定时检测配置文件的修改,有变化则自动重新加载配置,时间单位为秒,最小间隔为5s -->
<Configuration status="debug" name="MyApp" packages="" monitorInterval="600"> <!--properties:设置全局变量 -->
<properties>
<!--LOG_HOME:指定当前日志存放的目录 -->
<property name="LOG_HOME">./NettyStudy/log4j2</property>
<!--FILE_NAME:指定日志文件的名称 -->
<property name="FILE_NAME">application</property> <property name="log_pattern">%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level [%L] - %msg%n</property>
<property name="max_single_file_size">20MB</property>
</properties> <!--Appenders:定义日志输出目的地,内容和格式等 -->
<Appenders> <!--Console:日志输出到控制台标准输出 -->
<Console name="Console" target="SYSTEM_OUT">
<!--pattern:日期,线程名,日志级别,日志名称,日志信息,换行 -->
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console> <!--RollingFile:日志输出到文件,下面的文件都使用相对路径 -->
<!--fileName:当前日志输出的文件名称 -->
<!--filePattern:备份日志文件名称,备份目录为logs下面以年月命名的目录,备份时使用gz格式压缩 -->
<RollingFile name="RollingFile" fileName="${LOG_HOME}/${FILE_NAME}.log"
filePattern="${LOG_HOME}/$${date:yyyy-MM}/${FILE_NAME}-%d{yyyy-MM-dd}-%i.log.gz">
<!--pattern:日期,线程名,日志级别,日志名称,日志信息,换行 -->
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level [%L] - %msg%n"/>
<Policies>
<!--SizeBasedTriggeringPolicy:日志文件按照大小备份 -->
<!--size:指定日志文件最大为100MB,单位可以为KB、MB或GB -->
<SizeBasedTriggeringPolicy size="20MB"/>
</Policies> </RollingFile> <RollingFile name="InfoLogRollingFile" fileName="${LOG_HOME}/my_app_info.log"
filePattern="${LOG_HOME}/$${date:yyyy_MM_dd}/my_app_info_%d{yyyy_MM_dd_HH}_%i.log.gz">
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${log_pattern}"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="${max_single_file_size}"/>
</Policies>
<DefaultRolloverStrategy fileIndex="nomax">
<Delete basePath="${LOG_HOME}" maxDepth="2">
<IfFileName glob="*/my_app_info_*.log.gz">
<!-- 这里表示匹配“*/my_app_info_*.log.gz”模式的日志文件的删除策略如下:
- 只要日志文件总数量超过5个就删除按时间顺序最早的日志文件
- 只要日志文件总大小超过10MB就会删除按时间顺序最早的日志文件
- 只要日志文件最近修改时间为9分钟前或更早就会删除按时间顺序最早的日志文件 -->
<IfAny>
<IfAccumulatedFileSize exceeds="8MB"/>
<IfAccumulatedFileCount exceeds="5"/>
<IfLastModified age="9m"/>
</IfAny>
</IfFileName>
</Delete>
</DefaultRolloverStrategy>
</RollingFile> <RollingFile name="WarnLogRollingFile" fileName="${LOG_HOME}/my_app_warn.log"
filePattern="${LOG_HOME}/$${date:yyyy_MM_dd}/my_app_warn_%d{yyyy_MM_dd_HH}_%i.log.gz">
<ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${log_pattern}"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="${max_single_file_size}"/>
</Policies>
<DefaultRolloverStrategy fileIndex="nomax">
<Delete basePath="${LOG_HOME}" maxDepth="2">
<IfFileName glob="*/my_app_warn_*.log.gz">
<IfAny>
<IfAccumulatedFileSize exceeds="3GB"/>
<IfAccumulatedFileCount exceeds="3000"/>
<IfLastModified age="30d"/>
</IfAny>
</IfFileName>
</Delete>
</DefaultRolloverStrategy>
</RollingFile> <RollingFile name="ErrorLogRollingFile" fileName="${LOG_HOME}/my_app_error.log"
filePattern="${LOG_HOME}/$${date:yyyy_MM_dd}/my_app_error_%d{yyyy_MM_dd_HH}_%i.log.gz">
<ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${log_pattern}"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="${max_single_file_size}"/>
</Policies>
<DefaultRolloverStrategy fileIndex="nomax">
<Delete basePath="${LOG_HOME}" maxDepth="2">
<IfFileName glob="*/my_app_error_*.log.gz">
<IfAny>
<IfAccumulatedFileSize exceeds="3GB"/>
<IfAccumulatedFileCount exceeds="3000"/>
<IfLastModified age="30d"/>
</IfAny>
</IfFileName>
</Delete>
</DefaultRolloverStrategy>
</RollingFile> </Appenders>
<Loggers>
<AsyncLogger name="com.meituan.Main" level="trace" additivity="false">
<appender-ref ref="RollingFile"/>
</AsyncLogger>
<AsyncLogger name="RollingFile2" level="trace" additivity="false">
<appender-ref ref="RollingFile2"/>
</AsyncLogger>
<Root level="info">
<AppenderRef ref="Console"/>
<AppenderRef ref="InfoLogRollingFile"/>
</Root>
</Loggers>
</Configuration>

配置文件 application.yml

# netty 配置
netty:
# boss线程数量
boss: 4
# worker线程数量
worker: 2
# 连接超时时间
timeout: 6000
# 服务器主端口
port: 18023
# 服务器备用端口
portSalve: 18026
# 服务器地址
host: 127.0.0.1
spring:
application:
name: netty-server
mvc:
servlet:
load-on-startup: 1 #项目启动时执行初始化即可解决。 server:
port: 15026
undertow:
accesslog:
enabled: false
direct-buffers: true # 是否分配的直接内存(NIO直接分配的堆外内存)
buffer-size: 1024 #每块buffer的空间大小,越小的空间被利用越充分
threads:
worker: 20 # 阻塞任务线程池, 它的值设置取决于系统线程执行任务的阻塞系数,默认值是IO线程数*8
io: 4 # CPU有几核,就填写几。
servlet:
context-path: /undertow

启动Netty服务端

启动Netty客户端

客户端发送消息

服务端收到

Springboot集成Netty实现TCP通讯的更多相关文章

  1. SpringBoot集成netty实现客户端服务端交互和做一个简单的IM

    看了好几天的netty实战,慢慢摸索,虽然还没有摸着很多门道,但今天还是把之前想加入到项目里的 一些想法实现了,算是有点信心了吧(讲真netty对初学者还真的不是很友好......) 首先,当然是在S ...

  2. SpringBoot 集成Netty实现UDP Server

    注:ApplicationRunner 接口是在容器启动成功后的最后一步回调(类似开机自启动). UDPServer package com.vmware.vCenterEvent.netty; im ...

  3. 【Spring Boot】集成Netty Socket.IO通讯框架

    服务端 @Configuration public class NettySocketConfig { private static final Logger logger = LoggerFacto ...

  4. SpringBoot整合Netty实现socket通讯简单demo

    依赖 <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifa ...

  5. 使用netty实现的tcp通讯中如何实现同步返回

    在netty实现的tcp通讯中,一切都是异步操作,这提高了系统性能,但是,有时候client需要同步等待消息返回,如何实现呢?笔者已经实现,在此总结下重点要素 实现要点: 1.消息结构设计 消息头中需 ...

  6. springboot集成websocket的两种实现方式

    WebSocket跟常规的http协议的区别和优缺点这里大概描述一下 一.websocket与http http协议是用在应用层的协议,他是基于tcp协议的,http协议建立链接也必须要有三次握手才能 ...

  7. 基于Netty的TCP服务框架

    19年写的一个基础的TCP服务框架,内置了一个简单IOC容器,当时的目标是一方面能作为组件供第三方集成实现TCP通讯相关功能,另一方面作为提供一种服务框架范式.所以框架核心点主要还是通过适度的封装,隐 ...

  8. SpringBoot集成ActiveMQ

    前面提到了原生API访问ActiveMQ和Spring集成ActiveMQ.今天讲一下SpringBoot集成ActiveMQ.SpringBoot就是为了解决我们的Maven配置烦恼而生,因此使用S ...

  9. SpringBoot 集成MQTT配置

    目录 1. 前言 2. MQTT介绍 3. SpringBoot 集成MQTT 3.1 导入mqtt库 3.2 配置MQTT订阅者 3.3 配置MQTT发布者 3.4 MQTT消息处理和发送 3.4. ...

  10. SpringBoot集成Nacos

    一.环境说明 1.CentOS7 2.Jdk1.8 3.Mysql5.7 4.Nacos1.3 5.SpringBoot2.3.1.RELEASE 6.Maven3.6 二.下载Nacos 1.Nac ...

随机推荐

  1. pyhton - parallel - programming - cookbook(THREAD)

    基于线程的并行 通常,一个应用有一个进程,分成多个独立的线程,并行运行.互相配合,执行不同类型的任务. 线程是独立的处理流程,可以和系统的其他线程并行或并发地执行.多线程可以共享数据和资源,利用所谓的 ...

  2. 给你的 Discord 接入一个既能联网又能画画的 ChatGPT

    如果有这样一款 Discord 机器人,它既能访问互联网,又能绘画,还能给 YouTube 视频提供摘要.最重要的是,它是完全免费的,不需要提供 OpenAI 的 API Key,我就问你香不香? 现 ...

  3. Kubernetes使用Harbor作为私有镜像仓库

    概述 Harbor使用了基于角色的访问控制策略,当从Harbor中拉去镜像的时候,首先要进行身份认证,认证通过后才可以拉取镜像.在命令行模式下,需要先执行docker login,登陆成功后,才可以d ...

  4. node使用jsonwebtoken生成token与验证是否过期

    场景 我们可以使用 cookie,session,token 来做鉴权. 下面我们来看一下, 如何使用 token 来做鉴权 jwt.sign 的简单介绍 npm install jsonwebtok ...

  5. 详解RISC v中断

    声明 本文为本人原创,未经许可严禁转载.部分图源自网络,如有侵权,联系删除. RISC-V 中断与异常 trap(陷阱)可以分为异常与中断.在 RISC v 下,中断有三种来源:software in ...

  6. 关于ChatGPT与机器时代的展望

    关于 ChatGPT 与机器时代的展望 机器人这一概念,最初不是出自计算机科学家或工程师之手,而是来自于捷克的戏剧家卡雷尔·恰佩克(Karl Capek)在 1920 年编排的一出名为"罗森 ...

  7. SVE学习记录- SVE特性以及寄存器

    本文地址:https://www.cnblogs.com/wanger-sjtu/p/SVE_learn_0.html SVE对比NEON有几个新增的地方. 变长的向量 支持Gather-load & ...

  8. 【原创】Ftrace使用及实现机制

    Ftrace使用及实现机制 版权声明:本文为本文为博主原创文章,转载请注明出处 https://www.cnblogs.com/wsg1100 如有错误,欢迎指正. 目录 Ftrace使用及实现机制 ...

  9. PostgreSQL 10 文档: SQL 语法

    SQL 命令   这部分包含PostgreSQL支持的SQL命令的参考信息.每条命令的标准符合和兼容的信息可以在相关的参考页中找到. 目录 ABORT - 中止当前事务 ALTER AGGREGATE ...

  10. [selenium]相对定位器

    前言 Relative Locators,相对定位器,是Selenium 4引入的一个新的定位器,相对定位器根据源点元素去定位相对位置的其它元素. 相对定位方法其实是基于JavaScript的 get ...