如何制作一个Netty的springboot-starter

文章介绍了如何制作一个Netty的SpringBoot-Starter,包括内嵌的HTTP、WebSocket和自定义Socket协议的配置与数据处理,以及心跳机制和断线重连的实现。此外,还讨论了客户端的使用和后续可能的改进方向。
摘要由CSDN通过智能技术生成


最近出于学习的目的,想整个springboot-starter玩玩,然后又正好在玩netty,所以就决定搞一个netty-springboot-starter,目的主要是学习,初步设想有以下功能:

  • 可以通过配置一键启动服务端或者客户端
  • 通过注解就可以实现自己的数据处理Handler
  • 内置协议,可以通过配置进行切换如:http、webSocket、自定义协议等
  • 内置心跳机制、重连机制

目前以上功能基本实现,接下来看看功能使用介绍

一、使用

我们以服务端来举例,可打包到本地仓库引入使用

服务端

需要配置启动参数,协议那里如果不自定义则必填否则服务端不会启动(怎么自定义后面说),下面我们介绍内置的三种协议的配置启动

在这里插入图片描述

内嵌协议

HTTP

http-config为http参数配置项,不配也可以有默认值

配置如下:

在这里插入图片描述

数据处理类自己编写,如同netty一样,只需要打上 @NettyServerHandler 注解即可,如:

@NettyServerHandler
public class HttpBaseHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

    private final static Logger log= LoggerFactory.getLogger(HttpBaseHandler.class);


    @Resource
    private ApplicationEventPublisher applicationContext;

    public HttpBaseHandler(ApplicationEventPublisher applicationContext){
        this.applicationContext=applicationContext;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, FullHttpRequest t) throws Exception {
        log.info("http请求 uri:{},method:{}",t.uri(),t.method());

        DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
        response.content().writeBytes(JSON.toJSONBytes("Hello"));
        response.headers().add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON+";charset=UTF-8");
        response.headers().add(HttpHeaderNames.CONTENT_LENGTH,response.content().readableBytes());
        channelHandlerContext.writeAndFlush(response);
    }
}

启动springboot项目测试:

在这里插入图片描述

打开浏览器,访问127.0.0.1:8888,就可以看到请求结果了

在这里插入图片描述

在这里插入图片描述

WebSocket

由于WebSocket也同样会采用Http编解码,所以http的配置也同时会满足WebSocket,一样都有默认值,可以不配置

配置如下:

在这里插入图片描述

数据处理类自己编写,如同netty一样,只需要打上 @NettyServerHandler 注解即可,如:

@NettyServerHandler
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    private final static Logger log= LoggerFactory.getLogger(WebSocketHandler.class);

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        super.channelActive(ctx);
        log.info("建立连接");
    }

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {
        log.info("收到消息:"+textWebSocketFrame.text());
        channelHandlerContext.writeAndFlush(new TextWebSocketFrame(textWebSocketFrame.text()));
    }
}

我们准备一个WebSocket的客户端 html :

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html";charset="UTF-8" />
    <title>Netty WebSocket测试</title>
    <script type="text/javascript">
        var socket;
        if (!window.WebSocket) {
            window.WebSocket = window.MozWebSocket;
        }
        if (window.WebSocket) {
            socket = new WebSocket("ws://localhost:8888/ws");
            socket.onmessage = function (event) {
                var ta = document.getElementById('responseText');
                ta.value = "";
                ta.value=event.data;
            };
            socket.onopen = function (event) {
                var ta = document.getElementById('responseText');
                ta.value = "已连接";
            };

            socket.onclose = function (event) {
                var ta = document.getElementById('responseText');
                ta.value = "已关闭";
            };
        } else {
            alert("您的浏览器不支持WebSocket协议!");
        }

        function send(message) {
            if (!window.WebSocket) {
                return;
            }
            if (socket.readystate = WebSocket.OPEN) {
                socket.send(message);
            } else {
                alert("WebSocket连接没有建立成功!");
            }
        }

    </script>
</head>
<body>
<form onSubmit="return false;" ;>
    <hr color="black"/>
    <h3>客户端发送的信息</h3>
    <label>消息</label><input type="text" name="uid" value="Hello Server!"/><br/>
    <br/><input type="button" value="发送消息" οnclick="send(this.form.uid.value)"/>
    <hr color="black"/>
    <h3>服务端返回的应答消息</h3>
    <textarea id="responseText" style="width:900px;height:300px;"></textarea>
</form>
</body>
</html>

启动springboot项目测试:

在这里插入图片描述

在这里插入图片描述

Socket

普通的长连接也是需要自定义协议的,这里我内嵌了一个自己的协议,也就是博客里面写的那一套

配置如下:

在这里插入图片描述

数据处理类自己编写,如同netty一样,只需要打上 @NettyServerHandler 注解即可,如:

@NettyServerHandler
public class MyConfigHandler extends ChannelInboundHandlerAdapter {

    private static final Logger log = LoggerFactory.getLogger(MyConfigHandler.class);


    // 读取信息调用
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 和NIO一样有缓冲区 ByteBuf就是对ByteBuffer做了一层封装
        NettyBody msg1 = (NettyBody) msg;

