Netty详解

news2024/12/30 3:07:16

目录标题

  • 1、前期知识科普
    • 1.1 NIO 基本概念
    • 1.2 java BIO与NIO对比
    • 1.3 Reactor 模型
  • 2、Netty 基础概念
    • 2.1 Netty 简介
    • 2.2 Netty 执行流程
    • 2.3 Netty 核心组件
  • 3、Netty Demo编写
    • 3.1 总体框架
    • 3.2 具体代码
  • 4、交流群

1、前期知识科普

1.1 NIO 基本概念

阻塞(Block)与非阻塞(Non-Block)
阻塞和非阻塞是进程在访问数据的时候,数据是否准备就绪的一种处理方式,当数据没有准备的时候。

  • 阻塞:往往需要等待缓冲区中的数据准备好过后才处理其他的事情,否则一直等待在那里。

  • 非阻塞:当我们的进程访问我们的数据缓冲区的时候,如果数据没有准备好则直接返回,不会等待。如果数据已经准备好,也直接返回。

阻塞 IO :
在这里插入图片描述
非阻塞 IO :
在这里插入图片描述
同步(Synchronous)与异步(Asynchronous)
同步和异步都是基于应用程序和操作系统处理 IO 事件所采用的方式。比如

同步:是应用程序要直接参与 IO 读写的操作。

异步:所有的 IO 读写交给操作系统去处理,应用程序只需要等待通知。

同步方式在处理 IO 事件的时候,必须阻塞在某个方法上面等待我们的 IO 事件完成(阻塞 IO 事件或者通过轮询 IO事件的方式),对于异步来说,所有的 IO 读写都交给了操作系统。这个时候,我们可以去做其他的事情,并不需要去完成真正的 IO 操作,当操作完成 IO 后,会给我们的应用程序一个通知。

所以异步相比较于同步带来的直接好处就是在我们处理IO数据的时候,异步的方式我们可以把这部分等待所消耗的资源用于处理其他事务,提升我们服务自身的性能。

同步 IO :
在这里插入图片描述
异步 IO :
在这里插入图片描述

1.2 java BIO与NIO对比

BIO(传统IO):
BIO是一个同步并阻塞的IO模式,传统的 java.io 包,它基于流模型实现,提供了我们最熟知的一些 IO 功能,比如File抽象、输入输出流等。交互方式是同步、阻塞的方式,也就是说,在读取输入流或者写入输出流时,在读、写动作完成之前,线程会一直阻塞在那里,它们之间的调用是可靠的线性顺序。

NIO(Non-blocking/New I/O)
NIO 是一种同步非阻塞的 I/O 模型,于 Java 1.4 中引入,对应 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它支持面向缓冲的,基于通道的 I/O 操作方法。 NIO 提供了与传统 BIO 模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发
BIO与NIO的对比
在这里插入图片描述
NIO 的 Server 通信的简单模型:
在这里插入图片描述
BIO 的 Server 通信的简单模型:
在这里插入图片描述
NIO的特点
一个线程可以处理多个通道,减少线程创建数量;
读写非阻塞,节约资源:没有可读/可写数据时,不会发生阻塞导致线程资源的浪费

1.3 Reactor 模型

单线程的 Reactor 模型
在这里插入图片描述
多线程的 Reactor 模型
在这里插入图片描述
多线程主从 Reactor 模型
在这里插入图片描述

2、Netty 基础概念

2.1 Netty 简介

Netty 是一个 NIO 客户端服务器框架,可快速轻松地开发网络应用程序,例如协议服务器和客户端。它极大地简化和简化了网络编程,例如 TCP 和 UDP 套接字服务器。

“快速简便”并不意味着最终的应用程序将遭受可维护性或性能问题的困扰。Netty 经过精心设计,结合了许多协议(例如FTP,SMTP,HTTP 以及各种基于二进制和文本的旧式协议)的实施经验。结果,Netty 成功地找到了一种无需妥协即可轻松实现开发,性能,稳定性和灵活性的方法。
在这里插入图片描述

2.2 Netty 执行流程

在这里插入图片描述

2.3 Netty 核心组件

Channel
​ Channel是 Java NIO 的一个基本构造。可以看作是传入或传出数据的载体。因此,它可以被打开或关闭,连接或者断开连接。

