Netty实战系列一之多协议并存

前言

前面写了好多关于源码的文章,我觉得还是要来点实战的,解决一些问题,最近想用Netty写个游戏服务器框架,已经写了一点了,我想让一个netty进程一个端口可以响应多个协议,比如我希望一个端口就可以响应HTTP,WebSocketTCP私有协议。如果对这些还不了解的可以看下我写的源码文章,基本都有讲了,至于私有协议,我这里也会给出例子。

解决问题的思路

  1. 开两个netty进程,一个进程相应一个。
  2. 一个进程绑定多个端口,不同的端口处理不同的协议。
  3. 一个进程一个端口,分析协议头来判断不同的协议,动态添加处理器。

前两个应该比较简单,我们来简单的实现下第三个吧。

主服务类

没别的设置,就是基本模板,但是只有一个处理器,叫ProtocolSelectorHandler,这个就是我们来解析不同协议做动态添加的关键。

public class MyTestServer {
    public static void main(String[] args) throws Exception{
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {

            ServerBootstrap serverBootstrap = new ServerBootstrap();

            serverBootstrap.group(bossGroup, workerGroup);
            serverBootstrap.channel(NioServerSocketChannel.class);
            serverBootstrap.childOption(ChannelOption.TCP_NODELAY,true);

            serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {

                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ChannelPipeline pipeline = ch.pipeline();
                    pipeline.addLast(new ProtocolSelectorDecoder());

                }
            });

            //启动服务器
            ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
            channelFuture.channel().closeFuture().sync();

        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

ProtocolSelectorHandler协议选择器

这个就是实现动态协议的关键,其实也只是简单的判断前几个字节是什么,也就是要一些约定,比如说约定好WebSocket的话,URI就是/ws,如果看到GET /ws前缀的话,就说明要进行WebSocket握手了。我就添加WebSocket的处理器,重新来处理这个数据,然后把协议选择器删除,当然这里是简单的实现,可能期间出现问题,你把协议选择器删了就惨了,我们暂时不管哈哈。如果是自定义协议的话,我们一般不会传空格,所以我可以用空格来区别自定义协议和HTTP协议,HTTP请求行有空格嘛。当然我也只是简单的实现,如果要较真,还是把判断换行,然后看是不是HTTP协议比较好。

/**
 * 协议选择器,支持动态协议HTTP WEBSOCKET TCP私有协议
 * Author: wangwei
 */
public class ProtocolSelectorHandler extends ByteToMessageDecoder {
    /**
     * websocket定义请求行前缀
     */
    private static final String WEBSOCKET_LINE_PREFIX = "GET /ws";
    /**
     * websocket的uri
     */
    private static final String WEBSOCKET_PREFIX = "/ws";
    /**
     * 检查10个字节,没有空格就是自定义协议
     */
    private static final int SPACE_LENGTH = 10;

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        System.out.println("before :" + ctx.pipeline().toString());
        if (isWebSocketUrl(in)) {
            System.out.println("addWebSocketHandlers");

            addWebSocketHandlers(ctx.pipeline());
        } else if (isCustomProcotol(in)) {
            System.out.println("addTCPProtocolHandlers");

            addTCPProtocolHandlers(ctx.pipeline());
        } else {
            System.out.println("addHTTPHandlers");
            addHTTPHandlers(ctx.pipeline());
        }
        ctx.pipeline().remove(this);
        System.out.println("after :" + ctx.pipeline().toString());
    }

    /**
     * 是否有websocket请求行前缀
     *
     * @param byteBuf
     * @return
     */
    private boolean isWebSocketUrl(ByteBuf byteBuf) {
        if (byteBuf.readableBytes() < WEBSOCKET_LINE_PREFIX.length()) {
            return false;
        }
        byteBuf.markReaderIndex();
        byte[] content = new byte[WEBSOCKET_LINE_PREFIX.length()];
        byteBuf.readBytes(content);
        byteBuf.resetReaderIndex();
        String s = new String(content, CharsetUtil.UTF_8);
        return s.equals(WEBSOCKET_LINE_PREFIX);
    }

    /**
     * 是否是自定义是有协议
     * @param byteBuf
     * @return
     */
    private boolean isCustomProcotol(ByteBuf byteBuf) {
        byteBuf.markReaderIndex();
        byte[] content = new byte[SPACE_LENGTH];
        byteBuf.readBytes(content);
        byteBuf.resetReaderIndex();
        String s = new String(content, CharsetUtil.UTF_8);
        return s.indexOf(" ") == -1;
    }

