Netty (3)-ByteBuf、池、直接内存、16进制

  • Post author:
  • Post category:其他


传统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



版权声明:本文为wangb_java原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。