EventLoop 与 EventLoopGroup
​ EventLoop 定义了Netty的核心抽象,用来处理连接的生命周期中所发生的事件,在内部,将会为每个Channel分配一个EventLoop。

​ EventLoopGroup 是一个 EventLoop 池,包含很多的 EventLoop。

​ Netty 为每个 Channel 分配了一个 EventLoop,用于处理用户连接请求、对用户请求的处理等所有事件。EventLoop 本身只是一个线程驱动,在其生命周期内只会绑定一个线程,让该线程处理一个 Channel 的所有 IO 事件。

​ 一个 Channel 一旦与一个 EventLoop 相绑定,那么在 Channel 的整个生命周期内是不能改变的。一个 EventLoop 可以与多个 Channel 绑定。即 Channel 与 EventLoop 的关系是 n:1,而 EventLoop 与线程的关系是 1:1。

ServerBootstrap 与 Bootstrap
​ Bootstarp 和 ServerBootstrap 被称为引导类,指对应用程序进行配置,并使他运行起来的过程。Netty处理引导的方式是使你的应用程序和网络层相隔离。

​ Bootstrap 是客户端的引导类,Bootstrap 在调用 bind()(连接UDP)和 connect()(连接TCP)方法时,会新创建一个 Channel,仅创建一个单独的、没有父 Channel 的 Channel 来实现所有的网络交换。

​ ServerBootstrap 是服务端的引导类,ServerBootstarp 在调用 bind() 方法时会创建一个 ServerChannel 来接受来自客户端的连接,并且该 ServerChannel 管理了多个子 Channel 用于同客户端之间的通信。

ChannelHandler 与 ChannelPipeline
​ ChannelHandler 是对 Channel 中数据的处理器,这些处理器可以是系统本身定义好的编解码器,也可以是用户自定义的。这些处理器会被统一添加到一个 ChannelPipeline 的对象中,然后按照添加的顺序对 Channel 中的数据进行依次处理。

ChannelFuture
​ Netty 中所有的 I/O 操作都是异步的,即操作不会立即得到返回结果,所以 Netty 中定义了一个 ChannelFuture 对象作为这个异步操作的“代言人”,表示异步操作本身。如果想获取到该异步操作的返回值,可以通过该异步操作对象的addListener() 方法为该异步操作添加监 NIO 网络编程框架 Netty 听器,为其注册回调:当结果出来后马上调用执行。

​ Netty 的异步编程模型都是建立在 Future 与回调概念之上的。

3、Netty Demo编写

3.1 总体框架

在这里插入图片描述

3.2 具体代码

Maven 依赖

<dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>io.netty</groupId>
      <artifactId>netty-all</artifactId>
      <version>4.1.33.Final</version>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.36</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-simple</artifactId>
      <version>1.7.36</version>
    </dependency>
    <dependency>
      <groupId>cn.hutool</groupId>
      <artifactId>hutool-all</artifactId>
      <version>5.8.4</version>
    </dependency>
    <dependency>
      <groupId>com.spotify</groupId>
      <artifactId>docker-client</artifactId>
      <version>5.0.1</version>
    </dependency>
  </dependencies>

Netty客户端

  • NettyClient类
public class NettyClient implements RpcClient {

    private static final Logger logger = LoggerFactory.getLogger(NettyClient.class);
    private String host;
    private int port;
    private static final Bootstrap bootstrap;
    public NettyClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    static {
        EventLoopGroup group = new NioEventLoopGroup();
        bootstrap = new Bootstrap();
        bootstrap.group(group)
                .channel(NioSocketChannel.class)
                .option(ChannelOption.SO_KEEPALIVE, true)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();
                        pipeline
                                .addLast(new CommonDecoder())
                                .addLast(new CommonEncoder(new JsonSerializer()))
                                .addLast(new NettyClientHandler());
                    }
                });
    }
    @Override
    public Object sendRequest(Request request) {
        try {
            ChannelFuture future = bootstrap.connect(host, port).sync();
            logger.info("客户端连接到服务器 {}:{}", host, port);
            Channel channel = future.channel();
            if (channel != null) {
                channel.writeAndFlush(request).addListener(future1 -> {
                    if (future1.isSuccess()) {
                        logger.info(String.format("客户端发送消息: %s", request.toString()));
                    } else {
                        logger.error("发送消息时有错误发生: ", future1.cause());
                    }
                });
                channel.closeFuture().sync();
                AttributeKey<Response> key = AttributeKey.valueOf("Response");//注意这里的发送是非阻塞的,所以发送后会立刻返回,而无法得到结果。这里通过 AttributeKey 的方式阻塞获得返回结果:
                logger.info("key=====" + key);
                Response response = channel.attr(key).get();
                logger.info("rpcResponse.getData()========" + response.getData());
                return response.getData();
            }
        } catch (InterruptedException e) {
            logger.error("发送消息时有错误发生: ", e);
        }
        return null;
    }
}
  • NettyClientHandler类
