Netty知多少?-----第一篇

初次接触netty,很多迷茫,也很多坑,踩了,自己记下,分享到博客,以免后来人继续踩坑,喜欢就点赞或评论,因为刚开始写博客,谢谢了。

我假如你已经了解netty的一些知识,比如他的NIO是多么的好,ByteBuf是多么的好,所以你才选择用此框架,那么,就我和一起开始学习netty吧。(不过即使你不知道netty有多好,但是既然你来看这篇博客,就说明你想了解netty,那我们不说屁话,开始吧。)

首先、Netty我并没有开发客户端,而只开发服务端。

客户端有两个,一个是app,通过http请求我服务端接口(这个本篇博客不做说明);一个是硬件,通过tcp和建立连接,进行通讯(这里的tcp通讯报文数据格式(十六进制)采用:字头head(1个字节)+业务类型type(1个字节)+校验位(2个字节)+定长length(2个字节)+实体body(N个字节)+字尾cc(1个字节))。

其次,开始写代码:

其中有几个注意点:

1、流式编程,不断向 channel.pipeline().addLast()填东西

2、填的第一个就是心跳检测

new IdleStateHandler(READ_IDEL_TIME_OUT, WRITE_IDEL_TIME_OUT, ALL_IDEL_TIME_OUT, TimeUnit.SECONDS)

3、填的第二个是编码方式的类(这里很重要,我在这里踩了坑)

new ClientMessageDecoder(MAX_FRAME_LENGTH, LENGTH_FIELD_OFFSET, LENGTH_FIELD_LENGTH, LENGTH_ADJUSTMENT, INITIAL_BYTES_TO_STRIP, false)

ClientMessageDecoder()这个类继承 LengthFieldBasedFrameDecoder(这是netty用来处理tcp报文数据格式为head+type+校验码+length+body+cc粘包的一个类,只需要传入四个参数,就可以很好解决tcp粘包和拆包的问题)

4、填的第三个是解码方式的类(通过第3点,我们把ByteBuf缓存中的数据(客户端传来的数据放到这个缓存中),编码成我们想要的格式,现在就可以在这里解码,并交给handler去处理了)

channel.pipeline().addLast("encoder", new ClientMessageEncoder());

5、填的第四个是handler处理类了(TcpServerInboundHandler)。由他进行数据的处理,完成我们想要的业务吧。

6、后面还是有一些配置,主要设置连接数,长连接,是否将收到的数据理解发送处理的配置,不做讲解。

7、我将在你看完代码后讲解:ClientMessageDecoder,ClientMessageDecoder,TcpServerInboundHandler,这个三个类,很重要。

耐心看代码和注释

public class TcpServer {
	private static final String TAG = TcpServer.class.getName();
	private static final int READ_IDEL_TIME_OUT = 200; // 读超时
	private static final int WRITE_IDEL_TIME_OUT = 205;// 写超时
	private static final int ALL_IDEL_TIME_OUT = 210; // 所有超时
	private static final int MAX_FRAME_LENGTH = 1024 * 1024;
//	lengthFieldLength = 4;//长度字段占的长度
	private static final int LENGTH_FIELD_LENGTH = 4;
//	lengthFieldOffset = 0;//长度字段的偏差
	private static final int LENGTH_FIELD_OFFSET = 2;
//	lengthAdjustment = 0;//添加到长度字段的补偿值
	private static final int LENGTH_ADJUSTMENT = 0;
//	initialBytesToStrip = 0。//从解码帧中第一次去除的字节数
	private static final int INITIAL_BYTES_TO_STRIP = 0;
	/**
	 * (ChannelOption.SO_BACKLOG, 1024)//连接数
	 * (ChannelOption.SO_KEEPALIVE, true); //长连接
	 * (ChannelOption.TCP_NODELAY, true);  //不延迟,消息立即发送
	 * @param port
	 */
	public static void start(int port) {
		String output = "Server start at:" + port;
		LogUtil.info(TAG, output);
		EventLoopGroup bossGroup = new NioEventLoopGroup();
		EventLoopGroup workerGroup = new NioEventLoopGroup();
		try {
			ServerBootstrap serverBootstrap = new ServerBootstrap();
			serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
					.childHandler(new ChannelInitializer<SocketChannel>() {
						@Override
						public void initChannel(SocketChannel channel) throws Exception {
							channel.pipeline().addLast(new IdleStateHandler(READ_IDEL_TIME_OUT, WRITE_IDEL_TIME_OUT, ALL_IDEL_TIME_OUT, TimeUnit.SECONDS));
							channel.pipeline().addLast("decoder", new ServerMessageDecoder(MAX_FRAME_LENGTH, LENGTH_FIELD_OFFSET, LENGTH_FIELD_LENGTH, LENGTH_ADJUSTMENT, INITIAL_BYTES_TO_STRIP, false));
							channel.pipeline().addLast("encoder", new ServerMessageEncoder());
							channel.pipeline().addLast(new TcpServerInboundHandler());
						}
					}).option(ChannelOption.SO_BACKLOG, 1024)
					.childOption(ChannelOption.SO_KEEPALIVE, true)
					.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
					.childOption(ChannelOption.TCP_NODELAY, true);
			ChannelFuture future = serverBootstrap.bind(port).sync();
			future.channel().closeFuture().sync();
		} catch (Exception e) {
			LogUtil.error(TAG, e);
		} finally {
			workerGroup.shutdownGracefully();
			bossGroup.shutdownGracefully();
		}
	}
}

 

ClientMessageDecoder:

