Netty学习example示例

news2025/6/4 18:48:26

在这里插入图片描述

文章目录

  • simple
    • Server端
      • NettyServer
      • NettyServerHandler
    • Client端
      • NettyClient
      • NettyClientHandler
  • tcp(粘包和拆包)
    • Server端
      • NettyTcpServer
      • NettyTcpServerHandler
    • Client端
      • NettyTcpClient
      • NettyTcpClientHandler
  • protocol
    • codec
      • CustomMessageDecoder
      • CustomMessageEncoder
    • server端
      • ProtocolServer
      • ProtocolServerHandler
    • client端
      • ProtocolClient
      • ProtocolClientHandler
  • http
    • Server端
      • HttpServer
      • HttpServerHandler
    • Client端
      • HttpClient
      • HttpClientHandler
  • ws
    • Server端
      • WsServer
      • WsServerHandler
    • Client端
      • WsClient
      • WebSocketClientHandler
  • protobuf
    • Server端
      • NettyServer
      • NettyServerHandler
      • Student.proto
    • Client端
      • NettyClient
      • NettyClientHandler

simple

Server端

NettyServer

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;

import java.net.InetSocketAddress;

@Slf4j
public class NettyServer {
    public static void main(String[] args) {

        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap()
                    .group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler())
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new StringDecoder());
                            pipeline.addLast(new StringEncoder());
                            pipeline.addLast(new NettyServerHandler());
                        }
                    });
            ChannelFuture bindFuture = serverBootstrap.bind(new InetSocketAddress(8888));
            Channel channel = bindFuture.sync().channel();
            log.info("server start");
            channel.closeFuture().sync();
            log.info("server stop");
        } catch (Exception e) {
            log.info("server error", e);
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }
}

NettyServerHandler

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    public void channelRead(io.netty.channel.ChannelHandlerContext ctx, Object msg) throws Exception {
        log.info("服务端接收到客户端数据:{}", msg);
        ctx.writeAndFlush("服务端收到客户端的数据: " + msg);
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        log.info("【NettyServerHandler->userEventTriggered】: {}", evt);
    }

    public void exceptionCaught(io.netty.channel.ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.error("exceptionCaught异常:", cause);
        ctx.close();
    }

    public void handlerAdded(io.netty.channel.ChannelHandlerContext ctx) throws Exception {
        log.info("handlerAdded:{}", ctx.channel().remoteAddress());
    }

    public void handlerRemoved(io.netty.channel.ChannelHandlerContext ctx) throws Exception {
        log.info("handlerRemoved:{}", ctx.channel().remoteAddress());
    }


    public void channelRegistered(io.netty.channel.ChannelHandlerContext ctx) throws Exception {
        log.info("channelRegistered:{}", ctx.channel().remoteAddress());
    }

    public void channelUnregistered(io.netty.channel.ChannelHandlerContext ctx) throws Exception {
        log.info("channelUnregistered:{}", ctx.channel().remoteAddress());
    }

    public void channelActive(io.netty.channel.ChannelHandlerContext ctx) throws Exception {
        log.info("客户端连接:{}", ctx.channel().remoteAddress());
    }

    public void channelInactive(io.netty.channel.ChannelHandlerContext ctx) throws Exception {
        log.info("客户端断开连接:{}", ctx.channel().remoteAddress());
    }


}

Client端

NettyClient

@Slf4j
public class NettyClient {

    public static void main(String[] args) {

        NioEventLoopGroup group = new NioEventLoopGroup(1);
        try {

            Bootstrap bootstrap = new Bootstrap()
                    .group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new StringDecoder());
                            pipeline.addLast(new StringEncoder());
                            pipeline.addLast(new NettyClientHandler());
                        }
                    });
            ChannelFuture connectFuture = bootstrap.connect("127.0.0.1", 8888);
            Channel channel = connectFuture.sync().channel();
            System.out.println("客户端连接成功");

            Scanner sc = new Scanner(System.in);
            while (true) {
                System.out.println("请输入内容: ");
                String line = sc.nextLine();
                if (line == null || line.isEmpty()) {
                    continue;
                } else if ("exit".equals(line)) {
                    channel.close();
                    break;
                }
                channel.writeAndFlush(line);
            }

            channel.closeFuture().sync();
            System.out.println("客户端关闭");
        } catch (Exception e) {
            log.error("客户端发生异常: ", e);
        }

    }

}

NettyClientHandler

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class NettyClientHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        log.info( "【NettyClientHandler->channelRead】: {}", msg);
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        log.info( "【NettyClientHandler->userEventTriggered】: {}", evt);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.info( "异常: {}", cause.getMessage());
        ctx.close();
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        log.info( "【NettyClientHandler->handlerAdded】: {}", ctx);
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        log.info( "【NettyClientHandler->handlerRemoved】: {}", ctx);
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        log.info( "【NettyClientHandler->channelRegistered】: {}", ctx);
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        log.info( "【NettyClientHandler->channelUnregistered】: {}", ctx);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info( "【NettyClientHandler->channelActive】: {}", ctx);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.info( "【NettyClientHandler->channelInactive】: {}", ctx);
    }
}

