1. 程式人生 > >Netty學習篇(三)--- 客戶端 服務端實現雙向通訊

Netty學習篇(三)--- 客戶端 服務端實現雙向通訊

客戶端-服務端:客戶端傳送資料到服務端

上篇文章提到,讀寫處理邏輯是在 Bootstraphandler()方法指定的,上節課寫的如下程式碼:

.handler(new ChannelInitializer<Channel>() {
         @Override
         protected void initChannel(Channel channel) {
             // 指定資料讀寫處理邏輯 
         	channel.pipeline().addLast(new StringEncoder());
         }
});

現在,我們自定義一段處理邏輯給它,如下:

// 設定執行緒組
 bootstrap.group(group)
      // 設定執行緒模型
      .channel(NioSocketChannel.class)
      // 設定連線讀寫處理邏輯
      .handler(new ChannelInitializer<Channel>() {
              @Override
              protected void initChannel(Channel channel) {
                   // channel.pipeline() 責任鏈模式 返回和這條連線相關的邏輯處理鏈
// addLast 新增一個邏輯處理器 也就是我們自定義的讀寫處理邏輯了 channel.pipeline().addLast(new CustomizeHandler()); } });

上述程式碼中的

  • channel.pipeline()責任鏈模式 返回和這條連線相關的邏輯處理鏈
  • addLast() 新增一個邏輯處理器 也就是我們自定義的讀寫處理邏輯了

其中,CustomizeHandler.java程式碼如下:

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import java.nio.charset.Charset; import java.util.Date; /** * Created by zhoudl on 2018/10/4. */ public class CustomizeHandler extends ChannelInboundHandlerAdapter { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println(new Date() + ": 客戶端開始寫資料"); // 1. 獲取資料 ByteBuf buffer = getByteBuf(ctx); // 2. 寫資料 ctx.channel().writeAndFlush(buffer); } private ByteBuf getByteBuf(ChannelHandlerContext ctx) { // 1. 獲取二進位制抽象 ByteBuf ByteBuf buffer = ctx.alloc().buffer(); // 2. 準備資料,指定字串的字符集為 utf-8 byte[] bytes = "你好,蒼穹盛夏童鞋!".getBytes(Charset.forName("utf-8")); // 3. 填充資料到 ByteBuf buffer.writeBytes(bytes); return buffer; } }
  • 繼承自ChannelInboundHandlerAdapterchannelActive()方法會在連線成功之後自動回撥;
  • 寫資料的過程分為兩部分:
    • 獲取一個ByteBuf格式的二進位制資料,這個結構是Netty對二進位制資料做的抽象;
    • ctx.alloc()學過C/C++ 的人肯定知道 alloc 和記憶體相關,所以這行程式碼的意思是獲取ByteBuff的記憶體管理器,而這個記憶體管理器的作用就是分配一個ByteBuff出來;
    • 填充資料到ByteBuff中,這樣就達到了Netty傳輸資料的要求;
    • 使用ctx.channel().writeAndFlush(buffer);將資料寫出到服務端。

上述程式碼和傳統的Java Socket程式設計不同的一點就是寫出的資料格式不同,Netty是自己對二進位制資料做了一層抽象,定義了一個ByteBuff的結構出來,無論資料讀還是寫,Netty都只需要著這樣的格式才行,下面開始學習服務端如何讀取到這端資料。

服務端-客戶端:服務端讀取客戶端資料

同理,服務端的讀寫處理邏輯處理還是在ServerBootstrapchildHandler()方法中,這裡除了單詞不同之外,其他和客戶端同理,這就是Netty API 友好的體現方式之一,學了客戶端,服務端猜也能猜個大概,所謂大膽猜測,小心驗證。

.childHandler(new ChannelInitializer<Channel>() {
    @Override
    protected void initChannel(Channel channel) throws Exception {
        channel.pipeline().addLast(new StringDecoder());
        channel.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
            @Override
            protected void channelRead0(ChannelHandlerContext ctx, String msg) {
                System.out.println(msg);
            }
        });
    }
})

一樣的,接下來我們自定義一個處理器出來,程式碼如下:

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.nio.charset.Charset;
import java.util.Date;

/**
 * Created by zhoudl on 2018/10/4.
 */
public class CustomizeServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println(new Date() + ": 服務端讀取資料 -> " + byteBuf.toString(Charset.forName("utf-8")));
    }
}

同樣的,你會發現,它繼承了ChannelInboundHandlerAdapter,不同的在於這裡是讀資料,所以覆蓋的方法變了,換成了read()方法,當客戶端連線成功併發送資料之後這個方法被自動回撥。

接下來開始學習服務端向客戶端迴應資料的過程,學完上邊這倆之後,現在應該已經沒什麼難度了。

服務端-客戶端:服務端向客戶端迴應資料

此處寫資料和客戶端寫資料過程類似,我就不再贅述了,直接上程式碼,簡單直接又明瞭,程式碼如下:

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.nio.charset.Charset;
import java.util.Date;

/**
 * Created by zhoudl on 2018/10/4.
 */
public class CustomizeServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println(new Date() + ": 服務端讀取資料 -> " + byteBuf.toString(Charset.forName("utf-8")));
        // 回覆資料到客戶端
        System.out.println(new Date() + ": 服務端寫出資料");
        ByteBuf out = getByteBuf(ctx);
        ctx.channel().writeAndFlush(out);
    }

    private ByteBuf getByteBuf(ChannelHandlerContext ctx) {
        byte[] bytes = "你好,我是蒼穹盛夏!".getBytes(Charset.forName("utf-8"));
        ByteBuf buffer = ctx.alloc().buffer();
        buffer.writeBytes(bytes);
        return buffer;
    }

}

緊接著,客戶端需要讀取服務端發過來的資料,而讀取資料的過程和上述服務端讀取客戶端資料的程式碼無異,將以下程式碼新增到CustomizeHandler中,便能實現客戶端讀資料的邏輯,程式碼如下:

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    ByteBuf byteBuf = (ByteBuf) msg;
    System.out.println(new Date() + ": 客戶端讀到資料 -> " + byteBuf.toString(Charset.forName("utf-8")));
}