基于SpringBoot快速使用Netty - 客户端

启动配置代码

        该文章提供客户端代码,如需服务器端代码,请看下篇文章,基于SpringBoot项目编写的。

        支持运行项目时自动启动netty,支持断线无限重连,只需要修改配置文件中的IP和端口即可使用,可以直接复制代码,解码处理器需要自己编写逻辑,当然也可以使用提供的解码器,详细见下文。

        没有提供Controller,要是需要,可以自己新建一个Controller,再ClientBoot类中写一个sendMsg()方法,方法中调用connect()方法,然后在你的Controller里注入ClientBoot,调用sendMsg()即可。

ClientStarter : 启动器

//客户端启动器
@Slf4j
@Component
public class ClientStarter {
    @Resource
    private NettyConfig nettyConfig;

    public void bootstrap() {
    Bootstrap bootstrap = new Bootstrap();
    bootstrap.group(new NioEventLoopGroup(nettyConfig.getWorker()))
             .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, nettyConfig.getTimeout())
             .option(ChannelOption.SO_KEEPALIVE, true) // 避免意外断开
             .channel(NioSocketChannel.class) // 指定通道
             .handler(new ClientHandler()); // 指定处理器

        //连接服务器
        try {
            ClientBoot clientBoot = new ClientBoot();
            clientBoot.connect(bootstrap, nettyConfig);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

}

ClientBoot : 连接服务器

//客户端链接
@Component
@Slf4j
public class ClientBoot {

    public void connect(Bootstrap bootstrap, NettyConfig nettyConfig) throws InterruptedException {
        // 连接 netty
        ChannelFuture future = bootstrap.connect(nettyConfig.getHost(), nettyConfig.getPort());

        //连接失败无限重连,直到连接成功为止,重连时间为5秒/次
        future.addListener((ChannelFutureListener) channelFuture -> {
            if (!channelFuture.isSuccess()) {
                log.info("连接失败,尝试重新连接!");

                // 在连接失败后,5秒后尝试重新连接
                channelFuture.channel().eventLoop().schedule(() -> {
                    try {
                        connect(bootstrap, nettyConfig);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }, nettyConfig.getReconnect(), TimeUnit.SECONDS);
            } else {
                log.info("客户端连接服务器成功!");
            }
        });

        //给关闭通道进行监听
        Channel channel = future.channel();
        channel.closeFuture().sync();
    }
}

ClientHandler: 客户端的处理器

        这个类中的解码器和消息处理器是你主要写的地方,解码器需要根据自己的业务进行编写,也可以使用提供好的解码器,当然还可以自行添加一些其他的Handler

netty 提供的解码

DelimiterBasedFrameDecoder 解决TCP的粘包解码器
StringDecoder              消息转成String解码器
LineBasedFrameDecoder      自动完成标识符分隔解码器
FixedLengthFrameDecoder    固定长度解码器,二进制
Base64Decoder base64       解码对于 netty的数据传递都是ByteBuf,我们一般重写以上的解码器、编码器来实现自己的逻辑
//客户端处理器
public class ClientHandler extends ChannelInitializer<NioSocketChannel> {
    private static final NettyConfig nettyConfig = ApplicationContextHelperUtil.getBean(NettyConfig.class);

    @Override
    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
        ChannelPipeline pipeline = nioSocketChannel.pipeline();
        //心跳检测处理器
        //IdleStateHandler参数说明: 读空闲时间,写空闲时间,全部空闲时间,时间单位(默认秒)
        pipeline.addLast(new IdleStateHandler(nettyConfig.getReadTime(), nettyConfig.getWriteTime(), 0, TimeUnit.SECONDS));
        pipeline.addLast(new AnalyzeMessageHandler());//自定义解码器
        pipeline.addLast(new MonitorMessageHandler());//客户端消息处理器
    }
}

其他所需代码

依赖

        <!--netty-->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.39.Final</version>
        </dependency>

        <!--工具-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.5.4</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.5</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.12</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.75</version>
        </dependency>
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.10.1</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>2.2.10</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>30.1-jre</version>
        </dependency>

        <!--日志-->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.17.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>log4j-to-slf4j</artifactId>
                    <groupId>org.apache.logging.log4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>

配置启动类

项目启动入口(启动类)

@SpringBootApplication
public class NettyClientApplication {

    @Resource
    private ClientStarter clientStarter;

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(NettyClientApplication.class, args);

        try {
            InetAddress ip = Inet4Address.getLocalHost();
            System.out.println("当前IP地址==>>:" + ip.getHostAddress());
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        System.out.println("(♥◠‿◠)ノ゙  netty客户端启动成功   ლ(´ڡ`ლ)゙ ");

        NettyClientApplication application = context.getBean(NettyClientApplication.class);
        application.runClient();
    }

