Netty入門與實戰——客戶端與服務端雙向通信

*客戶端

在之前的文章中,客戶端相關的數據讀寫邏輯是通過 Bootstraphandler() 方法指定

.handler(new ChannelInitializer<SocketChannel>() {
    @Override
    public void initChannel(SocketChannel ch) {
        // 指定連接數據讀寫邏輯
    }
});

現在,在 initChannel() 方法裏面給客戶端添加一個邏輯處理器,這個處理器的作用就是負責向服務端寫數據

.handler(new ChannelInitializer<SocketChannel>() {
    @Override
    public void initChannel(SocketChannel ch) {
        ch.pipeline().addLast(new FirstClientHandler());
    }
});
  • ch.pipeline()返回的是和這條連接相關的邏輯處理鏈,採用了責任鏈模式
  • 調用 addLast() 方法添加一個邏輯處理器,這個邏輯處理器爲的就是在客戶端建立連接成功之後,向服務端寫數據

*FirstClientHandler源碼:

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

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

public class FirstClientHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) {   //客戶端寫數據給服務端
        System.out.println(new Date() + ": 客戶端寫出數據");

        // 1.獲取數據
        ByteBuf buffer = getByteBuf(ctx);

        // 2.寫數據
        ctx.channel().writeAndFlush(buffer);
    }

    private ByteBuf getByteBuf(ChannelHandlerContext ctx) {
        byte[] bytes = "你好,Netty!".getBytes(Charset.forName("utf-8"));

        ByteBuf buffer = ctx.alloc().buffer();

        buffer.writeBytes(bytes);

        return buffer;
    }


    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {  //讀取服務端的數據
        ByteBuf byteBuf = (ByteBuf) msg;

        System.out.println(new Date() + ": 客戶端讀到數據 -> " + byteBuf.toString(Charset.forName("utf-8")));
    }
}

1、這個邏輯處理器繼承自 ChannelInboundHandlerAdapter,然後覆蓋了 channelActive()方法,這個方法會在客戶端連接建立成功之後被調用

2、客戶端連接建立成功之後,調用到 channelActive() 方法,在這個方法裏面,我們編寫向服務端寫數據的邏輯

3、寫數據的邏輯分爲兩步:首先我們需要獲取一個 netty 對二進制數據的抽象 ByteBuf,上面代碼中, ctx.alloc() 獲取到一個 ByteBuf 的內存管理器,這個內存管理器的作用就是分配一個 ByteBuf,然後我們把字符串的二進制數據填充到 ByteBuf,這樣我們就獲取到了 Netty 需要的一個數據格式,最後我們調用 ctx.channel().writeAndFlush() 把數據寫到服務端

*服務端

在之前的文章中,服務端相關的數據處理邏輯是通過 ServerBootstrapchildHandler() 方法指定

.childHandler(new ChannelInitializer<NioSocketChannel>() {
   protected void initChannel(NioSocketChannel ch) {
       // 指定連接數據讀寫邏輯
   }
});

現在,在 initChannel() 方法裏面給服務端添加一個邏輯處理器,這個處理器的作用就是負責讀取客戶端來的數據

.childHandler(new ChannelInitializer<NioSocketChannel>() {
    protected void initChannel(NioSocketChannel ch) {
        ch.pipeline().addLast(new FirstServerHandler());
    }
});

*FirstServerHandler源碼

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

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

public class FirstServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {   //讀取來自客戶端的數據
        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 = "你好,歡迎學習Netty".getBytes(Charset.forName("utf-8"));

        ByteBuf buffer = ctx.alloc().buffer();

        buffer.writeBytes(bytes);

        return buffer;
    }
}

服務端側的邏輯處理器同樣繼承自 ChannelInboundHandlerAdapter,與客戶端不同的是,這裏覆蓋的方法是 channelRead(),這個方法在接收到客戶端發來的數據之後被回調。

這裏的 msg 參數指的就是 Netty 裏面數據讀寫的載體,這裏我們強轉之後,然後調用 byteBuf.toString() 就能夠拿到客戶端發過來的字符串數據。

總結:

1、客戶端和服務端的邏輯處理是均是在啓動的時候,通過給邏輯處理鏈 pipeline 添加邏輯處理器,來編寫數據的讀寫邏輯

2、在客戶端連接成功之後會回調到邏輯處理器的 channelActive() 方法,而不管是服務端還是客戶端,收到數據之後都會調用到 channelRead 方法

3、寫數據調用writeAndFlush方法,客戶端與服務端交互的二進制數據載體爲 ByteBufByteBuf 通過連接的內存管理器創建,字節數據填充到 ByteBuf 之後才能寫到對端

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