tcp(粘包和拆包)

Server端

NettyTcpServer

@Slf4j
public class NettyTcpServer {

    public static void main(String[] args) {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        NioEventLoopGroup workerGroup = new NioEventLoopGroup(1);
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap()
                    .group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new NettyTcpServerHandler());
                        }
                    });
            ChannelFuture bindFuture = serverBootstrap.bind("127.0.0.1", 9090);
            Channel channel = bindFuture.sync().channel();
            log.info("server start");
            channel.closeFuture().sync();
        } catch (Exception e) {
            log.info("server error", e);
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

}

NettyTcpServerHandler

@Slf4j
public class NettyTcpServerHandler extends ChannelInboundHandlerAdapter {


    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        byte[] bytes = new byte[byteBuf.readableBytes()];
        byteBuf.readBytes(bytes);
        String content = new String(bytes, StandardCharsets.UTF_8);
        log.info("服务端接收到的数据字节长度为:{}, 内容为: {}", bytes.length, content);
        ByteBuf buf = Unpooled.copiedBuffer(UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8));
        ctx.writeAndFlush(buf);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.info("异常: {}", cause.getMessage());
        ctx.close();
    }
}

Client端

NettyTcpClient

@Slf4j
public class NettyTcpClient {

    public static void main(String[] args) {
        NioEventLoopGroup group = new NioEventLoopGroup(1);
        try {
            Bootstrap bootstrap = new Bootstrap()
                    .group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<NioSocketChannel>() {

                        @Override
                        protected void initChannel(NioSocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new NettyTcpClientHandler());
                        }
                    });
            ChannelFuture connectFuture = bootstrap.connect(new InetSocketAddress("127.0.0.1", 9090));
            Channel channel = connectFuture.sync().channel();
            channel.closeFuture().sync();
        } catch (Exception e) {
            log.error("客户端发生异常: ", e);
        } finally {
            group.shutdownGracefully();
        }

    }

}

NettyTcpClientHandler

@Slf4j
public class NettyTcpClientHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        log.info("客户端接收到数据:{}", byteBuf.toString(StandardCharsets.UTF_8));
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        /*
         粘包:
         1. 这里连续发送10次byteBuf,发现服务端有可能1次就全部接收了,
            也有可能3次接受了,也有可能4次接收了,这是不确定的,
            这也就意味着基于底层NIO的tcp的数据传输 是基于流式传输的,会出现粘包的问题。
         2. 因此服务端必须 自行处理粘包问题,区分消息边界
         3. 这里测试的时候,可以多启动几个客户端来观察
         4. 这里示例的粘包示例与上面simple的区别在于:这里是在短时间内连续发送
         */
        /*for (int i = 0; i < 10; i++) {
            ByteBuf byteBuf = Unpooled.copiedBuffer(("hello, server " + i).getBytes(StandardCharsets.UTF_8));
            ctx.writeAndFlush(byteBuf);
        }*/

        /*
        拆包:
        1. 这里1次发送了1个10000字节长的数据,而服务端分多次收到,有可能是2次,有可能是1次, 这是不确定的,
        2. 假设真实数据包就有这么长,那么服务端可能需要分多次才能接收到完整的数据包,
        3. 同时,我们发现总的数据长度服务端都接收到了,这说明底层NIO的tcp的数据传输 是可靠的
        4. 1条比较长的消息,服务端分多次才能收到,所以服务端需要解决拆包的问题,将多次接收到的消息转为1条完整的消息
        5. 这里示例的拆包示例与上面simple的区别在于:这里1次发送的消息数据很长
        */
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 1000; i++) {
            sb.append("Netty拆包示例|");
        }
        ctx.writeAndFlush(Unpooled.copiedBuffer(sb.toString().getBytes(StandardCharsets.UTF_8)));
        log.info("客户端发送数据长度:{}", sb.toString().length());

		/* 拆包 与 粘包 的核心问题就是 tcp是流式传输的,tcp可以保证数据可靠传输,但需要对方在接收时需要能区分出消息边界,从而获取1条完整的消息 */

    }

}

protocol

codec

使用自定义协议,编解码器,识别消息边界,处理粘包和拆包问题

CustomMessageDecoder

public class CustomMessageDecoder extends ByteToMessageDecoder {
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        if (in.readableBytes() < 4) {
            return;
        }
        in.markReaderIndex();
        int len = in.readInt();
        if (in.readableBytes() < len) {
            in.resetReaderIndex();
            return;
        }
        byte[] bytes = new byte[len];
        in.readBytes(bytes);
        out.add(CustomMessage.builder()
                .len(len)
                .content(bytes)
                .build());
    }
}

CustomMessageEncoder