负责将客户端传来数据按照我们指定的方式进行编码,同时这里配置几个参数,解决了tcp粘包和拆包问题。本文只介绍继承LengthFieldBasedFrameDecoder这个类,处理tcp报文数据格式为head+type+校验码+length+body+cc粘包拆包问题,其他如报文数据格式定长啊,报文数据采用换行分隔符这里不做介绍。

首先明白 客户端传来的数据放到ByteBuf中,我们读取来就OK了

public class ClientMessageDecoder extends LengthFieldBasedFrameDecoder {
    // 判断传送客户端传送过来的数据是否按照协议传输,头部信息的大小应该是 byte+byte+int = 1+1+4 = 6
    private static final int HEADER_SIZE = 6;

    private String head;
    private String type;
    private int length;
    private static final String TAG = ClientMessageDecoder.class.getName();

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        LogUtil.error(TAG, cause);
    }
    /**
     * @param maxFrameLength      解码时,处理每个帧数据的最大长度
     * @param lengthFieldOffset   该帧数据中,存放该帧数据的长度的数据的起始位置
     * @param lengthFieldLength   记录该帧数据长度的字段本身的长度
     * @param lengthAdjustment    修改帧数据长度字段中定义的值,可以为负数
     * @param initialBytesToStrip 解析的时候需要跳过的字节数
     * @param failFast            为true,当frame长度超过maxFrameLength时立即报TooLongFrameException异常,为false,读取完整个帧再报异常
     */
    public ClientMessageDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip, boolean failFast) {
        super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip, failFast);
    }

    @Override
    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        ServerMessage customMsg = null;
        ByteBuf buf = null;
        try {
            if (in == null) {
                throw new Exception("in is null");
            }
            if (in.readableBytes() < HEADER_SIZE) {
                throw new Exception("Readable smaller");
            }
            // 注意在读的过程中,readIndex的指针也在移动
            head = Utils.byteToHexString(in.readByte()) ;
            type = Utils.byteToHexString(in.readByte());
            String valide = "";
            for (int i = 0; i < 4; i++) {
                valide = valide + Utils.byteToHexString(in.readByte());
            }
            if (Utils.isNullOrEmpty(valide) || valide.length() < 4)
                throw new Exception("valide error,valide:" + valide);
            length = Integer.parseInt(valide.substring(4), 16);
            if ((length > 24 && length < 500 && in.readerIndex() < 500) || length == 9) {
                if (in.readableBytes() < length) {
                    throw new Exception("The length is " + length + ",length is wrong" );
                }
                buf = in.readBytes(length);
                byte[] body = new byte[buf.readableBytes()];
                buf.readBytes(body);
                if (body.length < 1) {
                    throw new Exception("body.length is " + body.length);
                }
                String realBody = null;
                realBody = Utils.bytesToHexString(Utils.interceptBytes(body, 0, body.length - 1));
                customMsg = new ServerMessage(head, type, length, realBody);
            } else {
                throw new Exception("The length is " + length + ",length is not in range");
            }
        } catch (Exception e) {
            throw e;
        } finally {
            try {
                if (buf != null) {
                    buf.clear();
                    buf.release();
                }
                if (in != null) {
                    in.clear();
                }
            } catch (Exception e) {
                // TODO: handle exception
                throw e;
            }
        }
        return customMsg;
    }
}

ClientMessageDecoder

public class ServerMessageEncoder extends MessageToByteEncoder<ServerMessage> {
	private static final String TAG = ServerMessageEncoder.class.getName();

	@Override
	protected void encode(ChannelHandlerContext ctx, ServerMessage msg, ByteBuf out) throws Exception {
		if (null == msg) {
			throw new Exception("msg is null");
		}
		try {
			if (msg.getBody().length() > 24 && msg.getBody().length() < 256)
				if ("00".equals(msg.getType()))
			byte[] bodyBytes = msg.getBody().getBytes("utf-8");
			if (out != null && out.isWritable() && ctx.channel().isActive() && ctx.channel().isWritable()) {
				out.writeBytes(Utils.hexStringToBytes(msg.getHead()));
				out.writeBytes(Utils.hexStringToBytes(msg.getType()));
				out.writeInt(bodyBytes.length);
				out.writeBytes(bodyBytes);
			}
		} catch (Exception e) {
			// TODO: handle exception
			LogUtil.error(TAG, e);
			ctx.close();
		}
	}
}

小结:经过以上两步(编码,解码),我们已经收到了通过tcp协议传来的数据,并在编码的时候通过LengthFieldBasedFrameDecoder这个类,解决我们规定格式的报文的粘包拆包问题,现在我们可以通过

TcpServerInboundHandler 去处理业务了(好开心)

该类继承ChannelInboundHandlerAdapter,重写其中的channelRead(ChannelHandlerContext ctx, Object msg)方法,就可以了
@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		// TODO Auto-generated method stub
		try {
			ServerMessage customMsg = (ServerMessage) msg;
			String body = customMsg.getBody();
			if (!Utils.isNullOrEmpty(body) && body.length() > 24) {
                //带着我们custoMsg去分发到各处吧
				TypeDispatcher.dispatcher(ctx, customMsg);
			}  else if (ctx != null) {
				ctx.close();
			}
		} catch (Exception e) {
			// TODO: handle exception
			LogUtil.error(TAG, e);
			if (ctx != null) {
				ctx.close();
			}
		}
	}

 

总结

至此,我们已经完成了netty服务端的一个简单开发,但是对于ClientMessageDecoder继承LengthFieldBasedFrameDecoder时,传入的五个参数,他们是我们准确拿到客户端数据的关键,大家可以去看看netty权威指南2,进一步加深理解。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值