        // 读取数据
        log.info("客户端信息:" + JSON.toJSON(msg1));
    }

    // 连接事件 连接成功调用
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        SocketAddress socketAddress = ctx.channel().remoteAddress();
        log.info(socketAddress + " 已连接");

        // 发送数据
        ctx.writeAndFlush(new NettyMsg(ServiceCodeEnum.TEST_TYPE,"Hello Client!"));
    }

    // 断开连接调用
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.info(ctx.channel().remoteAddress() + " 已断开连接");
    }

    // 读取信息完成事件  信息读取完成后调用
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {

    }

    // 异常处理  发生异常调用
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.error("全局异常信息 ex={}", cause.getMessage(), cause);
        ctx.close();
    }
}

启动springboot项目测试:

在这里插入图片描述

自定义

内置的协议就以上三种,最后一个就是默认的自定义,这个只会加入被注解标识的Handler,无任何内置的编解码或者Handler

在这里插入图片描述

心跳机制

http协议是不需要心跳的,webSocket和socket两种都已经内嵌了心跳的处理了,我们只需要配置一下,然后监听心跳事件就可以了

配置

此配置代表5s触发一次读事件的空闲检测

在这里插入图片描述

事件

这里将Netty的空间检测事件转化为了spring中的事件机制,所以我们需要监听NettyServerIdleEvent事件,并将这个Listener注入到spring容器中,如下:

@Component
public class NettyServerHeartTestListener {
    private static final Logger log = LoggerFactory.getLogger(NettyServerHeartTestListener.class);

    public NettyServerHeartTestListener() {
    }

    @EventListener({NettyServerIdleEvent.class})
    public void serverIdleHandler(NettyServerIdleEvent event) {
        NettyServerNettyIdleEntity source = (NettyServerNettyIdleEntity) event.getSource();
        // 获取心跳配置
        NettyServerHeartConfig serverHeartConfig = source.getServerHeartConfig();
        // 获取netty管道 上下文
        ChannelHandlerContext ctx = source.getCtx();
        // 获取触发的事件
        IdleState state = source.getEvt().state();
        log.info("心跳事件触发:" + source.getEvt().state());
    }
}
结果

可以看到在没消息接收的时候,每5s就会触发一次

在这里插入图片描述

数据处理编排

我们知道Netty中数据处理类都是被放入管道中的,可以多个,类似责任链模式,所以我们自己的数据处理类怎么编排呢?主要靠 @NettyServerHandler 注解,里面有个order字段设置排序,被该注解标记的Handler最终都会被收集,并根据Order字段来排序,依次加入到Netty的管道中,order越小,越先加入

如何自定义

我们看一下一个常规的Netty服务端启动,大致可分为三个部分:

  1. 启动配置
  2. 数据处理器设置
  3. 管道处理设置

在这里插入图片描述

关于第一个部分现在只做了简单的配置:端口、线程数,option的配置还没做

关于第二个部分,数据处理器,我们是可以直接将这块替换的

继承NettyServerHandlerInitializer类,并注入到容器中即可替换,如下:

@Component
public class TestInit extends NettyServerHandlerInitializer {


    // nettyServerConfig        服务端的配置类 
    // context                  Spring容器上下文
    // nettyServerChannelInit   内置的一些协议初始化方法
    public TestInit(NettyServerConfig nettyServerConfig, ApplicationContext context, NettyServerChannelInit nettyServerChannelInit) {
        super(nettyServerConfig, context, nettyServerChannelInit);
    }

    @Override
    protected void initChannel(Channel channel) throws Exception {
//        super.initChannel(channel);
        // 这里就可以做自己的处理
    }
}

关于第三个部分,管道处理类的设置,现在是有内置的一些协议,当然也是支持完成自定义的

实现NettyServerChannelInit接口,并注入到容器中就可以了,如下:

NettyServerChannelInit接口有两个方法:

  • addChannelHandlers:默认实现的,该方法就是获取容器中被NettyServerHandler注解标记的所有Handler并按照排序加入Channel中
  • initChannelHandlers:这个就是需要自己实现的,可以调用addChannelHandlers方法,也可以完全自己添加
@Component
public class TestInit implements NettyServerChannelInit {


    @Override
    public void initChannelHandlers(Channel channel, NettyServerConfig nettyServerConfig, ApplicationContext context) {
        
    }
}

总结

可以看到关于数据处理方面,有三个方向可以拓展自定义的:

  1. 配置coding-type: default
  2. 替换ChannelInitializer
  3. 自定义Channel的管道处理

客户端

客户端的使用配置基本是复刻的服务端,就是在协议方面,没有Http和Websocket了,只有内置的一个自定义协议,同样的只需要配置就能直接启动

在这里插入图片描述

与服务端不同的点:

  • 注解方面:客户端为 @NettyClientHandler
  • 数据处理器自定义:客户端为NettyClientHandlerInitializer
  • 管道数据处理自定义:客户端为NettyClientChannelInit
  • 心跳检测事件:客户端为NettyClientIdleEvent

断线重连机制

由于是客户端断线重连是必不可少的,所以内置了断线重连机制(也就是博客中说的那种)

配置如下(有默认值不配也行):

在这里插入图片描述

看看效果:

启动连接失败,重连:

在这里插入图片描述

中途断线重连:

在这里插入图片描述

二、后续

目前这只是个初版,为了增加Netty移植性以及简化Netty在Springboot项目中的操作,肯定有很多欠缺的地方(说不定还有bug),望大佬们多多建议,后续可能会加入以下功能:

  • 内置几种加解密手段
  • Option配置方面的完善
  • 增加黑白名单校验
  • 增加多种主流协议的支持
  • 增加集群方面的支持和拓展
  • 内置多几种管道数据处理器,如文件传输、图片传输等

三、源码地址

链接: 源码地址
大佬有问题轻点喷!多建议

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值