public class CustomMessageEncoder extends MessageToByteEncoder<CustomMessage> {
    @Override
    protected void encode(ChannelHandlerContext ctx, CustomMessage msg, ByteBuf out) {
        out.writeInt(msg.getLen());
        out.writeBytes(msg.getContent());
    }
}

server端

ProtocolServer

@Slf4j
public class ProtocolServer {

    public static void main(String[] args) {

        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap()
                    .group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new CustomMessageDecoder());
                            pipeline.addLast(new CustomMessageEncoder());
                            pipeline.addLast(new ProtocolServerHandler());
                        }
                    });
            ChannelFuture bindFuture = serverBootstrap.bind(new InetSocketAddress("127.0.0.1", 9090));
            Channel channel = bindFuture.sync().channel();
            log.info("server start");
            channel.closeFuture().sync();

        } catch (Exception e) {
            log.info("server error", e);
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
        log.info("server stop");
    }

}

ProtocolServerHandler

@Slf4j
public class ProtocolServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 这里直接转, 如果不能转的话, 就说明前面的解码有问题
        CustomMessage customMessage = (CustomMessage) msg;
        log.info("服务端收到消息: {}, {}", customMessage.getLen(), new String(customMessage.getContent()));
        // 将消息回过去(需要加上对应的编码器)
        ctx.writeAndFlush(customMessage);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.info("ProtocolServerHandler异常: {}", cause.getMessage());
        ctx.close();
    }
}

client端

ProtocolClient

@Slf4j
public class ProtocolClient {

    public static void main(String[] args) {
        NioEventLoopGroup group = new NioEventLoopGroup(1);
        try {
            Bootstrap bootstrap = new Bootstrap()
                    .group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new CustomMessageEncoder());
                            pipeline.addLast(new CustomMessageDecoder());
                            pipeline.addLast(new ProtocolClientHandler());
                        }
                    });
            ChannelFuture connectFuture = bootstrap.connect("localhost", 9090);
            Channel channel = connectFuture.sync().channel();
            channel.closeFuture().sync();
        } catch (Exception e) {
            log.info("client error", e);
        } finally {
            group.shutdownGracefully();
        }
    }

}

ProtocolClientHandler

@Slf4j
public class ProtocolClientHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // 这里直接转, 如果不能转的话, 就说明前面的解码有问题
        CustomMessage customMessage = (CustomMessage) msg;
        log.info("客户端收到消息: {}, {}", customMessage.getLen(), new String(customMessage.getContent()));
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        for (int i = 1; i <= 20; i++) {
            byte[] bytes = ("hello, server " + i).getBytes(StandardCharsets.UTF_8);
            CustomMessage message = CustomMessage.builder()
                    .content(bytes)
                    .len(bytes.length)
                    .build();
            ctx.writeAndFlush(message);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.error("【ProtocolClientHandler->exceptionCaught】: {}", cause.getMessage());
    }
}

http

Server端

HttpServer

@Slf4j
public class HttpServer {

    public static void main(String[] args) {

        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap()
                    .group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler("【服务端主】"))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast("loggingHandler", new LoggingHandler("【服务端从】"));
                            pipeline.addLast("httpServerCodec", new HttpServerCodec());
                            pipeline.addLast("aggregator", new HttpObjectAggregator(10 * 1024 * 1024));
                            pipeline.addLast("httpServerHandler", new HttpServerHandler());
                        }
                    });
            ChannelFuture channelFuture = serverBootstrap.bind(new InetSocketAddress(8080));
            channelFuture.sync();
            log.info("http服务器启动成功, 您可以访问: http://localhost:8080/test");
            channelFuture.channel().closeFuture().sync();
        } catch (Exception e) {
            log.info("服务端发生异常: ", e);
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }


    }

}

HttpServerHandler

@Slf4j
public class HttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) {
        log.info("【HttpServerHandler->处理】:{}", msg);
        if (msg instanceof FullHttpRequest) {
            FullHttpRequest fullHttpRequest = (FullHttpRequest) msg;
            String uri = fullHttpRequest.uri();
            log.info("【uri】:{}", uri);
            HttpMethod method = fullHttpRequest.method();
            log.info("【method】:{}", method);

            // 响应回去
            byte[] bytes = ("服务器收到时间" + LocalDateTime.now()).getBytes(StandardCharsets.UTF_8);
            DefaultFullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(
                    HttpVersion.HTTP_1_1,
                    HttpResponseStatus.OK,
                    Unpooled.copiedBuffer(bytes)
            );
            fullHttpResponse.headers().set(HttpHeaderNames.CONTENT_LENGTH, bytes.length);
            fullHttpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain;charset=utf-8");
            ChannelPromise promise = ctx.newPromise();
            promise.addListener(new GenericFutureListener<Future<? super Void>>() {
                @Override
                public void operationComplete(Future<? super Void> future) throws Exception {
                    log.info("操作完成");
                    log.info("isDone: {}", future.isDone());
                    log.info("isSuccess: {}", future.isSuccess());
                    log.info("isCancelled: {}", future.isCancelled());
                    log.info("hasException: {}", future.cause() != null, future.cause());
                }
            });
            ctx.writeAndFlush(fullHttpResponse, promise);
            log.info("刚刚写完");

        }
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        log.error("【HttpServerHandler->userEventTriggered】:{}", evt);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
         log.error("【HttpServerHandler->exceptionCaught】", cause);
    }

    public void channelRegistered(ChannelHandlerContext ctx) {
        log.info("【HttpServerHandler->channelRegistered】");
    }

    public void channelUnregistered(ChannelHandlerContext ctx) {
        log.info("【HttpServerHandler->channelUnregistered】");
    }

    public void handlerAdded(ChannelHandlerContext ctx) {
        log.info("【HttpServerHandler->handlerAdded】");
    }

    public void handlerRemoved(ChannelHandlerContext ctx) {
        log.info("【HttpServerHandler->handlerRemoved】");
    }

     public void channelActive(ChannelHandlerContext ctx) {
        log.info("【HttpServerHandler->channelActive】");
    }
    public void channelInactive(ChannelHandlerContext ctx) {
        log.info("【HttpServerHandler->channelInactive】");
    }


}

