Netty进阶篇——协议设计与解析

本文展示了如何使用JavaNIO进行网络通信,包括通过Redis协议设置键值对,使用HTTP协议建立服务器并响应请求,以及设计和实现一个自定义协议,涉及编码解码过程。示例代码中包含了协议格式、编解码器的使用以及防止半包粘包问题的方法。

1、测试使用redis协议进行与redis的通信

Reids通信协议格式是:

set name zhangsan

*3 三个部分组成:命令、key、value

$3

set

$8

zhangsan

代码示例

public static void main(String[] args) {
        final byte[] LINE = {13,10};
        NioEventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new LoggingHandler());
                            ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
                                @Override
                                public void channelActive(ChannelHandlerContext ctx) throws Exception {
                                    ByteBuf buf = ctx.alloc().buffer();
                                    buf.writeBytes("*3".getBytes());
                                    buf.writeBytes(LINE);
                                    buf.writeBytes("$3".getBytes());
                                    buf.writeBytes(LINE);
                                    buf.writeBytes("set".getBytes());
                                    buf.writeBytes(LINE);
                                    buf.writeBytes("$4".getBytes());
                                    buf.writeBytes(LINE);
                                    buf.writeBytes("name".getBytes());
                                    buf.writeBytes(LINE);
                                    buf.writeBytes("$8".getBytes());
                                    buf.writeBytes(LINE);
                                    buf.writeBytes("zhangsan".getBytes());
                                    buf.writeBytes(LINE);
                                    ctx.writeAndFlush(buf);
                                }
                                @Override
                                public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                    ByteBuf buf = (ByteBuf) msg;
                                    System.out.println(buf.toString(Charset.defaultCharset()));
                                }
                            });
                        }
                    });
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1",6379).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            group.shutdownGracefully();
        }
    }

2、HTTP协议进行通信

  • HttpServerCodec() 对HTTP协议请求进行解析,包括了接收和发送
    • 结尾为Codec代表接收和发送
  • SimpleChannelInboundHandler() 表示接收特定格式的HTTP请求,可以避免进一步去判断是什么请求
public static void main(String[] args) {
        NioEventLoopGroup boss = new NioEventLoopGroup();
        NioEventLoopGroup worker = new NioEventLoopGroup();
        ServerBootstrap bootstrap = new ServerBootstrap();
        try {
            bootstrap.group(boss,worker)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new LoggingHandler());   //日志打印
                            ch.pipeline().addLast(new HttpServerCodec());  //接收HTTP协议的解析器
                            ch.pipeline().addLast(new SimpleChannelInboundHandler<HttpRequest>() {  //接收特定格式的HTTP协议的解析器
                                @Override
                                protected void channelRead0(ChannelHandlerContext ctx, HttpRequest msg) throws Exception {
//                                   获取请求头
                                    log.info("{}",msg.uri());

//                                    返回浏览器的响应对象
                                    DefaultFullHttpResponse response =
                                            new DefaultFullHttpResponse(msg.protocolVersion(), HttpResponseStatus.OK);
                                    byte[] bytes = ("<h1>Hello, world!</h1></br><h2>"+new Date() +"</h2>").getBytes();
//                                      设置返回的消息长度,避免浏览器一直继续等待响应
                                    response.headers().setInt(CONTENT_LENGTH,bytes.length);
                                    response.content().writeBytes(bytes);
                                    ctx.writeAndFlush(response);
                                }
                            });
//                            不设置接收http格式时,则需要对不同的请求头进行判断,分别处理
/*                            ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
                                @Override
                                public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                    log.debug("******请求类型:{} **********",msg.getClass());
                                    if (msg instanceof HttpRequest){  //请求头
                                    }else if (msg instanceof HttpContent) //请求体
                                    super.channelRead(ctx, msg);
                                }
                            });*/
                        }
                    });
            ChannelFuture channelFuture = bootstrap.bind(8888).sync();
            channelFuture.channel().closeFuture().sync();
            
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }
    }

3、自定义协议

要素

  • 魔数:用来在第一时间判断是否无效数据包
  • 版本号:可以支持协议的升级
  • 序列化算法:消息正文到底采用哪种序列化方式,可以由此扩展:例如:json、protobuf、jdk
  • 指令类型:是登录、注册群聊。。。跟业务相关
  • 请求序号:为了双工通信、提供异步能力
  • 正文长度
  • 消息正文:json、xml、对象流….

固定字节一般需要设计成2的n次方

常用编解码器:

  • MessageToMessageCodec **:**可以用于在多线程环境下多个channel共享使用,可以使用Sharable标识
  • ByteToMessageCodec:不可以用与在多线程环境下多个channel共享使用

两者在使用时都需要配合new LengthFieldBasedFrameDecoder()一起使用,去避免出现半包粘包问题

代码实例

@Slf4j
public class MessageCodeC extends ByteToMessageCodec<Message> {
//      编码
    @Override
    protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception {
//        1、4个字节的魔数
        out.writeBytes(new byte[]{1, 2, 3, 4});
//        2、1个字节的版本
        out.writeByte(1);
//        3、1字节的序列化算法 jdk:1,json:2
        out.writeByte(0);
//        4、1字节的指令类型
        out.writeByte(msg.getMessageType());
//        5、4个字节请求序列
        out.writeInt(msg.getSequenceId());
//        无意义填充
        out.writeByte(0xff);

//              把对象转为byte数组
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(msg);
        byte[] bytes = bos.toByteArray();

//        7、4个字节的正文长度
        out.writeInt(bytes.length);
//        8、获取内容的字节数组
        out.writeBytes(bytes);
    }

//    解码
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        int magicNum = in.readInt();
        byte version = in.readByte();
        byte serializerType = in.readByte();
        byte messageType = in.readByte();
        int sequenceId = in.readInt();
        in.readByte();
        int length = in.readInt();
        byte[] bytes = new byte[length];
        in.readBytes(bytes, 0, length);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
        Message message = (Message) ois.readObject();
        log.debug("{}, {}, {}, {}, {}, {}", magicNum, version, serializerType, messageType, sequenceId, length);
        log.debug("{}", message);
        out.add(message);
    }
}

测试使用

public static void main(String[] args) {
        NioEventLoopGroup boss = new NioEventLoopGroup();
        NioEventLoopGroup whrker = new NioEventLoopGroup();
        LoggingHandler logging = new LoggingHandler();
        MessageCodeC message = new MessageCodeC();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap()
                    .group(boss, whrker)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {

                            ch.pipeline().addLast(logging);
                            ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(
                                    1024, 12, 4, 0, 0));
                            ch.pipeline().addLast(message);
                        }
                    });
            ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
            channelFuture.channel().closeFuture().sync();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            boss.shutdownGracefully();
            whrker.shutdownGracefully();
        }
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值