传统IO在收发数据时,会阻塞当前线程,一边接收数据,一边对数据进行处理,处理完一段数据再继续接收下一段,再处理。而NIO会一次性将接收的所有数据,放入内存,处理数据时只需要读取内存,而IO线程被完全释放,这就是非阻塞。而被放入内存的数据在 netty中的表现形式就是本篇要讲的ByteBuf
接收数据
继续延用修改第1篇的代码,以下就是一次性接收数据放入内存(ByteBuf),并且打印出来的过程
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf in = (ByteBuf) msg; //把收到的数据msg转换成ByteBuf
System.out.println(in.toString(CharsetUtil.UTF_8)); //以UTF-8格式转换成字符串
发送数据
如果要发出数据,也要先将数据放入内存,即转换成ByteBuf。再一次性的从内存把数据发出去,避免写的过程阻塞IO
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf out = Unpooled.buffer(); //创建一个ByteBuf
out.writeBytes("hello".getBytes()); //将字符串写入ByteBuf
ctx.writeAndFlush(out); //发出ByteBuf
逐字读取
在一些场景中,需要对ByteBuf进行逐个字节的处理,比如:接收的数据中第1个字符代表类型,第2个字符代表状态等,根据每个字节的值,都需要做各种不同的处理
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf in = (ByteBuf) msg;
while(in.isReadable()){ //是否有可读数据
byte b = in.readByte(); //读取一个字节
System.out.println((char)b); //打印出来
}
关于isReadable())方法:ByteBuf中的数据存放在一个数组中,读取数据时从头部开始,并记录读取位置。每读取一次,位置会向后偏移,当读取到尾部,没有更多可读数据时,这个方法结果就会为否。
池
上面发送数据代码中有个Unpooled,即非池,每次需要重新创建ByteBuf,用完后销毁。使用池可以重复利用,以提高性能,类似连接池。,PooledByteBufAllocator可用于实现池的操作,如下
PooledByteBufAllocator pool = PooledByteBufAllocator.DEFAULT;
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf out = pool.buffer();
out.writeBytes("hello".getBytes());
ctx.writeAndFlush(out);
堆内存和直接内存
开头说过,收发的数据存入在内存中,即ByteBuf。而存入不同的内存,效果也不一样。
- 堆内存:由JAVA虚拟机创建并管理回收的一部分内存,性能较高。
- 直接内存:是JVM以外的系统内存,显然性能会比堆内存低。
但是,在对外收发数据时,不能直接使用JVM中的堆内存,需要用直接内存。此时,netty会自动将堆内存中的数据复制到直接内存,再进行收发。如果使用直接内存,则可省略了这一步骤。所以
- 进行大量后台业务处理时,使用堆内存。
- 收发数据时,使用直接内存。
netty默认情况下,使用的直接内存。也可以自己指定
pool.heapBuffer(); //堆内存
pool.directBuffer(); //直接内存
16进制
- 前面代码中1个字节通过ASCII值,只能存储一个字符的内容,可表达的指令意义有限,如数字指令只能表达0-9。而同样的空间用16进制来存储的话,一个字符可以存储2个16进制,16*16-256,可表达数字指令0-255。
- 计算机原始语言就是二进制,16进制数其实就是由4个二进制数组成。对计算机而说,如果二进制算普通话,16进制可以算方言,是可以听的懂的语言。而字符则属于外语,需要翻译,所以计算机更乐于处理数字指令。
因此,服务器与机器之间,普遍采用16进制的数字指令进行通讯。除非有人为参与的语言通讯,比如聊天,才必须要采用字符串进行通讯。接收16进制代码如下
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf in = (ByteBuf) msg;
System.out.println(ByteBufUtil.hexDump(in)); //转换16进制
也可以逐个读取字节
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf in = (ByteBuf) msg;
while(in.isReadable()){
int b = in.readByte()& 0xFF ; //读取1个字节转成10进制int表示
System.out.println(Integer.toHexString(b)); //再转成2个16进制字符串
}
发出16进制,如下。前面说过,一个byte刚好存储2个16进制,以下发出了2个byte,即4个16进制数:1122
ByteBuf out = pool.buffer();
out.writeByte(0x11); //0x前缀的16进制
out.writeByte(Integer.parseInt("22", 16)); //将字符串转换成16进制
ctx.writeAndFlush(out);
多字节读写
前面的代码中,writeByte和readByte都是单字节读写,也可以是多字节
ByteBuf out = pool.buffer();
out.writeByte(0x11); //1个字节
out.writeShort(0x2222); //2个字节
out.writeMedium(0x333333); //3个字节
out.writeInt(0x44444444); //4个字节
以上是写方法,读取方法也是一样,write换成read