Client端

HttpClient

@Slf4j
public class HttpClient {

    public static void main(String[] args) {
        NioEventLoopGroup group = new NioEventLoopGroup(1);
        try {
            Bootstrap bootstrap = new Bootstrap()
                    .group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast("loggingHandler", new LoggingHandler(LogLevel.DEBUG));
                            pipeline.addLast("httpClientCodec", new HttpClientCodec());
                            pipeline.addLast("", new HttpObjectAggregator(10 * 1024));
                            pipeline.addLast("httpClientHandler", new HttpClientHandler());
                        }
                    });
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080);
            channelFuture.sync();

            Channel channel = channelFuture.channel();

            sendGetRequest(channel);

            // 等待通道关闭
            channelFuture.channel().closeFuture().sync();
        } catch (Exception e) {
            log.info("客户端发生异常: ", e);
        } finally {
            // 遇到问题, 调用此方法后客户端没有正常关闭, 将netty版本4.1.20.FINAL切换到4.1.76.FINAL即可
            group.shutdownGracefully();
            log.info("关闭group-finally");
        }
        log.info("客户端执行完毕");
    }

    private static void sendGetRequest(Channel channel) throws URISyntaxException {
        String url = "http://localhost:8080/test"; // 测试URL
        URI uri = new URI(url);
        String host = uri.getHost();
        String path = uri.getRawPath() + (uri.getRawQuery() == null ? "" : "?" + uri.getRawQuery());

        // 构建HTTP请求
        FullHttpRequest request = new DefaultFullHttpRequest(
                HttpVersion.HTTP_1_1,
                HttpMethod.GET,
                path,
                Unpooled.EMPTY_BUFFER
        );
        request.headers()
                .set(HttpHeaderNames.HOST, host)
                .set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE)
                .set(HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.GZIP);

        // 发送请求
        ChannelFuture channelFuture = channel.writeAndFlush(request);
        log.info("Request sent: " + request);
    }

}

HttpClientHandler

@Slf4j
public class HttpClientHandler extends SimpleChannelInboundHandler<FullHttpResponse> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpResponse response) {
        // 处理响应
        log.info("处理响应, 响应头: {}", response.headers().toString());
        log.info("处理响应, 响应体: {}", response.content().toString(CharsetUtil.UTF_8));

        // 关闭连接
        ctx.channel().close();
        log.info("关闭连接");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.info( "异常: {}", cause.getMessage());
        ctx.close();
    }
}

ws

Server端

WsServer

@Slf4j
public class WsServer {

    public static void main(String[] args) {

        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap()
                    .group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler())
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast("httpServerCodec", new HttpServerCodec());
                            pipeline.addLast("aggregator", new HttpObjectAggregator(10 * 1024 * 1024));
                            WebSocketServerProtocolConfig config = WebSocketServerProtocolConfig.newBuilder()
                                    .websocketPath("/ws")
                                    .checkStartsWith(true)
                                    .build();
                            pipeline.addLast("wsProtocolHandler", new WebSocketServerProtocolHandler(config));
                            pipeline.addLast("wsServerHandler", new WsServerHandler());
                        }
                    });
            ChannelFuture channelFuture = serverBootstrap.bind("127.0.0.1", 9090);
            channelFuture.sync();
            log.info("ws服务启动成功");
            channelFuture.channel().closeFuture().sync();
        } catch (Exception e) {
            log.error("服务端发生异常: ", e);
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

        log.info("ws服务关闭");

    }

}

WsServerHandler

@Slf4j
public class WsServerHandler extends SimpleChannelInboundHandler<WebSocketFrame> {

    private static Map<String, Channel> CHANNELS = new ConcurrentHashMap<>();
    private static AttributeKey<String> ATTRIBUTE_KEY_TOKEN = AttributeKey.valueOf("token");
    private static AttributeKey<Boolean> ATTRIBUTE_KEY_REPEAT = AttributeKey.valueOf("repeat");