public class NettyClientHandler extends SimpleChannelInboundHandler<Response> {

    private static final Logger logger = LoggerFactory.getLogger(NettyClientHandler.class);
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Response msg) throws Exception {
        try {
            logger.info(String.format("客户端接收到消息: %s", msg));
            AttributeKey<Response> key = AttributeKey.valueOf("Response");//接收返回的值
            ctx.channel().attr(key).set(msg);
            ctx.channel().close();
        } finally {
            ReferenceCountUtil.release(msg);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        logger.error("过程调用时有错误发生:");
        cause.printStackTrace();
        ctx.close();
    }
}

Netty服务

  • NettyServer类
public class NettyServer implements RpcServer {

    private static final Logger logger = LoggerFactory.getLogger(NettyServer.class);

    @Override
    public void start(int port) {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
           logger.info("开始启动服务器,端口为:"+port);
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .option(ChannelOption.SO_BACKLOG, 256)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .childOption(ChannelOption.TCP_NODELAY, true)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new CommonEncoder(new JsonSerializer()));
                            pipeline.addLast(new CommonDecoder());
                            pipeline.addLast(new NettyServerHandler());
                        }
                    });
            ChannelFuture future = serverBootstrap.bind(port).sync();
            future.channel().closeFuture().sync();

        } catch (InterruptedException e) {
            logger.error("启动服务器时有错误发生: ", e);
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
  • NettyServerHandler类
public class NettyServerHandler extends SimpleChannelInboundHandler<Request> {

    private static final Logger logger = LoggerFactory.getLogger(NettyServerHandler.class);
    private static ServiceRegistry serviceRegistry;

    static {
        serviceRegistry = new ServiceRegistryImpl();
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Request msg) throws Exception {
        try {
            logger.info("服务器接收到请求: {}", msg);
            String methodName = msg.getMethodName();
            String result = serviceRegistry.getService(methodName);
            ChannelFuture future = ctx.writeAndFlush(Response.success(result));
            future.addListener(ChannelFutureListener.CLOSE);
        } finally {
            ReferenceCountUtil.release(msg);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        logger.error("处理过程调用时有错误发生:");
        cause.printStackTrace();
        ctx.close();
    }

}

service

  • NettyClient
public interface NettyClient {

    Object sendRequest(Request request);
}
  • NettyServer
public interface NettyServer {
    void start(int port);
}
  • ServiceRegistry
public interface ServiceRegistry {
    <T> void register(String methodName);
    String getService(String serviceName);
}

Impl

  • ServiceRegistry Impl类 服务注册
public class ServiceRegistryImpl implements ServiceRegistry {

    private static final Logger logger = LoggerFactory.getLogger(ServiceRegistry.class);

    private static final Map<String, String> serviceMap = new ConcurrentHashMap<>();
    private static final Set<String> registeredService = ConcurrentHashMap.newKeySet();

    @Override
    public synchronized <T> void register(String methodName) {
        logger.info("service1==="+methodName);
        if(registeredService.contains(methodName)) return;
        registeredService.add(methodName);
       serviceMap.put(methodName,methodName+"执行结果返回");
       logger.info("方法: {} 执行结果: {}", methodName, methodName+"执行结果返回");
    }

    @Override
    public synchronized String getService(String methodName) {
        String result = serviceMap.get(methodName);
        if(result == null) {
            throw new NettyException(NettyError.SERVICE_NOT_FOUND);
        }
        return result;
    }
}

endecode包序列化

  • CommonDecoder类
public class CommonDecoder extends ReplayingDecoder {

    private static final Logger logger = LoggerFactory.getLogger(CommonDecoder.class);
    private static final int MAGIC_NUMBER = 0xCAFEBABE;

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        int magic = in.readInt();
        if(magic != MAGIC_NUMBER) {
            logger.error("不识别的协议包: {}", magic);
            throw new NettyException(NettyError.UNKNOWN_PROTOCOL);
        }
        int packageCode = in.readInt();

        Class<?> packageClass;
        if(packageCode == PackageType.REQUEST_PACK.getCode()) {
            packageClass = Request.class;
        } else if(packageCode == PackageType.RESPONSE_PACK.getCode()) {
            packageClass = Response.class;
        } else {
            logger.error("不识别的数据包: {}", packageCode);
            throw new NettyException(NettyError.UNKNOWN_PACKAGE_TYPE);
        }
        int serializerCode = in.readInt();
        CommonSerializer serializer = CommonSerializer.getByCode(serializerCode);
        if(serializer == null) {
            logger.error("不识别的反序列化器: {}", serializerCode);
            throw new NettyException(NettyError.UNKNOWN_SERIALIZER);
        }
        logger.info("序列化器为:"+serializer);
        int length = in.readInt();
        byte[] bytes = new byte[length];
        in.readBytes(bytes);
        Object obj = serializer.deserialize(bytes, packageClass);
        out.add(obj);
    }
}

  • CommonEncoder类
public class CommonEncoder extends MessageToByteEncoder {

    private static final int MAGIC_NUMBER = 0xCAFEBABE;

    private final CommonSerializer serializer;

    public CommonEncoder(CommonSerializer serializer) {
        this.serializer = serializer;
    }

    @Override
    protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
        out.writeInt(MAGIC_NUMBER);
        if(msg instanceof Request) {
            out.writeInt(PackageType.REQUEST_PACK.getCode());
        } else {
            out.writeInt(PackageType.RESPONSE_PACK.getCode());
        }
        out.writeInt(serializer.getCode());
        byte[] bytes = serializer.serialize(msg);
        out.writeInt(bytes.length);
        out.writeBytes(bytes);
    }
}
  • CommonSerializer
