本文地址: juejin.im/post/5dbcde…
Netty 使用
首先到官網看一下Netty Server 和 Client的demo, netty.io/wiki/user-g…, 我用的是4.1.xx,一般來說不是大版本變更, 變化不會很大.下面是 Netty Server 的demo,跟官網的是一樣的.
public class Main {
// 下面是一個接收執行緒, 3個worker執行緒 ,所以這裡是 Reactor多執行緒模式
// 用 Netty 的預設執行緒工廠,可以不傳這個引數
private final static ThreadFactory threadFactory = new DefaultThreadFactory("Netty學習之路");
// Boss 執行緒池,用於接收客戶端連線
private final static NioEventLoopGroup boss = new NioEventLoopGroup(1,threadFactory);
// Worker執行緒池,用於處理客戶端操作
private final static NioEventLoopGroup worker = new NioEventLoopGroup(3,threadFactory);
/*
* 下面是在構造方法中, 如果不傳執行緒數量,預設是0, super 到 MultithreadEventLoopGroup 這裡後, 最終會用 CPU核數*2 作為執行緒數量, Reactor多執行緒模式的話,就指定 boss 執行緒數量=1
* private static final int DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
* protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
* super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
* }
*/
public static void main(String[] args) throws Exception{
try {
new NettyServer(8888).start();
// NIOTest();
System.out.println(1<<0);
}catch(Exception e){
System.out.println("netty server啟動失敗");
e.printStackTrace();
}
}
static class NettyServer{
private int port;
NettyServer(int port){
this.port = port;
}
void start()throws Exception{
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
ChannelFuture future = serverBootstrap
.group(boss, worker)
.channel(NioServerSocketChannel.class)
// 客戶端連線等待佇列大小
.option(ChannelOption.SO_BACKLOG, 1024)
// 接收緩衝區
.option(ChannelOption.SO_RCVBUF, 32*1024)
// 連線超時
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10*1000)
.childHandler(new ChildChannelHandle())
.bind(this.port)
.sync();
future.channel().closeFuture().sync();
}catch(Exception e){
throw e;
}finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
static class ChildChannelHandle extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
// 字串編碼
pipeline.addLast(worker,new StringEncoder());
// 字串解碼
pipeline.addLast(worker,new StringDecoder());
// 自定義的handle, 狀態變化後進行處理的 handle
pipeline.addLast(worker,new StatusHandle());
// 自定義的handle, 現在是對讀取到的訊息進行處理
pipeline.addLast(worker,new CustomHandle());
}
}
}
複製程式碼
客戶端的操作就簡單的使用終端來操作了
這裡對 inactive 和 active 進行了狀態的輸出, 輸出接收資料並且原樣返回給客戶端接下來看一下程式碼
- CustomHandle
這裡對接收到的客戶端的資料進行處理
public class CustomHandle extends ChannelInboundHandlerAdapter {
private Thread thread = Thread.currentThread();
// 讀取到客戶端資料的事件
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 這裡簡單輸出一下,然後原樣返回給客戶端
System.out.println(thread.getName()+": channelRead content : "+msg);
ctx.writeAndFlush(msg);
}
}
複製程式碼
- StatusHandle
對狀態變化後進行處理的Handle(客戶端上下線事件)
public class StatusHandle extends ChannelInboundHandlerAdapter {
private Thread thread = Thread.currentThread();
private String ip;
// 客戶端上線事件
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
this.ip = ctx.channel().remoteAddress().toString();
System.out.println(thread.getName()+": ["+this.ip+"] channelActive -------");
}
// 客戶端下線事件
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println(thread.getName()+": ["+this.ip+"] channelInactive -------");
}
}
複製程式碼
上面標記了兩個地方, 是接下來要講的地方
1.NioServerSocketChannel作用相當於NIO ServerSocketChannel 2.ChildChannelHandle extends ChannelInitializer , 實現 initChannel 方法,這個東西延伸到一個重要的概念,Netty的事件傳遞 Pipeline
NioServerSocketChannel
這個類是 Netty 用於服務端的類,用於接收客戶端連線等. 用過NIO的同學都知道, serverSocket開啟的時候,需要註冊 ACCEPT 事件來監聽客戶端的連線
-
(小插曲)下面是Java NIO 的事件(netty基於NIO,自然也會有跟NIO一樣的事件)
-
public static final int OP_READ = 1 << 0; // 讀訊息事件
-
public static final int OP_WRITE = 1 << 2; // 寫訊息事件
-
public static final int OP_CONNECT = 1 << 3; // 連線就緒事件
-
public static final int OP_ACCEPT = 1 << 4; // 新連線事件
先看一下 NioServerSocketChannel 的繼承類圖
從上面的demo的 channel(NioServerSocketChannel.class) 開始說起吧,可以看到是工廠生成channel. public B channel(Class<? extends C> channelClass) {
if (channelClass == null) {
throw new NullPointerException("channelClass");
} else {
return this.channelFactory((io.netty.channel.ChannelFactory)(new ReflectiveChannelFactory(channelClass)));
}
}
複製程式碼
工廠方法生成 NioServerSocketChannel 的時候呼叫的構造方法:
public NioServerSocketChannel(ServerSocketChannel channel) {
super(null, channel, SelectionKey.OP_ACCEPT);
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
複製程式碼
繼續往下跟,跟到 AbstractNioChannel 的構造方法:
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent);
this.ch = ch;
// 記住這個地方記錄了 readInterestOp
this.readInterestOp = readInterestOp;
try {
// 設定為非阻塞
ch.configureBlocking(false);
} catch (IOException e) {
try {
ch.close();
} catch (IOException e2) {
if (logger.isWarnEnabled()) {
logger.warn(
"Failed to close a partially initialized socket.", e2);
}
}
throw new ChannelException("Failed to enter non-blocking mode.", e);
}
}
複製程式碼
回到 ServerBootstrap 的鏈式呼叫, 接下來看 bind(port) 方法,一路追蹤下去,會看到
private ChannelFuture doBind(final SocketAddress localAddress) {
// 初始化和註冊
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
if (regFuture.isDone()) {
ChannelPromise promise = channel.newPromise();
doBind0(regFuture, channel, localAddress, promise);
return promise;
} else {
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
Throwable cause = future.cause();
if (cause != null) {
promise.setFailure(cause);
} else {
promise.registered();
doBind0(regFuture, channel, localAddress, promise);
}
}
});
return promise;
}
}
複製程式碼
看 initAndRegister 方法
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
channel = channelFactory.newChannel();
init(channel);
} catch (Throwable t) {
if (channel != null) {
channel.unsafe().closeForcibly();
return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
}
return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
}
// 看到這裡的註冊, 繼續往下看
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
複製程式碼
config().group().register(channel); 往下看, 追蹤到 AbstractChannel 的 register --> regist0(promise) (由於呼叫太多,省去了中間的一些呼叫程式碼)
private void register0(ChannelPromise promise) {
try {
// check if the channel is still open as it could be closed in the mean time when the register
// call was outside of the eventLoop
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
boolean firstRegistration = neverRegistered;
// 執行註冊
doRegister();
neverRegistered = false;
registered = true;
// Ensure we call handlerAdded(...) before we actually notify the promise. This is needed as the
// user may already fire events through the pipeline in the ChannelFutureListener.
// 這裡官方也說得很清楚了,確保我們在使用 promise 的通知之前真正的呼叫了 pipeline 中的 handleAdded 方法, 下面第二點
pipeline.invokeHandlerAddedIfNeeded();
safeSetSuccess(promise);
// 先呼叫 regist 方法
pipeline.fireChannelRegistered();
// Only fire a channelActive if the channel has never been registered. This prevents firing
// multiple channel actives if the channel is deregistered and re-registered.
// 只有 channel 之前沒有註冊過才會呼叫 channelActive
// 這裡防止 channel deregistered(登出) 和 re-registered(重複呼叫 regist) 的時候多次呼叫 channelActive
if (isActive()) {
if (firstRegistration) {
// 執行 channelActive 方法
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
// This channel was registered before and autoRead() is set. This means we need to begin read
// again so that we process inbound data.
//
// channel 已經註冊過 並且 已經設定 autoRead().這意味著我們需要開始再次讀取才能處理 inbound 的資料
// See https://github.com/netty/netty/issues/4805
beginRead();
}
}
} catch (Throwable t) {
// Close the channel directly to avoid FD leak.
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
複製程式碼
看到 doRegister() 方法,繼續跟下去, 跟蹤到 AbstractNioChannel 的 doRegister() 方法
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
// 這裡呼叫java的 NIO 註冊
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
return;
} catch (CancelledKeyException e) {
if (!selected) {
eventLoop().selectNow();
selected = true;
} else {
throw e;
}
}
}
}
複製程式碼
寫過NIO的同學應該熟悉上面的這句話:
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
複製程式碼
這裡就是呼叫了java NIO的註冊, 至於為什麼註冊的時候 ops = 0 , 繼續追蹤下去,此處省略一堆呼叫....(實在是過於繁雜)最後發現, 最終都會呼叫 AbstractNioChannel 的 doBeginRead() 方法修改 selectionKey 的 interestOps, 客戶端連線後,註冊的讀事件在這裡也是相同的操作.
protected void doBeginRead() throws Exception {
// Channel.read() or ChannelHandlerContext.read() was called
final SelectionKey selectionKey = this.selectionKey;
if (!selectionKey.isValid()) {
return;
}
readPending = true;
final int interestOps = selectionKey.interestOps();
// // 這裡是判斷有沒有註冊過相同的事件,沒有的話才修改 ops
if ((interestOps & readInterestOp) == 0) {
// 就是這裡, 記得剛才註冊的時候,ops == 0 嗎, this.readInterestOp 在上面的初始化的時候賦了值
// 與 0 邏輯或, 所以最終值就是 this.readInterestOp , 註冊事件的數值 不清楚的話可以看一下最上面
selectionKey.interestOps(interestOps | readInterestOp);
}
}
複製程式碼
上面介紹了服務端的 ACCEPT 事件, 接下來是客戶端連線的過程.
上面介紹的 服務端 ACCEPT 最後呼叫的 NIO 的 register 方法, read也是呼叫nio的register, 但是在 SocketChannel(client) 呼叫register 之前, 服務端是有一個 server.accept() 方法獲取客戶端連線, 以此為契機, 最後在 NioServerSocketChannel 裡面找到了accept 方法.
// 1
protected int doReadMessages(List<Object> buf) throws Exception {
// accept 客戶端, 傳入 serverSocketChannel
SocketChannel ch = SocketUtils.accept(javaChannel());
try {
if (ch != null) {
// 建立新的 Netty 的 Channel , 並設定 ops =1 (read), 最後再呼叫 doBeginRead的時候就會修改 ops 的值 , 跟 server 的一樣
buf.add(new NioSocketChannel(this, ch));
return 1;
}
} catch (Throwable t) {
logger.warn("Failed to create a new channel from an accepted socket.", t);
try {
ch.close();
} catch (Throwable t2) {
logger.warn("Failed to close a socket.", t2);
}
}
return 0;
}
// 2
public static SocketChannel accept(final ServerSocketChannel serverSocketChannel) throws IOException {
try {
return AccessController.doPrivileged(new PrivilegedExceptionAction<SocketChannel>() {
@Override
public SocketChannel run() throws IOException {
// nio 的方法
return serverSocketChannel.accept();
}
});
} catch (PrivilegedActionException e) {
throw (IOException) e.getCause();
}
}
複製程式碼
客戶端連線的時候,會觸發上面的 server.accept(), 然後會觸發 AbstractChannel 的 register 方法 從而呼叫
AbstractChannel.this.pipeline.fireChannelRegistered();// 這個方法呼叫下面的兩個方法
複製程式碼
static void invokeChannelRegistered(final AbstractChannelHandlerContext next) {
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelRegistered();
} else {
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelRegistered();
}
});
}
}
private void invokeChannelRegistered() {
if (invokeHandler()) {
try {
((ChannelInboundHandler) handler()).channelRegistered(this);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelRegistered();
}
}
複製程式碼
中間省略掉了一些呼叫(不得不說Netty的呼叫有點複雜..), 最後會呼叫到 我們接下來要講的 ChannelInitializer的 handlerAdded方法.這些都跟一個 pipeline 相關.
Pipeline
pipeline ,事件傳輸管道(傳輸途徑啥的都行),它是一條 handle 訊息傳遞鏈.
先看一下 AbstractChannelHandlerContext 中的 兩個方法
// 查詢下一個 inboundHandle (從當前位置往後查詢 intBound)
private AbstractChannelHandlerContext findContextInbound() {
AbstractChannelHandlerContext ctx = this;
do {
ctx = ctx.next; // 往後查詢
} while (!ctx.inbound);
return ctx;
}
// 查詢下一個 OutboundHandle (從當前位置往前查詢 outBound )
private AbstractChannelHandlerContext findContextOutbound() {
AbstractChannelHandlerContext ctx = this;
do {
ctx = ctx.prev; // 往前查詢
} while (!ctx.outbound);
return ctx;
}
複製程式碼
為什麼是這樣呢,我們從 AbstractChannelHandleContext 的 read 和 write 兩個方法進入檢視
AbstractChannelHandleContext
read
@Override
public ChannelHandlerContext fireChannelRead(final Object msg) {
// findContextInbound() 就是上面2個方法的第一個,結果是往後找到一個 inbound 的 handle
// 呼叫的下面的方法
invokeChannelRead(findContextInbound(), msg);
return this;
}
//當讀資料(入棧)事件的時候, 從當前位置往後查詢 InBoundHandleHandle, 觸發下一個 InBoundHandleHandle 的 channelRead 事件
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelRead(m);
} else {
executor.execute(new Runnable() {
public void run() {
next.invokeChannelRead(m);
}
});
}
}
複製程式碼
write
// 當寫資料(出棧)事件的時候, 從當前的 handle 往前查詢到 OutBoundHandle, 觸發前一個 OutBoundHandle 的 write 事件傳遞
private void write(Object msg, boolean flush, ChannelPromise promise) {
AbstractChannelHandlerContext next = this.findContextOutbound();
Object m = this.pipeline.touch(msg, next);
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
if (flush) {
next.invokeWriteAndFlush(m, promise);
} else {
next.invokeWrite(m, promise);
}
} else {
Object task;
if (flush) {
task = AbstractChannelHandlerContext.WriteAndFlushTask.newInstance(next, m, promise);
} else {
task = AbstractChannelHandlerContext.WriteTask.newInstance(next, m, promise);
}
if (!safeExecute(executor, (Runnable)task, promise, m)) {
((AbstractChannelHandlerContext.AbstractWriteTask)task).cancel();
}
}
}
複製程式碼
一個平時可能不會注意到到地方:
上面講的是 AbstractChannelHandleContext 中的方法, AbstractChannel 中也有 write 方法 ,接下來我們就講 ctx.write() 和 channel.write() 這兩個方法呼叫的區別
ctx.write vs channel.write
AbstractChannelHandleContext#ctx.write()
public ChannelFuture write(Object msg, ChannelPromise promise) {
if (msg == null) {
throw new NullPointerException("msg");
} else {
try {
if (this.isNotValidPromise(promise, true)) {
ReferenceCountUtil.release(msg);
return promise;
}
} catch (RuntimeException var4) {
ReferenceCountUtil.release(msg);
throw var4;
}
// 呼叫下面那個方法
this.write(msg, false, promise);
return promise;
}
}
private void write(Object msg, boolean flush, ChannelPromise promise) {
// 找到下一個 outboundHandle
AbstractChannelHandlerContext next = this.findContextOutbound();
Object m = this.pipeline.touch(msg, next);
EventExecutor executor = next.executor();
// 後面程式碼太多省略
...
}
複製程式碼
AbstractChannel#channel.write()
// ctx.channel().write()
public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) {
// 呼叫下面的方法
return this.pipeline.writeAndFlush(msg, promise);
}
public final ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) {
// tail 是最末尾的一個handle
return this.tail.writeAndFlush(msg, promise);
}
複製程式碼
總結: ctx.write() 這個方法是在 當前 handle 開始往前查詢 OutBoundHandle進行事件傳遞, 而 channel.write() 是從 pipeline的最後一個handle(tail)往前查詢 OutBoundHandle 進行事件傳遞, 所以 channel.write() 傳遞的事件會經過所有的 OutBoundHandle .
// 字串編碼
pipeline.addLast(worker,new StringEncoder()); // 1.outbound
// 字串解碼
pipeline.addLast(worker,new StringDecoder()); // 2.inbound
// 自定義的handle, 狀態變化後進行處理的 handle
pipeline.addLast(worker,new StatusHandle()); // 3.inbound
// 自定義的handle, 現在是對讀取到的訊息進行處理
pipeline.addLast(worker,new CustomHandle()); // 4.inbound
複製程式碼
我們上面4個 handle 新增的順序為 out, in , in, in , 所以最終呼叫的話,會變成下面這樣(handle前面的數字僅僅是新增進pipeline的順序)
ChannelInitializer
這個類見上面的 ChildChannelHandle
類.
當 channel(客戶端通道)一旦被註冊,將會呼叫這個方法,這個方法用來初始化客戶端的pipeline, 並且在方法返回的時候, 這個例項(ChannelInitializer)將會被從 ChannelPipeline (客戶端的 pipeline) 中移除.
原文:
public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter
/**
* This method will be called once the {@link Channel} was registered. After the method returns this instance
* will be removed from the {@link ChannelPipeline} of the {@link Channel}.
*
* @param ch the {@link Channel} which was registered.
* @throws Exception is thrown if an error occurs. In that case it will be handled by
* {@link #exceptionCaught(ChannelHandlerContext, Throwable)} which will by default close
* the {@link Channel}.
*/
protected abstract void initChannel(C ch) throws Exception;
複製程式碼
initChannel
除了這個抽象方法, 這個類還有一個過載方法 ,具體實現就在這個方法裡.
private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
if (initMap.putIfAbsent(ctx, Boolean.TRUE) == null) { // Guard against re-entrance.
try {
// 第二步
// 這裡呼叫我們自己實現的那個抽象方法 , 將 我們前面定義的 handle 都加入到 client 的 pipeline 中
initChannel((C) ctx.channel());
} catch (Throwable cause) {
exceptionCaught(ctx, cause);
} finally {
// 第三步,將自己(ChannelInitializer)從pipeline中移除
// 所以客戶端的pipeline中就不會有 ChannelInitializer 這個handle(它也是個handle)
// 它的作用是用來初始化客戶端的pipeline中的handle
remove(ctx);
}
return true;
}
return false;
}
複製程式碼
remove
// 將自己從客戶端的pipeline中移除
private void remove(ChannelHandlerContext ctx) {
try {
ChannelPipeline pipeline = ctx.pipeline();
if (pipeline.context(this) != null) {
pipeline.remove(this);
}
} finally {
initMap.remove(ctx);
}
}
複製程式碼
這篇的程式碼有點多, 如果只是demo使用的話, 不需要花費什麼時間, 如果想要深入瞭解一下 Netty 的話, 可以從這裡的事件傳遞開始對原始碼的一點點分析.
最後
這次的內容到這裡就結束了,最後的最後,非常感謝你們能看到這裡!!你們的閱讀都是對作者的一次肯定!!!
覺得文章有幫助的看官順手點個贊再走唄(終於暴露了我就是來騙讚的(◒。◒)),你們的每個贊對作者來說都非常重要(異常真實),都是對作者寫作的一次肯定(double)!!!