《Netty权威指南 第2版》学习笔记(5)---MessagePack编解码

前言

MessagePack是一个高效的二进制序列化框架,它像JSON一样支持不同语言之间的数据交换,但是它的性能更快,序列化之后的码流也更小。

MessagePack的特点如下:

1、编解码高效,性能高
2、序列化之后的码流小
3、支持跨语言

演示案例

引入jar包

        <dependency>
            <groupId>org.msgpack</groupId>
            <artifactId>msgpack</artifactId>
            <version>0.6.12</version>
        </dependency>

服务端


import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class EchoServer {
    
    
    public void bind(int port) throws InterruptedException {
    
    
        EventLoopGroup boss = new NioEventLoopGroup();
        EventLoopGroup worker = new NioEventLoopGroup();
        try {
    
    
            ServerBootstrap b = new ServerBootstrap();
            b.group(boss, worker)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
    
    
                        protected void initChannel(SocketChannel ch) throws Exception {
    
    
                            /* MessagePack编码器 继承ChannelOutboundHandlerAdapter */
                            ch.pipeline().addLast(new MsgPackEncoder());
                            /* MessagePack解码器 继承ChannelInboundHandlerAdapter */
                            ch.pipeline().addLast(new MsgPackDecoder());
                            ch.pipeline().addLast(new EchoServerHandler());
                        }
                    });
            ChannelFuture future = b.bind(port).sync();
            future.channel().closeFuture().sync();
        } finally {
    
    
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws InterruptedException {
    
    
        new EchoServer().bind(8088);
    }
}

EchoServerHandler

import com.wyl.netty.code6.UserInfo;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class EchoServerHandler extends ChannelInboundHandlerAdapter {
    
    
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
        /*
        msg已经通过前一个MsgPackDecoder被反序列化为UserInfo对象,
        所以EchoServerHandler直接强转为UserInfo即可
         */
        UserInfo u = (UserInfo) msg;
        System.out.println("server receive the msgPack message " + u);
        //服务端也响应客户端,发送一个UserInfo对象
        UserInfo userInfo = new UserInfo();
        userInfo.setUserName("i`m server");
        userInfo.setAge(20);
        ctx.writeAndFlush(userInfo);
    }

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

客户端


import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

public class EchoClient {
    
    

    public void client(int port, String host) throws InterruptedException {
    
    
        EventLoopGroup 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 {
    
    
                            /* MessagePack编码器 继承ChannelOutboundHandlerAdapter */
                            ch.pipeline().addLast(new MsgPackEncoder());
                            /* MessagePack解码器 继承ChannelInboundHandlerAdapter */
                            ch.pipeline().addLast(new MsgPackDecoder());
                            ch.pipeline().addLast(new EchoClientHandler());

                        }
                    });
            ChannelFuture future = bootstrap.connect(host, port).sync();
            future.channel().closeFuture().sync();
        } finally {
    
    
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws InterruptedException {
    
    
        new EchoClient().client(8088, "localhost");
    }

}

EchoClientHandler


import com.wyl.netty.code6.UserInfo;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class EchoClientHandler extends ChannelInboundHandlerAdapter {
    
    
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    
    
        /*
        客户端发送UserInfo对象
         */
        UserInfo userInfo = new UserInfo();
        userInfo.setAge(18);
        userInfo.setUserName("hello i`m client");
        for (int i = 0; i < 1; i++) {
    
    
            ctx.write(userInfo);
        }
        ctx.flush();
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
        /*
        客户端接收服务端发来内容
         */
        UserInfo u = (UserInfo) msg;
        System.out.println("clint receive the msgPack message : " + u);
    }
}

解码器


import com.wyl.netty.code6.UserInfo;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
import org.msgpack.MessagePack;

import java.util.List;

public class MsgPackDecoder extends MessageToMessageDecoder<ByteBuf> {
    
    
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
    
    
        //从msg中获取可读的字节内容,并读取到字节数组中
        int len = msg.readableBytes();
        byte[] array = new byte[len];
        msg.getBytes(msg.readerIndex(), array, 0, len);

        /*
        通过MessagePack的read方法,将字节数组反序列化为UserInfo对象。
        然后将UserInfo对象添加到解码列表中。
         */
        MessagePack messagePack = new MessagePack();
        out.add(messagePack.read(array, UserInfo.class));
    }
}

编码器


import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import org.msgpack.MessagePack;