public interface CommonSerializer {

    byte[] serialize(Object obj);

    Object deserialize(byte[] bytes, Class<?> clazz);

    int getCode();

    static CommonSerializer getByCode(int code) {
        switch (code) {
            case 1:
                return new JsonSerializer();
            default:
                return null;
        }
    }
}
  • JsonSerializer
public class JsonSerializer implements CommonSerializer {

    private static final Logger logger = LoggerFactory.getLogger(JsonSerializer.class);
    private ObjectMapper objectMapper = new ObjectMapper();
    @Override
    public byte[] serialize(Object obj) {
        try {
            return objectMapper.writeValueAsBytes(obj);

        } catch (JsonProcessingException e) {
            logger.error("serialize序列化时有错误发生:", e);
            throw new SerializeException("序列化时有错误发生");
        }
    }
    @Override
    public Object deserialize(byte[] bytes, Class<?> clazz) {
        try {
            Object obj = objectMapper.readValue(bytes, clazz);
//            if (obj instanceof Request) {
//                obj = handleRequest(obj);
//            }
            return obj;
        } catch (IOException e) {
            logger.error("deserialize序列化时有错误发生:", e);
            throw new SerializeException("序列化时有错误发生");
        }
    }
    /*
        这里由于使用JSON序列化和反序列化Object数组,无法保证反序列化后仍然为原实例类型
        需要重新判断处理
     */
//    private Object handleRequest(Object obj) throws IOException {
//        Request request = (Request) obj;
//        for (int i = 0; i < request.getParamTypes().length; i++) {
//            Class<?> clazz = request.getParamTypes()[i];
//            if (!clazz.isAssignableFrom(request.getParameters()[i].getClass())) {
//                byte[] bytes = objectMapper.writeValueAsBytes(request.getParameters()[i]);
//                request.getParameters()[i] = objectMapper.readValue(bytes, clazz);
//            }
//        }
//        return request;
//    }
    @Override
    public int getCode() {
        return SerializerCode.valueOf("JSON").getCode();
    }

}