    @Override

    protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame webSocketFrame) throws Exception {
        log.info("【WsServerHandler->处理】:{}", webSocketFrame);
        if (webSocketFrame instanceof TextWebSocketFrame) {
            TextWebSocketFrame textWebSocketFrame = (TextWebSocketFrame) webSocketFrame;
            log.info("【textWebSocketFrame.text()】:{}", textWebSocketFrame.text());
            sendAll(ctx.channel(), textWebSocketFrame.text());
        }
    }

    private void sendAll(Channel channel, String text) {
        CHANNELS.forEach((token, ch) -> {
            if (channel != ch) {
                ch.writeAndFlush(new TextWebSocketFrame(text));
            }
        });
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        log.info("【WsServerHandler->userEventTriggered】: {}", evt);

        if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
            WebSocketServerProtocolHandler.HandshakeComplete handshakeComplete = (WebSocketServerProtocolHandler.HandshakeComplete) evt;
            String requestUri = handshakeComplete.requestUri();
            String subprotocol = handshakeComplete.selectedSubprotocol();
            log.info("【requestUri】:{}", requestUri);
            log.info("【subprotocol】:{}", subprotocol);

            handleAuth(requestUri, ctx);

        }

    }

    private void handleAuth(String requestUri, ChannelHandlerContext ctx) {
        try {
            Map<String, String> queryParams = getQueryParams(requestUri);

            String token = queryParams.get("token");
            log.info("【token】:{}", token);

            if (token == null) {
                ctx.close();
                log.info("token为空, 关闭channel");
            } else {
                ctx.channel().attr(ATTRIBUTE_KEY_TOKEN).set(token);
                Channel oldChannel = CHANNELS.put(token, ctx.channel());
                if (oldChannel != null) {
                    oldChannel.attr(ATTRIBUTE_KEY_REPEAT).set(true);
                    oldChannel.close();
                } else {
                    sendAll(ctx.channel(), "欢迎" + token + "进入聊天室");
                }
            }

        } catch (Exception e) {
            ctx.close();
        }
    }

    private static Map<String, String> getQueryParams(String requestUri) throws URISyntaxException {
        URI uri = new URI(requestUri);
        String query = uri.getQuery();
        Map<String, String> queryParams = new HashMap<>();
        if (query != null) {
            String[] params = query.split("&");
            for (String param : params) {
                String[] keyValue = param.split("=");
                String key = keyValue[0];
                String value = keyValue.length > 1 ? keyValue[1] : "";
                queryParams.put(key, value);
            }
        }
        return queryParams;
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.error("【WsServerHandler->exceptionCaught】", cause);
    }

    public void handlerAdded(ChannelHandlerContext ctx) {
        log.info("【WsServerHandler->handlerAdded】");
    }

    public void handlerRemoved(ChannelHandlerContext ctx) {
        log.info("【WsServerHandler->handlerRemoved】");
    }

    public void channelRegistered(ChannelHandlerContext ctx) {
        log.info("【WsServerHandler->channelRegistered】");
    }

    public void channelUnregistered(ChannelHandlerContext ctx) {
        log.info("【WsServerHandler->channelUnregistered】");
    }

    public void channelActive(ChannelHandlerContext ctx) {
        log.info("【WsServerHandler->channelActive】");
    }

    public void channelInactive(ChannelHandlerContext ctx) {
        log.info("【WsServerHandler->channelInactive】");
        Channel channel = ctx.channel();
        Boolean isRepeat = channel.attr(ATTRIBUTE_KEY_REPEAT).get() != null
                && channel.attr(ATTRIBUTE_KEY_REPEAT).get();
        if (!isRepeat) {
            CHANNELS.computeIfPresent(ctx.attr(ATTRIBUTE_KEY_TOKEN).get(), (key, ch) -> {
                CHANNELS.remove(channel.attr(ATTRIBUTE_KEY_TOKEN));
                sendAll(channel, channel.attr(ATTRIBUTE_KEY_TOKEN).get() + "离开聊天室");
                return null;
            });
        }
    }


}

Client端

WsClient

@Slf4j
public class WsClient {