public class MsgPackEncoder extends MessageToByteEncoder<Object> {
    
    
    protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
    
    
        /* 通过MessagePack将UserInfo对象序列化为字节数组并写入ByteBuf中 */
        MessagePack messagePack = new MessagePack();
        byte[] raw = messagePack.write(msg);
        out.writeBytes(raw);
    }
}

UserInfo对象

需要添加@Message,表示支持MessagePack序列化


import org.msgpack.annotation.Message;

@Message
public class UserInfo {
    
    

    private String userName;

    private int age;

    public void setUserName(String userName) {
    
    
        this.userName = userName;
    }

    public void setAge(int age) {
    
    
        this.age = age;
    }

    public String getUserName() {
    
    
        return userName;
    }

    public int getAge() {
    
    
        return age;
    }

    @Override
    public String toString() {
    
    
        return "UserInfo{" +
                "userName='" + userName + '\'' +
                ", age=" + age +
                '}';
    }

}

运行结果

服务端
在这里插入图片描述
客户端
在这里插入图片描述

粘包、半包问题

修改客户端发送次数
在这里插入图片描述

因为产生粘包、半包问题,导致序列化报错

警告: An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
io.netty.handler.codec.DecoderException: org.msgpack.MessageTypeException: Expected array, but got integer value

在这里插入图片描述

支持粘包、半包处理

修改EchoClient ,添加LengthFieldBasedFrameDecoder、LengthFieldPrepender


import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;

public class EchoClient {
    
    

    public void client(int port, String host) throws InterruptedException {
    
    
        EventLoopGroup 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 {
    
    
                            /*
                            自定义长度解码器,需要放在入栈消息链的头部
                            65535, 0, 2, 0, 2含义
                            int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip
                            maxFrameLength 65535:数据包最大长度
                            lengthFieldOffset 0:长度域的偏移量
                            lengthFieldLength 2:长度域本身的长度
                            lengthAdjustment 0:修正值,公式:(数据包的长度-lengthFieldOffset-lengthFieldLength-长度域的值)
                            initialBytesToStrip 2:从数据包中跳过的字节数
                             */
                            ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2));
                            /* 在ByteBuf之前增加2个字节的消息长度字段 */
                            ch.pipeline().addLast(new LengthFieldPrepender(2));
                            /* MessagePack编码器 */
                            ch.pipeline().addLast(new MsgPackEncoder());
                            /* MessagePack解码器 */
                            ch.pipeline().addLast(new MsgPackDecoder());
                            ch.pipeline().addLast(new EchoClientHandler());

                        }
                    });
            ChannelFuture future = bootstrap.connect(host, port).sync();
            future.channel().closeFuture().sync();
        } finally {
    
    
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws InterruptedException {
    
    
        new EchoClient().client(8088, "localhost");
    }

}

同样修改EchoServer

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;

public class EchoServer {
    
    
    public void bind(int port) throws InterruptedException {
    
    
        EventLoopGroup boss = new NioEventLoopGroup();
        EventLoopGroup worker = new NioEventLoopGroup();
        try {
    
    
            ServerBootstrap b = new ServerBootstrap();
            b.group(boss, worker)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
    
    
                        protected void initChannel(SocketChannel ch) throws Exception {
    
    
                            /*
                            参数含义同客户端
                             */
                            ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2));
                            ch.pipeline().addLast(new LengthFieldPrepender(2));
                            ch.pipeline().addLast(new MsgPackEncoder());
                            ch.pipeline().addLast(new MsgPackDecoder());
                            ch.pipeline().addLast(new EchoServerHandler());
                        }
                    });
            ChannelFuture future = b.bind(port).sync();
            future.channel().closeFuture().sync();
        } finally {
    
    
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws InterruptedException {
    
    
        new EchoServer().bind(8088);
    }
}

运行结果

客户端、服务端恢复正常通信

在这里插入图片描述

在这里插入图片描述

入栈、出站过程梳理

服务端入栈处理过程:

LengthFieldBasedFrameDecoder、MsgPackDecoder、EchoServerHandler
先处理粘包、半包问题,然后进行对象反序列化处理,最后业务EchoServerHandler读取

服务端出站处理过程:

MsgPackEncoder、LengthFieldPrepender
先进行对象序列化处理,然后指定长度域的长度

客户端入栈、出站过程与服务端类似。

猜你喜欢

转载自blog.csdn.net/CSDN_WYL2016/article/details/114265383