看一下TimeClientHander.java
package io.netty.example.time; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import java.util.Date; public class TimeClientHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { final ByteBuf m = (ByteBuf) msg;//1 try { final long currentTimeMills = (m.readUnsignedInt() - 2208988800L) * 1000L;//2 System.out.println(new Date(currentTimeMills)); ctx.close(); } finally { m.release(); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }对象转换成ByteBuf。
直接通过readUnsignedInt()方法读取数值。
代码看起来比较简单,但是一定可能性(小概率)会有IndexOutOfBoundsException异常,我们下节讨论。
处理基于stream的传输 Socket Buffer的一个问题TCP/IP是一个典型的stream-based协议,接收数据然后放到socket buffer里面。但是这个buffer队列存的是byte,而不是packet数据包。所以,就算发了两个packet,在系统看来,他就是一堆byte。所以,没有办法保证你读取到的东西和发过来的一定一样。
举个例子,假设收到了3个数据包,ABC,DEF,GHI
有可能收到的是下面这样的
所以,server和client需要一种规则来划分数据包,然后对方就知道每个包到底是啥样的。
第一种解决方案其实道理上来说因为int数据包也就4个字节,所以不太会被分片,不太容易出现IndexOutOfBoundsException异常。但是,随着数据包变大,分片的可能性就会增加,到时候异常出现的概率就会增大。
因为我们知道收到的数据是4个字节,所以,我们可以分配一个4自己的空间,等到一满,我们就知道已经收到该有的数据包了,就直接处理就好。来改一下我们的TimeClientHandler
package io.netty.example.time; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import java.util.Date; public class TimeClientHandler2 extends ChannelInboundHandlerAdapter { private ByteBuf buf; @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { buf = ctx.alloc().buffer(4);//1 } @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { buf.release();//1 buf = null; } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { final ByteBuf m = (ByteBuf) msg; buf.writeBytes(m);//2 m.release(); if (buf.readableBytes() >= 4) {//3 final long currentTimeMillis = (buf.readUnsignedInt() - 2208988800L) * 1000L; System.out.println(new Date(currentTimeMillis)); ctx.close(); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }重写了handlerAdded和handlerRemoved方法,在这两个方法里面初始化或者销毁buf对象。只要这两个方法不会阻塞太长时间,是没有关系的。
把收到的内容写到buf对象里面。
每次有数据过来的时候会进入channelRead方法(不同的连接不会串),做一个业务逻辑判断。
第二种解决方案虽然上面的问题是解决了,但是因为我们晓得发过来的数据是4个字节的(就一个字段),所以比较好处理。但是,如果这个对象是一个比较复杂的业务对象,那么要维护这个类就会比较麻烦。
我们可以对这个TimeClientHandler2的功能拆解成2部分。
TimeDecoder专门处理数据包分片的问题。