这几天在研究netty框架,其中的ByteBufjava.nio.ByteBuffer的微创新令人感受颇深。java.nio.ByteBuffer的引入对套接字编程提供了极大的方便性。在此之前,如果要从Socket中读取一个4字节整数,假如采用Big-Endian编码,其过程通常如下:



/**
 * 使用Socket.getInputStream从套接字中读取一个4字节整数,表示PDU的长度.
 * @param s 套接字
 * @return PDU的长度
 * @throws IOException 如果在访问套接字时出现错误
 */
publiclong getPduLength(Socket s) throws IOException {
long pduLength;
byte[] buf = newbyte[4];
int cnt = 0;
while (cnt < buf.length) {
cnt += s.getInputStream().read(buf, cnt, buf.length - cnt);
}
pduLength = buf[0];
pduLength &= buf[1] << 8;
pduLength &= buf[2] << 16;
pduLength &= buf[3] << 24;
return pduLength;
}


分析:由于java的安全性限制不能直接访问内存,所以在读取到足够的字节之后,需要通过位运算来得到一个4字节整数。而且每次读取字节时都要进行边界检查,对于高性能的网络通信要求来说,不是很理想。



java引入nio包的ByteBuffer之后,这种情况得到了明显改善。ByteBuffernative方法实现了对内存的直接访问,避免了位操作及反复的边界检查,性能得到改善。代码也很简洁:


/**
 * 使用java.nio.SocketChannel从套接字中读取一个4字节整数,表示PDU的长度.
 * @param s 套接字
 * @return PDU的长度
 * @throws IOException 如果在访问套接字时出现错误
 */
publiclong getPduLength(SocketChannel s) throws IOException {
ByteBuffer buf = ByteBuffer.allocate(4);
int cnt = 0;
while (cnt < 4) {
cnt += s.read(buf);
}
buf.flip();
return buf.getLong();
}



但在实际编程上,由于nio包只是提供了一个基本的通信接口,工程师仍然需要大量的多线程、异步操作、共享访问等工作,这些工作对很多程序员来说不啻于一场梦魇。java是一个开放的语言,大量框架应运而生,这些框架的目标是希望解放程序员的精力,让程序员关注于Business Rules,而不是编程本身。Netty就是其中一个优秀的框架。以其中的ByteBuf重写上述例程:


/**
 * 使用Netty框架的ByteBuf从套接字中读取一个4字节整数,表示PDU的长度.
 * @param s 套接字
 * @return PDU的长度
 * @throws IOException 如果在访问套接字时出现错误
 */
publiclong getPduLengthNetty(SocketChannel s) throws IOException {
ByteBuf buf = PooledByteBufAllocator.DEFAULT.buffer(4);
int cnt = 0;
ByteBuffer tmp = ByteBuffer.allocate(4);
while (cnt < 4) {
cnt += s.read(tmp);
}
tmp.flip();
buf.writeBytes(tmp);
return buf.readLong();
}


从上述代码看,ByteBuf的使用和ByteBuffer基本类似,而且由于Netty是采用Pure-java编写的,所以仍然需要依赖nio提供的接口,读取一个4字节整数的代码量并没有减少,而且还有所增加,逻辑更多了一层,这显然没有达到Netty的初衷。

请关注:Netty框架中的ByteBufjava.nio.ByteBuffer的微创新(2