    public static void main(String[] args) {

        NioEventLoopGroup group = new NioEventLoopGroup(1);
        try {
            CountDownLatch connectLatch = new CountDownLatch(1);
            Bootstrap bootstrap = new Bootstrap()
                    .group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new HttpClientCodec());
                            pipeline.addLast(new HttpObjectAggregator(10 * 1024));
                            WebSocketClientProtocolConfig config = WebSocketClientProtocolConfig.newBuilder()
                                    .handleCloseFrames(false)
                                    .build();
                            WebSocketClientHandshaker webSocketClientHandshaker = WebSocketClientHandshakerFactory.newHandshaker(
                                    new URI("ws://localhost:9090/ws/1?token=abc"),
                                    WebSocketVersion.V13,
                                    null,
                                    true,
                                    new DefaultHttpHeaders());
                            pipeline.addLast(new WebSocketClientProtocolHandler(webSocketClientHandshaker, config));
                            pipeline.addLast(new WebSocketClientHandler(connectLatch));
                        }
                    });
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 9090);
            Channel channel = channelFuture.channel();

            channelFuture.addListener((ChannelFutureListener) future -> {
                if (!future.isSuccess()) {
                    System.err.println("Connection failed: " + future.cause());
                    connectLatch.countDown(); // 确保不会死等
                }
            });

            // 等待连接完成(带超时)
            if (!connectLatch.await(10, TimeUnit.SECONDS)) {
                throw new RuntimeException("Connection timed out");
            }

            Scanner sc = new Scanner(System.in);
            while (true) {
                System.out.print("请输入:");
                String line = sc.nextLine();
                if (StringUtil.isNullOrEmpty(line)) {
                    continue;
                }
                if ("exit".equals(line)) {
                    channel.close();
                    break;
                } else {
                    // 发送消息
                    WebSocketFrame frame = new TextWebSocketFrame(line);
                    channelFuture.channel().writeAndFlush(frame);
                }
            }

            channelFuture.channel().closeFuture().sync();
        } catch (Exception e) {
            log.info("客户端发生异常: ", e);
        } finally {
            group.shutdownGracefully();
        }

    }

}

WebSocketClientHandler

@Slf4j
public class WebSocketClientHandler extends SimpleChannelInboundHandler<WebSocketFrame> {

    private CountDownLatch connectLatch;

    public WebSocketClientHandler(CountDownLatch connectLatch) {
        this.connectLatch = connectLatch;
    }
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception {
        // 处理接收到的WebSocket帧
        if (frame instanceof TextWebSocketFrame) {
            String text = ((TextWebSocketFrame) frame).text();
            System.out.println("Received: " + text);
        } else if (frame instanceof PingWebSocketFrame) {
            // 响应Ping帧
            ctx.writeAndFlush(new PongWebSocketFrame(frame.content().retain()));
            System.out.println("Responded to ping");
        } else if (frame instanceof CloseWebSocketFrame) {
            System.out.println("Received close frame");
            ctx.close();
        } else if (frame instanceof BinaryWebSocketFrame) {
            System.out.println("Received binary data: " + frame.content().readableBytes() + " bytes");
        }
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        // 处理握手完成事件
        if (evt == WebSocketClientProtocolHandler.ClientHandshakeStateEvent.HANDSHAKE_COMPLETE) {
            System.out.println("WebSocket handshake complete event");
            // 握手完成后可以发送初始消息
            connectLatch.countDown();
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        System.err.println("WebSocket error: ");
        cause.printStackTrace();
        ctx.close();
    }

}

protobuf

Server端

NettyServer

@Slf4j
public class NettyServer {
    public static void main(String[] args) {

        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap()
                    .group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler())
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new ProtobufEncoder());
                            pipeline.addLast(new ProtobufDecoder(StudentPOJO.Student.getDefaultInstance()));
                            pipeline.addLast(new NettyServerHandler());
                        }
                    });
            ChannelFuture bindFuture = serverBootstrap.bind(new InetSocketAddress(8888));
            Channel channel = bindFuture.sync().channel();
            log.info("server start");
            channel.closeFuture().sync();
            log.info("server stop");
        } catch (Exception e) {
            log.info("server error", e);
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }
}

NettyServerHandler

@Slf4j
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        log.info("服务端接收到客户端数据:{}", msg);
        if (msg instanceof StudentPOJO.Student) {
            StudentPOJO.Student student = (StudentPOJO.Student) msg;
            log.info( "客户端发送的数据:{}, {}, {}", student, student.getId(), student.getName());
        }
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        log.info("【NettyServerHandler->userEventTriggered】: {}", evt);
    }

    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.error("exceptionCaught异常:", cause);
        ctx.close();
    }

    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        log.info("handlerAdded:{}", ctx.channel().remoteAddress());
    }

    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        log.info("handlerRemoved:{}", ctx.channel().remoteAddress());
    }


    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        log.info("channelRegistered:{}", ctx.channel().remoteAddress());
    }

    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        log.info("channelUnregistered:{}", ctx.channel().remoteAddress());
    }

    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("channelActive:{}", ctx.channel().remoteAddress());
    }

    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.info("channelInactive:{}", ctx.channel().remoteAddress());
    }


}

Student.proto

syntax = "proto3"; //版本
option java_outer_classname = "StudentPOJO";//生成的外部类名,同时也是文件名
//protobuf 使用message 管理数据
message Student { //会在 StudentPOJO 外部类生成一个内部类 Student, 他是真正发送的POJO对象
    int32 id = 1; // Student 类中有 一个属性 名字为 id 类型为int32(protobuf类型) 1表示属性序号,不是值
    string name = 2;
}
// 执行命令 protoc.exe --java_out=生成路径 Student.proto路径