entity包请求返回类

  • Request类
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Request implements Serializable {
    /**
     * 待调用方法名称
     */
    private String methodName;

}

  • Response类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Response<T> implements Serializable {

    /**
     * 响应对应的请求号
     */
    private String requestId;
    /**
     * 响应状态码
     */
    private Integer statusCode;
    /**
     * 响应状态补充信息
     */
    private String message;
    /**
     * 响应数据
     */
    private T data;

    public static <T> Response<T> success(T data, String requestId) {
        Response<T> response = new Response<>();
        response.setRequestId(requestId);
        response.setStatusCode(ResponseCode.SUCCESS.getCode());
        response.setData(data);
        return response;
    }
    public static <T> Response<T> success(T data) {
        Response<T> response = new Response<>();
//        response.setRequestId(requestId);
        response.setStatusCode(ResponseCode.SUCCESS.getCode());
        response.setData(data);
        return response;
    }
    public static <T> Response<T> fail(ResponseCode code, String requestId) {
        Response<T> response = new Response<>();
        response.setRequestId(requestId);
        response.setStatusCode(code.getCode());
        response.setMessage(code.getMessage());
        return response;
    }
    public static <T> Response<T> fail(ResponseCode code) {
        Response<T> response = new Response<>();
//        response.setRequestId(requestId);
        response.setStatusCode(code.getCode());
        response.setMessage(code.getMessage());
        return response;
    }

}

enumeration包枚举类

  • NettyError
@AllArgsConstructor
@Getter
public enum NettyError {

    UNKNOWN_ERROR("出现未知错误"),
    SERVICE_SCAN_PACKAGE_NOT_FOUND("启动类ServiceScan注解缺失"),
    CLIENT_CONNECT_SERVER_FAILURE("客户端连接服务端失败"),
    SERVICE_INVOCATION_FAILURE("服务调用出现失败"),
    SERVICE_NOT_FOUND("找不到对应的服务"),
    SERVICE_NOT_IMPLEMENT_ANY_INTERFACE("注册的服务未实现接口"),
    UNKNOWN_PROTOCOL("不识别的协议包"),
    UNKNOWN_SERIALIZER("不识别的(反)序列化器"),
    UNKNOWN_PACKAGE_TYPE("不识别的数据包类型"),
    SERIALIZER_NOT_FOUND("找不到序列化器"),
    RESPONSE_NOT_MATCH("响应与请求号不匹配"),
    FAILED_TO_CONNECT_TO_SERVICE_REGISTRY("连接注册中心失败"),
    REGISTER_SERVICE_FAILED("注册服务失败");
    private final String message;

}
  • PackageType
@AllArgsConstructor
@Getter
public enum PackageType {

    REQUEST_PACK(0),
    RESPONSE_PACK(1);
    private final int code;
}
  • ResponseCode
@AllArgsConstructor
@Getter
public enum ResponseCode {

    SUCCESS(200, "调用方法成功"),
    FAIL(500, "调用方法失败"),
    METHOD_NOT_FOUND(500, "未找到指定方法"),
    CLASS_NOT_FOUND(500, "未找到指定类");
    private final int code;
    private final String message;

}
  • SerializerCode 字节流中标识序列化和反序列化器
@AllArgsConstructor
@Getter
public enum SerializerCode {

    KRYO(0),
    JSON(1),
    HESSIAN(2),
    PROTOBUF(3);
    private final int code;

}

exception 包 异常类

  • NettyException
public class NettyException extends RuntimeException {

    public NettyException(NettyError error, String detail) {
        super(error.getMessage() + ": " + detail);
    }

    public NettyException(String message, Throwable cause) {
        super(message, cause);
    }

    public NettyException(NettyError error) {
        super(error.getMessage());
    }

}
  • SerializeException
public class SerializeException extends RuntimeException {
    public SerializeException(String msg) {
        super(msg);
    }
}

Test 包测试类

  • NettyTestServer
public class NettyTestServer {

    public static void main(String[] args) {
        ServiceRegistry registry = new ServiceRegistryImpl();
        registry.register("方法A");
        NettyServer server = new NettyServer();
        server.start(9999);
    }

}
  • NettyTestClient
public class NettyTestClient {

    public static void main(String[] args) {
        Request request = new Request();
        request.setMethodName("方法A");
        NettyClient nettyClient = new NettyClient("127.0.0.1", 9999);
        System.out.println(nettyClient.sendRequest(request));
    }
}