    public void runClient() {
        // 异步启动 Netty
        Executors.newSingleThreadExecutor().execute(clientStarter::bootstrap);
    }


}

配置文件

# yml配置netty
netty:
  client:
    boss: 1 # boss线程数量 默认为cpu线程数*2 负责 ServerSocketChannel 上的 accept 事件
    worker: 4 # worker线程数量 默认为cpu线程数*2 负责 socketChannel 上的读写
    timeout: 100000 # 连接超时时间(毫秒)
    port: 6999 # 服务器主端口 默认6999
    host: 127.0.0.1 # 服务器地址 127.0.0.1
    writeTime: 2 # 客户端写入时间 2秒 目前默认发送心跳用
    readTime: 900 # 客户端读取时间 15分钟 900秒
    reconnect: 5 # 重新连接时间 5秒

Config

Netty属性配置

//Netty属性配置
@Data
@Configuration
public class NettyConfig {
    /**
     * boss线程数量 默认为cpu线程数*2
     * 负责 ServerSocketChannel 上的 accept 事件
     */
    @Value("${netty.client.boss}")
    private Integer boss;
    /**
     * worker线程数量 默认为cpu线程数*2
     * 负责 socketChannel 上的读写
     */
    @Value("${netty.client.worker}")
    private Integer worker;
    /**
     * 连接超时时间 默认为30s
     */
    @Value("${netty.client.timeout}")
    private Integer timeout;
    /**
     * 服务器主端口 默认6999
     */
    @Value("${netty.client.port}")
    private Integer port;
    /**
     * 服务器地址 默认为本地
     */
    @Value("${netty.client.host}")
    private String host;
    /**
     * 客户端写入时间 2秒
     * 目前默认发送心跳
     */
    @Value("${netty.client.writeTime}")
    private Integer writeTime;
    /**
     * 客户端读取时间 15分钟
     */
    @Value("${netty.client.readTime}")
    private Integer readTime;
    /**
     * 重新连接时间 5秒
     */
    @Value("${netty.client.reconnect}")
    private Integer reconnect;

}

Handler

AnalyzeMessageHandler: 自定义解码器

//接收到服务器的报文并解析
@Slf4j
public class AnalyzeMessageHandler extends ByteToMessageDecoder {

    private static final NettyServiceImpl nettyService = ApplicationContextHelperUtil.getBean(NettyServiceImpl.class);

    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
        log.info("接受到服务器发来得数据,正在进行解析中...");
        int len = byteBuf.writerIndex();//报文总长度
        // ... 根据需求编写自己的解码逻辑
    }
}

MonitorMessageHandler: 消息处理器

//Netty 客户端监听消息处理器
@Slf4j
@ChannelHandler.Sharable
@RequiredArgsConstructor
public class MonitorMessageHandler extends ChannelInboundHandlerAdapter {

    private static final ClientStarter clientStarter = ApplicationContextHelperUtil.getBean(ClientStarter.class);

    /**
     * 服务端上线的时候调用
     *
     * @param ctx 通道处理程序(上线服务器信息)
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("连上了服务器:" + ctx.channel().remoteAddress());
        super.channelActive(ctx);
    }

    /**
     * 服务端掉线的时候调用
     * 如果发生服务器掉线,等待10秒后重新尝试连接
     *
     * @param ctx 通道处理程序(下线服务器信息)
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws InterruptedException {
        log.info("{}断开了服务器", ctx.channel().remoteAddress());

        // 关闭连接并释放底层的套接字资源
        ctx.channel().close();
        // 优雅关闭功能
        ctx.channel().eventLoop().parent().shutdownGracefully();
        // 清除计数
        ReferenceCountUtil.release(ctx);
        // 重新连接
        clientStarter.bootstrap();
    }

    /**
     * 读取服务端消息
     *
     * @param ctx 通道处理程序(上线服务器信息)
     * @param msg 收到的信息
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ByteBuf mes = (ByteBuf) msg;
        log.info("来自服务端的消息: {}", mes);
    }

    /**
     * 异常发生时候调用
     *
     * @param ctx 通道处理程序(上线服务器信息)
     * @param exc 异常信息
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable exc) {
        log.error("{}出现异常请注意查看:{}", ctx.channel().remoteAddress(), NettyUtil.printStackTrace((Exception) exc));
    }

    /**
     * 用来触发特殊事件
     * 该方法是监听处理ClientHandler类中IdleStateHandler处理器的
     *
     * @param ctx 通道处理程序
     * @param evt 事件信息
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            // 触发了写空闲事件
            if (event.state() == IdleState.WRITER_IDLE) {
                log.info("已2秒未向服务器发消息了,自动发送一条心跳包!");
                ByteBuf message = SendMessage.heartbeatMessage((short) 0);//厂站号默认0
                ctx.writeAndFlush(message);
            }

            // 触发了读空闲事件
            if (event.state().equals(IdleState.READER_IDLE)) {
                log.info("已经15分钟未收到服务器报文,正在进行重新连接!");
                ctx.pipeline().remove(this);//移除当前处理器本身
                ctx.channel().close();//关闭当前通道
                ctx.close();//关闭通道
                clientStarter.bootstrap();
            }

            // 触发了全部空闲事件
            if (event.state() == IdleState.ALL_IDLE) {
                log.info("全部空闲时间超时了!");
                ctx.close();
                clientStarter.bootstrap();
            }
        }
        super.userEventTriggered(ctx, evt);
    }

}

Utils工具类

解决注入空指针

        从Spring的上下文中去获取到类,解决@Autowired注入空指针的问题

/**
 * 从Spring的上下文中去获取到类,解决@Autowired注入空指针的问题
 * @author nld
 * @version 1.0
 */
@Component
public class ApplicationContextHelperUtil implements ApplicationContextAware {
    private static ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext1 ) throws BeansException {
        applicationContext = applicationContext1;
    }