Client端

NettyClient

@Slf4j
public class NettyClient {

    public static void main(String[] args) {

        NioEventLoopGroup group = new NioEventLoopGroup(1);
        try {

            Bootstrap bootstrap = new Bootstrap()
                    .group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new ProtobufEncoder());
                            pipeline.addLast(new ProtobufDecoder(StudentPOJO.Student.getDefaultInstance()));
                            pipeline.addLast(new NettyClientHandler());
                        }
                    });
            ChannelFuture connectFuture = bootstrap.connect("127.0.0.1", 8888);
            Channel channel = connectFuture.sync().channel();
            log.info("客户端连接成功");
            channel.closeFuture().sync();
            log.info("客户端关闭");
        } catch (Exception e) {
            log.error("客户端发生异常: ", e);
        }

    }

}

NettyClientHandler

@Slf4j
public class NettyClientHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        log.info( "【NettyClientHandler->channelRead】: {}", msg);
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        log.info( "【NettyClientHandler->userEventTriggered】: {}", evt);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.info( "异常: {}", cause.getMessage());
        ctx.close();
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        log.info( "【NettyClientHandler->handlerAdded】: {}", ctx);
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        log.info( "【NettyClientHandler->handlerRemoved】: {}", ctx);
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        log.info( "【NettyClientHandler->channelRegistered】: {}", ctx);
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        log.info( "【NettyClientHandler->channelUnregistered】: {}", ctx);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info( "【NettyClientHandler->channelActive】: {}", ctx);
        StudentPOJO.Student student = StudentPOJO.Student.newBuilder().setId(1).setName("张三san").build();
        ctx.writeAndFlush(student);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.info( "【NettyClientHandler->channelInactive】: {}", ctx);
    }
}

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

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

相关文章

[RoarCTF 2019]Easy Calc