测试结果,先启动服务端再启动客户端
服务端:
在这里插入图片描述
客户端:
在这里插入图片描述

4、交流群

后端专属技术群
我建了一个后端专属技术群,欢迎从事编程开发、技术招聘HR进群,也欢迎大家分享自己公司的内推信息,相互帮助,一起进步!
文明发言,以交流技术、职位内推、行业探讨为主

图片

资源共享,共同进步

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

0x21 树与图的遍历

0x21 树与图的遍历 树与图最常见的储存方式就是使用一个邻接表保存它们的边集。邻接表以head数组为表头&#xff0c;使用ver和edge数组分别存储边的终点和权值&#xff0c;使用next数组模拟链表指针&#xff08;就像我们在0x13节中讲解邻接表所给出的代码那样&#xff09;。 …

科技铸就企业转型钢筋铁骨,群硕获评2023年度数字化影响力企业

12月15日&#xff0c;STIF2023第四届国际科创节暨DSC2023国际数字服务大会在北京顺利举行&#xff0c;本次大会以“数实融合 推动高质量发展”为主题&#xff0c;各大科技服务企业齐聚一堂&#xff0c;共同探讨2023科技发展新趋势。 大会上&#xff0c;群硕软件继2022年后再度…

Java版商城:Spring Cloud+SpringBoot b2b2c实现多商家入驻、直播带货及免 费小程序商城搭建

1. 涉及平台 平台管理、商家端&#xff08;pc端、手机端&#xff09;、买家平台&#xff08;h5/公众号、小程序、app端&#xff08;ios/android&#xff09;、微服务平台&#xff08;业务服务&#xff09; 2. 核心架构 spring cloud、spring boot、mybatis、redis 3. 前端框架…

如何远程访问Axure RP制作的本地web站点实现协同办公

文章目录 前言1.在AxureRP中生成HTML文件2.配置IIS服务3.添加防火墙安全策略4.使用cpolar内网穿透实现公网访问4.1 登录cpolar web ui管理界面4.2 启动website隧道4.3 获取公网URL地址4.4. 公网远程访问内网web站点4.5 配置固定二级子域名公网访问内网web站点4.5.1创建一条固定…

浅析AI视频分析与视频管理系统EasyCVR平台及场景应用

人工智能的战略重要性导致对视频智能分析的需求不断增加。鉴于人工智能视觉技术的巨大潜力&#xff0c;人们的注意力正在从传统的视频监控转移到计算机视觉的监控过程自动化。 1、什么是视频分析&#xff1f; 视频分析或视频识别技术&#xff0c;是指从视频片段中提取有用信息…

java.lang.UnsupportedOperationException

一、背景 记录一次小坑… 最近在写一个关于Excel导出的小需求&#xff0c;由于系统都有一些工具类&#xff0c;还有原来已经做好的导出&#xff0c;直接拿过来改了改就用了&#xff0c;没想到直接报错&#xff0c;尴尬。 还是那句话&#xff0c;别人都能用&#xff0c;我复制…

innovus:ccopt_design流程

我正在「拾陆楼」和朋友们讨论有趣的话题&#xff0c;你⼀起来吧&#xff1f; 拾陆楼知识星球入口 ccopt完整的流程包括如下几个步骤&#xff1a; spec文件可以只创建一次&#xff0c;无需多次创建。 1&#xff09;clustering阶段 set_ccopt_property balance_mode cluster …

产品经理之Axure的元件库使用详细案例

⭐⭐ 产品经理专栏&#xff1a;产品专栏 ⭐⭐ 个人主页&#xff1a;个人主页 ​ 目录 前言 一.Axure的元件库的使用 1.1 元件介绍 1.2 基本元件的使用 1.2.1 矩形、按钮、标题的使用 1.2.2 图片及热区的使用 1.3 表单元件及表格元件的使用 1.3.1表单元件的使用 1.3.…

NFS|在linux环境下的安装和配置NFS

简介 NFS全称网络文件系统&#xff0c;可用于不同服务器之间的文件共享。 接下来介绍下NFS在linux环境下安装和配置。主要分为服务端和客户端。 服务端安装 开启rpcbind/portmap和nfs服务 # service portmaper start [rootlocalhost java]# service portmap start Redirectin…

低代码平台浅析:引迈JNPF