    public static ApplicationContext getApplicationContext(){
        return applicationContext;
    }

    /**
     *
     * @param clazz 需要注入的类
     * @param <T> 泛型
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(Class<T> clazz) {
        return (T) applicationContext.getBean(clazz);
    }
}

 Netty工具类

      用于打印ByteBuf 方便调试,处理异常的工具类,自己编写的一个类,可以自己往里添加

/**
 * 异常打印工具类
 *
 * @author nld
 * @version 1.0
 */
public class NettyUtil {

    //Exceptiony 异常处理
    public static String printStackTrace(Exception e) {
        Writer writer = new StringWriter();
        PrintWriter printWriter = new PrintWriter(writer);
        e.printStackTrace(printWriter);
        printWriter.close();
        return writer.toString();
    }

    //ByteBuf 打印用
    public static void log(ByteBuf buffer) {
        int length = buffer.readableBytes();
        int rows = length / 16 + (length % 15 == 0 ? 0 : 1) + 4;
        StringBuilder buf = new StringBuilder(rows * 80 * 2)
                .append("read index:").append(buffer.readerIndex())
                .append(" write index:").append(buffer.writerIndex())
                .append(" capacity:").append(buffer.capacity())
                .append(StringUtil.NEWLINE);
        ByteBufUtil.appendPrettyHexDump(buf, buffer);
        System.out.println(buf.toString());
    }
}

        以上就是客户端的相关代码,可以直接复制使用,服务器代码见下篇文章,编写博客纯属个人爱好,代码如有不足之处,欢迎各位评论提出优化方案,共勉!感谢!

  • 9
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
很抱歉,作为一名AI语言模型,我无法生成代码。不过我可以给你提供一些参考资料,希望能对你有所帮助。 首先,你需要在Spring Boot项目中引入Netty和WebSocket的依赖,例如: ```xml <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.22.Final</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> ``` 然后,你可以编写一个WebSocket客户端类,例如: ```java @Component public class WebSocketClient { private final WebSocketClientHandshaker handshaker; private WebSocketChannel channel; public WebSocketClient(@Value("${websocket.url}") String url) throws Exception { URI uri = new URI(url); handshaker = WebSocketClientHandshakerFactory.newHandshaker(uri, WebSocketVersion.V13, null, true, new DefaultHttpHeaders()); } public void connect() throws Exception { Bootstrap bootstrap = new Bootstrap(); EventLoopGroup group = new NioEventLoopGroup(); try { bootstrap.group(group) .channel(NioSocketChannel.class) .handler(new WebSocketClientInitializer(handshaker)); ChannelFuture future = bootstrap.connect(handshaker.uri().getHost(), handshaker.uri().getPort()).sync(); channel = ((WebSocketClientHandler) future.channel().pipeline().last()).getChannel(); handshaker.handshake(channel).sync(); } catch (InterruptedException e) { e.printStackTrace(); } finally { group.shutdownGracefully(); } } public void send(String message) { WebSocketFrame frame = new TextWebSocketFrame(message); channel.writeAndFlush(frame); } public void close() { channel.close(); } } ``` 其中,WebSocketClientInitializer是用于初始化Netty的WebSocket客户端的,例如: ```java public class WebSocketClientInitializer extends ChannelInitializer<SocketChannel> { private final WebSocketClientHandshaker handshaker; public WebSocketClientInitializer(WebSocketClientHandshaker handshaker) { this.handshaker = handshaker; } @Override protected void initChannel(SocketChannel channel) throws Exception { channel.pipeline() .addLast(new HttpClientCodec()) .addLast(new HttpObjectAggregator(8192)) .addLast(new WebSocketClientHandler(handshaker)); } } ``` 最后,你可以在Spring Boot的控制器中使用WebSocketClient来与WebSocket服务器进行通信,例如: ```java @RestController public class WebSocketController { @Autowired private WebSocketClient webSocketClient; @PostMapping("/send") public void send(@RequestBody String message) { webSocketClient.send(message); } } ``` 这样你就可以使用Spring BootNetty来实现WebSocket客户端了。希望对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

10JQK炸

如果对您有所帮助,请给点鼓励吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值