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