低代码平台能够改变应用交付和管理的模式&#xff0c;大幅缩减交付周期&#xff0c;最终帮助业务加速创新。引迈JNPF作为当中的一个低代码平台&#xff0c;其在用户体系方面做得怎样呢&#xff1f;我针对引迈JNPF进行了相关体验与测评&#xff0c;一起来看下。 低代码平台体验简…

SpringBoot Starter机制 ——自动化配置

目录 一、Starter机制 1.1 什么是 SpringBoot Starter 1.2 SpringBoot Starter 的作用 1.3 Starter的应用场景 二、案例 2.1 模拟短信发送模版 2.2 AOP实现日志切面模版 一、Starter机制 1.1 什么是 SpringBoot Starter Spring Boot Starter是Spring Boot框架提供的一种…

[Linux] LVS负载均衡群集+NAT部署

一、LVS负载均衡群集知识 1.1 群集的的定义及意义 Cluster&#xff0c;集群&#xff08;也称群集&#xff09;由多台主机构成&#xff0c;但对外只表现为一一个整体&#xff0c;只提供一-个访问入口(域名或IP地址)&#xff0c; 相当于一台大型计算机。 群集的作用&#xff1…

upload-labs笔记

简介 upload-labs是一个使用php语言编写的&#xff0c;专门收集渗透测试和CTF中遇到的各种上传漏洞的靶场。旨在帮助大家对上传漏洞有一个全面的了解。目前一共21关&#xff0c;每一关都包含着不同上传方式。 文件上传漏洞是指&#xff1a; Web 服务器允许用户将文件上传至其…

使用blip2进行图片输入文本输出

多模态的重要模型blip2,官方提供模型可以直接用来图片生成文本 github地址&#xff1a;https://github.com/salesforce/LAVIS/tree/main/projects/blip2 个人相当于跑了一下blip2的demo&#xff0c;记录下过程&#xff0c;供今后需要参考&#xff1a; 1、首先是环境安装&#…

Spring上下文之注解模块ConfigurationMethod

博主介绍:✌全网粉丝5W+,全栈开发工程师,从事多年软件开发,在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战,博主也曾写过优秀论文,查重率极低,在这方面有丰富的经验✌ 博主作品:《Java项目案例》主要基于SpringBoot+MyBatis/MyBatis-plus+…

如何实现免费的文档翻译

文中有彩蛋&#xff0c;请一定要看完。 目录 文中有彩蛋&#xff0c;请一定要看完。 一、问题的提出 二、文档翻译现状 三、如何免费海量文档翻译 1. 采用CAT工具机器翻译API法 2. 采用小牛文档翻译 四、学后反思 一、问题的提出 随着互联网和人工智能技术的飞速发展&…

【力扣】19. 删除链表的倒数第 N 个结点

19. 删除链表的倒数第 N 个结点 相比于昨天&#xff0c;感觉刷题越来越轻松了~ 我进步了&#xff01; 以后刷题力度要加快了&#xff0c;因为我报了蓝桥杯&#xff01;加油~ 法一&#xff1a;计算链表长度 思路&#xff1a; 首先用个函数来计算出该链表的长度&#xff0c;然…

接口返回HTML页面详解

import requests from bs4 import BeautifulSoup import re import jsonurl https://listado.mercadolibre.com.mx/hogar-muebles-jardin/cocina/almacenamiento-organizacion/organizadores-cocina/_CustId_570995983_PrCategId_AD# 添加 headers 和 cookies headers {User-…

批量解压imagenet1k数据集中的zip文件

导言&#xff1a; 最近在处理imagenet1k数据集时&#xff0c;面对大量的zip包&#xff0c;手动一个一个解压显然不是明智的选择。作为程序员&#xff0c;我们可以采用批量解压的方法来提高效率&#xff0c;下面就是解决这一问题的方法和原因分析。 问题背景&#xff1a; image…

拆解大语言模型 RLHF 中的PPO算法

为什么大多数介绍大语言模型 RLHF 的文章&#xff0c;一讲到 PPO 算法的细节就戛然而止了呢&#xff1f;要么直接略过&#xff0c;要么就只扔出一个 PPO 的链接。然而 LLM x PPO 跟传统的 PPO 还是有些不同的呀。 其实在 ChatGPT 推出后的相当一段时间内&#xff0c;我一直在等…