    /**
     * 动态添加WebSocket处理器
     * @param pipeline
     */
    private void addWebSocketHandlers(ChannelPipeline pipeline) {
        pipeline.addLast(new HttpServerCodec());
        pipeline.addLast(new HttpObjectAggregator(8192));
        pipeline.addLast(new WebSocketServerProtocolHandler(WEBSOCKET_PREFIX));
        pipeline.addLast(new MyTextWebSocketFrameHandler());
    }
    /**
     * 动态添加TCP私有协议处理器
     * @param pipeline
     */
    private void addTCPProtocolHandlers(ChannelPipeline pipeline) {
        pipeline.addLast(new CustomDecoder(1024, 1, 4));//这里1代表长度属性是从索引1位置开始的,4代表有4个字节的长度
        pipeline.addLast(new CutsomServerHandler());
    }


    /**
     * 动态添加HTTP处理器
     * @param pipeline
     */
    private void addHTTPHandlers(ChannelPipeline pipeline) {
        pipeline.addLast(new HttpServerCodec());
        pipeline.addLast(new HttpObjectAggregator(8192));
        pipeline.addLast(new MyHttpServerHandler());


    }
}

MyTextWebSocketFrameHandler服务端处理websocket

这个很简单,就是收到了返回一下。

public class MyTextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame>{
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {

        System.out.println(String.format("收到websocket客户端[%s]消息:", ctx.channel().remoteAddress()+":"+ctx.channel().id().asLongText()) + msg.text());
        ctx.channel().writeAndFlush(new TextWebSocketFrame(  msg.text()));
    }


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("异常发生:" + cause.getMessage());
        ctx.close();
    }
}

MyHttpServerHandler服务端处理http

也是简单的打印下收到的信息,没有回复。

public class MyHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
        if (msg instanceof FullHttpRequest) {

            FullHttpRequest httpRequest = (FullHttpRequest) msg;
            ByteBuf content1 = httpRequest.content();
            String cont = content1.toString(CharsetUtil.UTF_8);
            System.out.println(String.format("收到HTTP客户端[%s]消息", ctx.channel().remoteAddress()+":"+ctx.channel().id().asLongText())  + ":" + cont);

        }

    }

}

自定义协议

SimpleProtocol协议

首先定义了要传递的协议格式,这个就是用TCP字节流直接来传的。

@Data
public class SimpleProtocol {
    /**
     * 协议类型
     */
    private byte protocolType;
    /**
     * 消息体长度
     */
    private int bodyLength;
    /**
     * 消息内容
     */
    private byte[] body;
}

CustomEncoder自定义协议编码器

自定义协议当然需编解码啦,最简单的实现:

public class CustomEncoder extends MessageToByteEncoder<SimpleProtocol> {

    @Override
    protected void encode(ChannelHandlerContext ctx, SimpleProtocol simpleProtocol, ByteBuf out) throws Exception {
        byte protocolType = simpleProtocol.getProtocolType();
        int length = simpleProtocol.getBodyLength();
        byte[] body = simpleProtocol.getBody();
        out.writeByte(protocolType);
        out.writeInt(length);
        if(length>0){
            out.writeBytes(body);
        }
    }
}

CustomDecoder自定义协议解码器

我偷懒了直接集成LengthFieldBasedFrameDecoder,内部处理了粘包拆包问题,这里就注意要记得处理要完释放缓冲区。

public class CustomDecoder extends LengthFieldBasedFrameDecoder {
    

    public CustomDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength) {
        super(maxFrameLength, lengthFieldOffset, lengthFieldLength);
    }
    
    @Override
    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        ByteBuf byteBuf = (ByteBuf) super.decode(ctx, in);
        if (byteBuf == null) {
            return null;
        }
        SimpleProtocol simpleProtocol = new SimpleProtocol();
        simpleProtocol.setProtocolType(byteBuf.readByte());
        int length = byteBuf.readInt();
        simpleProtocol.setBodyLength(length);
        if (simpleProtocol.getBodyLength() > 0) {
            byte[] body = new byte[length];
            byteBuf.readBytes(body);
            simpleProtocol.setBody(body);
        }
        in.release();//记得释放
        return simpleProtocol;
    }

}

CutsomServerHandler服务端自定义协议处理器

也是简单的打印下收到的信息。

public class CutsomServerHandler extends SimpleChannelInboundHandler<SimpleProtocol> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, SimpleProtocol msg) throws Exception {
        System.out.println(String.format("收到TCP私有协议客户端[%s]消息:", ctx.channel().remoteAddress()+":"+ctx.channel().id().asLongText()) +new String(msg.getBody(), CharsetUtil.UTF_8));
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }

}

验证结果

基本的都写了,我们看看执行的结果吧。先HTTP
在这里插入图片描述
然后上WebSocket,看看两个一起怎么样。
在这里插入图片描述
再加自定义协议。
在这里插入图片描述
其实只要你理解了HTTPWebSocket协议是怎么解析的,TCP私有协议怎么解析,自然知道怎么去区分啦,因为他们都是基于TCP的,你能拿到TCP的字节数据,就有办法判断是哪种协议,所有的应用层的协议都可以处理,当然我这里是简单的实现了3个,你可以扩展N个。

源码:netty_action

更多参考:
https://cloud.tencent.com/developer/article/1366184

好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。

评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值