Netty學習篇(三)--- 客戶端 服務端實現雙向通訊
阿新 • • 發佈:2018-12-13
客戶端-服務端:客戶端傳送資料到服務端
上篇文章提到,讀寫處理邏輯是在 Bootstrap
的handler()
方法指定的,上節課寫的如下程式碼:
.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;
}
}
- 繼承自
ChannelInboundHandlerAdapter
的channelActive()
方法會在連線成功之後自動回撥; - 寫資料的過程分為兩部分:
- 獲取一個
ByteBuf
格式的二進位制資料,這個結構是Netty對二進位制資料做的抽象; ctx.alloc()
學過C/C++ 的人肯定知道 alloc 和記憶體相關,所以這行程式碼的意思是獲取ByteBuff
的記憶體管理器,而這個記憶體管理器的作用就是分配一個ByteBuff
出來;- 填充資料到
ByteBuff
中,這樣就達到了Netty傳輸資料的要求; - 使用
ctx.channel().writeAndFlush(buffer);
將資料寫出到服務端。
- 獲取一個
上述程式碼和傳統的Java Socket程式設計不同的一點就是寫出的資料格式不同,Netty是自己對二進位制資料做了一層抽象,定義了一個ByteBuff
的結構出來,無論資料讀還是寫,Netty都只需要著這樣的格式才行,下面開始學習服務端如何讀取到這端資料。
服務端-客戶端:服務端讀取客戶端資料
同理,服務端的讀寫處理邏輯處理還是在ServerBootstrap
的childHandler()
方法中,這裡除了單詞不同之外,其他和客戶端同理,這就是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")));
}