Netty (3)-ByteBuf、池、直接內存、16進制

傳統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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章