Netty中实现MessagePack编解码器以及解决粘包问题-参考netty权威指南2

首先maven需要增加依赖

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

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

实现编码器类 MsgpackEncoder,注意这里是需要继承MessageToByteEncoder类,重写它的encode方法, 这的Object表示对Object类型进行编码,这里实际直接拿来用就行了

package io.netty.handler.codec.msgpack;

import org.msgpack.MessagePack;

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

public class MsgpackEncoder extends MessageToByteEncoder<Object> {

	@Override
	protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
		MessagePack msgPack = new MessagePack();
		byte[] raw = msgPack.write(msg);
		out.writeBytes(raw);
	}
}

实现解码器类 MsgpackDecoder

package io.netty.handler.codec.msgpack;

import java.util.List;

import org.msgpack.MessagePack;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;

public class MsgpackDecoder extends MessageToMessageDecoder<ByteBuf> {

	@Override
	protected void decode(ChannelHandlerContext ctx, ByteBuf msg, 
			List<Object> out) throws Exception {
		
		final int length = msg.readableBytes();
		final byte[] array = new byte[length];
		msg.getBytes(msg.readerIndex(), array, 0, length);
		MessagePack msgPack = new MessagePack();
		out.add(msgPack.read(array));
	}
}

编写客户端的EchoClientHandler

package io.netty.handler.codec.msgpack;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class EchoClientHandler extends ChannelInboundHandlerAdapter {

    // sendNumber为写入发送缓冲区的对象数量
    private int sendNumber;

    public EchoClientHandler(int sendNumber) {
        this.sendNumber = sendNumber;
    }

    /**
     * 构建长度为UserInfoNum的UserInfo对象数组
     * @param UserInfoNum
     * @return
     */
    private UserInfo[] getUserInfoArray(int UserInfoNum) {
        UserInfo[] userInfos = new UserInfo[UserInfoNum];
        UserInfo userInfo = null;
        for(int i = 0; i < UserInfoNum; i++) {
        	userInfo = new UserInfo();
        	userInfo.setName("ABCDEFG --->" + i);
        	userInfo.setAge(i);
            userInfos[i] = userInfo;
        }
        return userInfos;
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        UserInfo[] userInfos = getUserInfoArray(sendNumber);
        for (UserInfo UserInfo : userInfos) {
            ctx.write(UserInfo);
        }
        ctx.flush();
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("Client receive the msgpack message : " + msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

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

}

编写客户端的EchoClient

package io.netty.handler.codec.msgpack;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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 connect(String host, int port, final int sendNumber) throws Exception {
        // 配置客户端NIO线程组
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group).channel(NioSocketChannel.class)
                .option(ChannelOption.TCP_NODELAY, true)
                // 设置TCP连接超时时间
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000)
                .handler(new ChannelInitializer<SocketChannel>() {

                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                    	ch.pipeline().addLast("frameDecoder", new 
                    			LengthFieldBasedFrameDecoder(65525, 0, 2, 0, 2));
                        // 添加MesspagePack解码器
                        ch.pipeline().addLast("msgpack decoder", new MsgpackDecoder());
                        
                        ch.pipeline().addLast("frameEncoder", new LengthFieldPrepender(2));
                        // 添加MessagePack编码器
                        ch.pipeline().addLast("msgpack encoder", new MsgpackEncoder());
                        // 添加业务处理handler
                        ch.pipeline().addLast(new EchoClientHandler(sendNumber));
                    }
                });
            // 发起异步连接操作
            ChannelFuture f = b.connect(host, port).sync();

            // 等待客户端链路关闭
            f.channel().closeFuture().sync();
        } finally {
            // 优雅退出,释放NIO线程组
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        int port = 8080;
        if(args != null && args.length > 0) {
            try {
                port = Integer.valueOf(port);
            } catch (NumberFormatException e) {
                // 采用默认值
            }
        }
        int sendNumber = 10;
        new EchoClient().connect("localhost", port, sendNumber);
    }
}

编写服务端EchoServerHandler

package io.netty.handler.codec.msgpack;

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 {
        System.out.println("Server receive the msgpack message : " + msg);
        ctx.write(msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // 发生异常,关闭链路
        ctx.close();
    }
}

编写服务端的EchoServer

package io.netty.handler.codec.msgpack;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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 Exception {
        // 配置服务端的NIO线程组
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 1024)
                .childHandler(new ChannelInitializer<SocketChannel>() {

                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                    	ch.pipeline().addLast("frameDecoder", new 
                    			LengthFieldBasedFrameDecoder(65525, 0, 2, 0, 2));
                        // 添加MesspagePack解码器
                        ch.pipeline().addLast("msgpack decoder", new MsgpackDecoder());
                        
                        ch.pipeline().addLast("frameEncoder", new LengthFieldPrepender(2));
                        // 添加MessagePack编码器
                        ch.pipeline().addLast("msgpack encoder", new MsgpackEncoder());
                        // 添加业务处理handler
                        ch.pipeline().addLast(new EchoServerHandler());
                    }
                });

            // 绑定端口,同步等待成功
            ChannelFuture f = b.bind(port).sync();

            // 等待服务端监听端口关闭
            f.channel().closeFuture().sync();
        } finally {
            // 优雅退出,释放线程池资源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        int port = 8080;
        if(args != null && args.length > 0) {
            try {
                port = Integer.valueOf(port);
            } catch (NumberFormatException e) {
                // TODO: handle exception
            }
        }
        new EchoServer().bind(port);
    }
}

编写pojoUserInfo

package io.netty.handler.codec.msgpack;

import org.msgpack.annotation.Message;

@Message
public class UserInfo {

	private String name;
	private int age;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	@Override
    public String toString() {
        return "User [name=" + name + ", age=" + age + "]";
    }
}

以上代码可以直接用来参考运行, 在client和server里面增加的编解码器顺序不要变

ch.pipeline().addLast("frameDecoder", new 
      LengthFieldBasedFrameDecoder(65525, 0, 2, 0, 2));
// 添加MesspagePack解码器
ch.pipeline().addLast("msgpack decoder", new MsgpackDecoder());
//
ch.pipeline().addLast("frameEncoder", new LengthFieldPrepender(2));
// 添加MessagePack编码器
ch.pipeline().addLast("msgpack encoder", new MsgpackEncoder());
 // 添加业务处理handler
ch.pipeline().addLast(new EchoServerHandler());

上面一段表示在解码器之前增加一个LengthFieldBasedFrameDecoder表示用来处理半包消息,会在消息前增加两个字节,后续解析的时候根据消息头来的字节信息来解析,保证每次拿到的数据都是整包消息, 可以认为跟DelimiterBasedFrameDecoder类似的感觉,本人是这么认为的通过特定的消息字节来保证报文完整性

在编码器之前增加LengthFieldPrepender表示在字节缓冲区的报文之前增加两个字节长度

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值