簡介
channel是netty中資料傳輸和資料處理的渠道,也是netty程式中不可或缺的一環。在netty中channel是一個介面,針對不同的資料型別或者協議channel會有具體的不同實現。
雖然channel很重要,但是在程式碼中確實很神秘,基本上我們很少能夠看到直接使用channel的情況,那麼事實真的如此嗎?和channel相關的ChannelGroup又有什麼作用呢?一起來看看吧。
神龍見首不見尾的channel
其實netty的程式碼是有固定的模板的,首先根據是server端還是client端,然後建立對應的Bootstrap和ServerBootstrap。然後給這個Bootstrap配置對應的group方法。然後為Bootstrap配置channel和handler,最後啟動Bootstrap即可。
這樣一個標準的netty程式就完成了。你需要做的就是為其挑選合適的group、channel和handler。
我們先看一個最簡單的NioServerSocketChannel的情況:
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChatServerInitializer());
b.bind(PORT).sync().channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
這裡,我們將NioServerSocketChannel設定為ServerBootstrap的channel。
這樣就完了嗎?channel到底是在哪裡用到的呢?
別急,我們仔細看一下try block中的最後一句:
b.bind(PORT).sync().channel().closeFuture().sync();
b.bind(PORT).sync()實際上返回了一個ChannelFuture物件,透過呼叫它的channel方法,就返回了和它關聯的Channel物件。
然後我們呼叫了channel.closeFuture()方法。closeFuture方法會返回一個ChannelFuture物件,這個物件將會在channel關閉的時候收到通知。
而sync方法會實現同步阻塞,一直等到channel關閉為止,從而進行後續的eventGroup的shutdown操作。
在ServerBootstrap中構建模板中,channel其實有兩個作用,第一個作用是指定ServerBootstrap的channel,第二個作用就是透過channel獲取到channel關閉的事件,最終關閉整個netty程式。
雖然我們基本上看不到channel的直接方法呼叫,但是channel毋庸置疑,它就是netty的靈魂。
接下來我們再看一下具體訊息處理的handler的基本操作:
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// channel活躍
ctx.write("Channel Active狀態!\r\n");
ctx.flush();
}
通常如果需要在handler中向channel寫入資料,我們呼叫的是ChannelHandlerContext的write方法。這個方法和channel有什麼關係呢?
首先write方法是ChannelOutboundInvoker介面中的方法,而ChannelHandlerContext和Channel都繼承了ChannelOutboundInvoker介面,也就是說,ChannelHandlerContext和Channel都有write方法:
ChannelFuture write(Object msg);
因為這裡我們使用的是NioServerSocketChannel,所以我們來具體看一下NioServerSocketChannel中write的實現。
經過檢查程式碼我們會發現NioServerSocketChannel繼承自AbstractNioMessageChannel,AbstractNioMessageChannel繼承自AbstractNioChannel,AbstractNioChannel繼承自AbstractChannel,而這個write方法就是AbstractChannel中實現的:
public ChannelFuture write(Object msg) {
return pipeline.write(msg);
}
Channel的write方法,實際上呼叫了pipeline的write方法。下面是pipeLine中的write方法:
public final ChannelFuture write(Object msg) {
return tail.write(msg);
}
這裡的tail是一個AbstractChannelHandlerContext物件。
這樣我們就得出了這樣的結論:channel中的write方法最終實際上呼叫的是ChannelHandlerContext中的write方法。
所以上面的:
ctx.write("Channel Active狀態!\r\n");
實際上可以直接從channel中呼叫:
Channel ch = b.bind(0).sync().channel();
// 將訊息寫入channel中
ch.writeAndFlush("Channel Active狀態!\r\n").sync();
channel和channelGroup
channel是netty的靈魂,對於Bootstrap來說,要獲取到對應的channel,可以透過呼叫:
b.bind(PORT).sync().channel()
來得到,從上面程式碼中我們也可以看到一個Bootstrap只會對應一個channel。
channel中有一個parent()方法,用來返回它的父channel,所以channel是有層級結構的,
我們再來看一下channelGroup的定義:
public interface ChannelGroup extends Set<Channel>, Comparable<ChannelGroup>
可以看到ChannelGroup實際上是Channel的集合。ChannelGroup用來將類似的Channel構建成集合,從而可以對多個channel進行統一的管理。
可以能有小夥伴要問了,一個Bootstrap不是隻對應一個channel嗎?那麼哪裡來的channel的集合?
事實上,在一些複雜的程式中,我們可能啟動多個Bootstrap來處理不同的業務,所以相應的就會有多個channel。
如果建立的channel過多,並且這些channel又是很同質化的時候,就有需求對這些channel進行統一管理。這時候就需要用到channelGroup了。
channelGroup的基本使用
先看下channelGroup的基本使用,首先是建立一個channelGroup:
ChannelGroup recipients =
new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
有了channelGroup之後,可以呼叫add方法,向其中新增不同的channel:
recipients.add(channelA);
recipients.add(channelB);
並且還可以統一向這些channel中傳送訊息:
recipients.write(Unpooled.copiedBuffer(
"這是從channelGroup中發出的統一訊息.",
CharsetUtil.UTF_8));
基本上channelGroup提供了write,flush,flushAndWrite,writeAndFlush,disconnect,close,newCloseFuture等功能,用於對集合中的channel進行統一管理。
如果你有多個channel,那麼可以考慮使用channelGroup。
另外channelGroup還有一些特性,我們來詳細瞭解一下。
將關閉的channel自動移出
ChannelGroup是一個channel的集合,當然我們只希望儲存open狀態的channel,如果是close狀態的channel,還要手動從ChannelGroup中移出的話實在是太麻煩了。
所以在ChannelGroup中,如果一個channel被關閉了,那麼它會自動從ChannelGroup中移出,這個功能是怎麼實現的呢?
先來看下channelGroup的add方法:
public boolean add(Channel channel) {
ConcurrentMap<ChannelId, Channel> map =
channel instanceof ServerChannel? serverChannels : nonServerChannels;
boolean added = map.putIfAbsent(channel.id(), channel) == null;
if (added) {
channel.closeFuture().addListener(remover);
}
if (stayClosed && closed) {
channel.close();
}
return added;
}
可以看到,在add方法中,為channel區分了是server channel還是非server channel。然後根據channel id將其存入ConcurrentMap中。
如果新增成功,則給channel新增了一個closeFuture的回撥。當channel被關閉的時候,會去呼叫這個remover方法:
private final ChannelFutureListener remover = new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
remove(future.channel());
}
};
remover方法會將channel從serverChannels或者nonServerChannels中移出。從而保證ChannelGroup中只儲存open狀態的channel。
同時關閉serverChannel和acceptedChannel
雖然 ServerBootstrap的bind方法只會返回一個channel,但是對於server來說,可以有多個worker EventLoopGroup,所以當客戶端和伺服器端建立連線之後建立的accepted Channel是server channel的子channel。
也就是說一個伺服器端有一個server channel和多個accepted channel。
那麼如果我們想要同時關閉這些channel的話, 就可以使用ChannelGroup的close方法。
因為如果Server channel和非Server channel在同一個ChannelGroup的話,所有的IO命令都會先發給server channel,然後才會發給非server channel。
所以我們可以將Server channel和非Server channel統統加入同一個ChannelGroup中,在程式的最後,統一呼叫ChannelGroup的close方法,從而達到該目的:
ChannelGroup allChannels =
new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
public static void main(String[] args) throws Exception {
ServerBootstrap b = new ServerBootstrap(..);
...
b.childHandler(new MyHandler());
// 啟動伺服器
b.getPipeline().addLast("handler", new MyHandler());
Channel serverChannel = b.bind(..).sync();
allChannels.add(serverChannel);
... 等待shutdown指令 ...
// 關閉serverChannel 和所有的 accepted connections.
allChannels.close().awaitUninterruptibly();
}
public class MyHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) {
// 將accepted channel新增到allChannels中
allChannels.add(ctx.channel());
super.channelActive(ctx);
}
}
ChannelGroupFuture
另外,和channel一樣,channelGroup的操作都是非同步的,它會返回一個ChannelGroupFuture物件。
我們看下ChannelGroupFuture的定義:
public interface ChannelGroupFuture extends Future<Void>, Iterable<ChannelFuture>
可以看到ChannelGroupFuture是一個Future,同時它也是一個ChannelFuture的遍歷器,可以遍歷ChannelGroup中所有channel返回的ChannelFuture。
同時ChannelGroupFuture提供了isSuccess,isPartialSuccess,isPartialFailure等方法判斷命令是否部分成功。
ChannelGroupFuture還提供了addListener方法用來監聽具體的事件。
總結
channel是netty的核心,當我們有多個channel不便進行管理的時候,就可以使用channelGroup進行統一管理。
本文已收錄於 http://www.flydean.com/04-1-netty-channel-group/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!