查看源代码 <!--Ive set up WAF to ensure security.--> <script>$(#calc).submit(function(){$.ajax({url:"calc.php?num"encodeURIComponent($("#content").val()),type:GET,success:function(data){$("#result").html(<div …

[Windows]在Win上安装bash和zsh - 一个脚本搞定

目录 前言安装步骤配置要求下载安装脚本启动程序 前言 Windows是一个很流行的系统, 但是在Windows上安装bash和zsh一直是一个让人头疼的问题. 本蛙特意打包了一个程序, 用于一站式解决这一类的问题. 安装步骤 配置要求 系统: Windows软件: Powershell 5.1或以上 下载安装…

从认识AI开始-----解密LSTM:RNN的进化之路

前言 我在上一篇文章中介绍了 RNN&#xff0c;它是一个隐变量模型&#xff0c;主要通过隐藏状态连接时间序列&#xff0c;实现了序列信息的记忆与建模。然而&#xff0c;RNN在实践中面临严重的“梯度消失”与“长期依赖建模困难”问题&#xff1a; 难以捕捉相隔很远的时间步之…

leetcode0513. 找树左下角的值-meidum

1 题目&#xff1a;找树左下角的值 官方标定难度&#xff1a;中 给定一个二叉树的 根节点 root&#xff0c;请找出该二叉树的 最底层 最左边 节点的值。 假设二叉树中至少有一个节点。 示例 1: 输入: root [2,1,3] 输出: 1 示例 2: 输入: [1,2,3,4,null,5,6,null,null,7]…

命令行式本地与服务器互传文件

文章目录 1. 背景2. 传输方式2.1 SCP 协议传输2.2 SFTP 协议传输 3. 注意 命令行式本地与服务器互传文件 1. 背景 多设备协同工作中&#xff0c;因操作系统的不同&#xff0c;我们经常需要将另外一个系统中的文件传输到本地PC进行浏览、编译。多设备文件互传&#xff0c;在嵌入…

LabelImg: 开源图像标注工具指南

LabelImg: 开源图像标注工具指南 1. 简介 LabelImg 是一个图形化的图像标注工具&#xff0c;使用 Python 和 Qt 开发。它是目标检测任务中最常用的标注工具之一&#xff0c;支持 PASCAL VOC 和 YOLO 格式的标注输出。该工具开源、免费&#xff0c;并且跨平台支持 Windows、Lin…

计算机网络 TCP篇常见面试题总结

目录 TCP 的三次握手与四次挥手详解 1. 三次握手&#xff08;Three-Way Handshake&#xff09; 2. 四次挥手&#xff08;Four-Way Handshake&#xff09; TCP 为什么可靠&#xff1f; 1. 序列号与确认应答&#xff08;ACK&#xff09; 2. 超时重传&#xff08;Retransmis…

树欲静而风不止,子欲养而亲不待

2025年6月2日&#xff0c;13~26℃&#xff0c;一般 待办&#xff1a; 物理2 、物理 学生重修 职称材料的最后检查 教学技能大赛PPT 遇见&#xff1a;使用通义创作了一副照片&#xff0c;很好看&#xff01;都有想用来创作自己的头像了&#xff01; 提示词如下&#xff1a; A b…

Kotlin中的::操作符详解

Kotlin提供了::操作符&#xff0c;用于创建对类或对象的成员(函数、属性)的引用。这种机制叫做成员引用(Member Reference)。这是Kotlin高阶函数和函数式编程的重要组成部分。 简化函数传递 在Java中&#xff0c;我们这样传方法&#xff1a; list.forEach(item -> System.…

深入详解编译与链接:翻译环境和运行环境,翻译环境:预编译+编译+汇编+链接,运行环境

目录 一、翻译环境和运行环境 二、翻译环境&#xff1a;预编译编译汇编链接 &#xff08;一&#xff09;预处理&#xff08;预编译&#xff09; &#xff08;二&#xff09;编译 1、词法分析 2、语法分析 3、语义分析 &#xff08;三&#xff09;汇编 &#xff08;四&…

定时任务:springboot集成xxl-job-core(二)

定时任务实现方式&#xff1a; 存在的问题&#xff1a; xxl-job的原理&#xff1a; 可以根据服务器的个数进行动态分片&#xff0c;每台服务器分到的处理数据是不一样的。 1. 多台机器动态注册 多台机器同时配置了调度器xxl-job-admin之后&#xff0c;执行器那里会有多个注…

DeviceNET转EtherCAT网关:医院药房自动化的智能升级神经中枢

在现代医院药房自动化系统中&#xff0c;高效、精准、可靠的设备通信是保障患者用药安全与效率的核心。当面临既有支持DeviceNET协议的传感器、执行器&#xff08;如药盒状态传感器、机械臂限位开关&#xff09;需接入先进EtherCAT高速实时网络时&#xff0c;JH-DVN-ECT疆鸿智能…

一:UML类图

一、类的设计 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 学习设计模式的第一步是看懂UML类图,类图能直观的表达类、对象之间的关系,这将有助于后续对代码的编写。 类图在软件设计及应用框架前期设计中是不可缺少的一部分,它的主要成分包括:类名、…

Java 中 MySQL 索引深度解析:面试核心知识点与实战

&#x1f91f;致敬读者 &#x1f7e9;感谢阅读&#x1f7e6;笑口常开&#x1f7ea;生日快乐⬛早点睡觉 &#x1f4d8;博主相关 &#x1f7e7;博主信息&#x1f7e8;博客首页&#x1f7eb;专栏推荐&#x1f7e5;活动信息 文章目录 Java 中 MySQL 索引深度解析&#xff1a;面试…

设计模式之结构型:装饰器模式

装饰器模式(Decorator Pattern) 定义 装饰器模式是一种​​结构型设计模式​​&#xff0c;允许​​动态地为对象添加新功能​​&#xff0c;而无需修改其原始类。它通过将对象包装在装饰器类中&#xff0c;以​​组合代替继承​​&#xff0c;实现功能的灵活扩展(如 Java I/O …

MySQL安装及启用详细教程(Windows版)

MySQL安装及启用详细教程&#xff08;Windows版&#xff09; &#x1f4cb; 概述 本文档将详细介绍MySQL数据库在Windows系统下的下载、安装、配置和启用过程。 &#x1f4e5; MySQL下载 官方下载地址 官方网站: https://dev.mysql.com/downloads/社区版本: https://dev.my…

【HarmonyOS Next之旅】DevEco Studio使用指南(二十九) -> 开发云数据库

目录 1 -> 开发流程 2 -> 创建对象类型 3 -> 添加数据条目 3.1 -> 手动创建数据条目文件 3.2 -> 自动生成数据条目文件 4 -> 部署云数据库 1 -> 开发流程 云数据库是一款端云协同的数据库产品&#xff0c;提供端云数据的协同管理、统一的数据模型和…

批量导出CAD属性块信息生成到excel——CAD C#二次开发(插件实现)

本插件可实现批量导出文件夹内大量dwg文件的指定块名的属性信息到excel&#xff0c;效果如下&#xff1a; 插件界面&#xff1a; dll插件如下&#xff1a; 使用方法&#xff1a; 1、获取此dll插件。 2、cad命令行输入netload &#xff0c;加载此dll&#xff08;要求AutoCAD&…

Goreplay最新版本的安装和简单使用

一&#xff1a;概述 Gor 是一个开源工具&#xff0c;用于捕获实时 HTTP 流量并将其重放到测试环境中&#xff0c;以便使用真实数据持续测试您的系统。它可用于提高对代码部署、配置更改和基础设施更改的信心。简单易用。 项目地址&#xff1a;buger/goreplay: GoReplay is an …

Android Studio 解决报错 not support JCEF 记录

问题&#xff1a;Android Studio 安装Markdown插件后&#xff0c;报错not support JCEF不能预览markdown文件。 原因&#xff1a;Android Studio不是新装&#xff0c;之前没留意IDE自带的版本是不支持JCEF的。 解决办法&#xff1a; 在菜单栏选中Help→Find Action&#xff…