Channel,EventLoop和ChannelFuture類構成了Netty網絡抽象的代表:
- Channel:對應Socket
- EventLoop:對應控制流,多線程處理,并發
- ChannelFuture:對應異步通知
Channel接口
Channel是對Socket的封裝,大大降低了直接使用Socket的復雜性。
EventLoop接口
EventLoop用于處理連接的生命周期中所發生的事件。在服務端編程中,EventLoop起到了監聽端口連接和數據讀取的工作。
ChannelFuture接口
Netty中的所有IO操作都是異步的,一個操作不會立即返回,我們需要在執行操作之后的某個時間點確定其結果的方法,即ChannelFuture接口,其addListener方法注冊了一個ChannelFutureListener,一遍在某個操作完成時得到通知。
ChannelHandler和ChannelPipeline
ChannelHandler充當了所有處理入站和出站數據的應用程序邏輯的容器。ChannelHandler常常實現了傳入數據包拆分以及業務邏輯控制的功能。
ChannelPipeline接口
ChannelPipeline為ChannelHandler鏈提供容器,并定義了用于在該鏈上傳播入站和出站事件流的API。即ChannelPipeline其實就是一個保存很多ChannelHandler的鏈表。
BootStrap接口
Netty的引導類為應用程序的網絡層配置提供了容器。有兩種類型的引導:一種用戶客戶端,稱為Bootstrap,另一種稱為用于服務器,稱為ServerBootstrap。
示例
上面提到的這些接口在實際使用過程中并不是挨個使用的,而是使用BootStrap接口來將需要的接口組織在一起,下面是個基于netty的服務端的例子:
public class Server {
public static void main(String[] args) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childOption(ChannelOption.TCP_NODELAY, true)
.childAttr(AttributeKey.newInstance("childAttr"), "childAttrValue")
.handler(new ServerHandler())
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
}
});
ChannelFuture f = b.bind(8888).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
public class ServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) {
System.out.println("channelActive");
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) {
System.out.println("channelRegistered");
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) {
System.out.println("handlerAdded");
}
@Override
public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
super.channelRead(ctx, msg);
new Thread(new Runnable() {
@Override
public void run() {
// 耗時的操作
String result = loadFromDB();
ctx.channel().writeAndFlush(result);
ctx.executor().schedule(new Runnable() {
@Override
public void run() {
// ...
}
}, 1, TimeUnit.SECONDS);
}
}).start();
}
private String loadFromDB() {
return "hello world!";
}
}
下面就針對上面使用的ServerBootstrap類來分析Netty是如何來封裝java nio相關的操作的。
Netty 如何獲取NIO中需要的Channel
首先從ChannelFuture f = b.bind(8888).sync();
中的bind方法開始分析。
這個bind方法經過層層調用,最終會調用initAndRegister方法:
private ChannelFuture doBind(final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister();
...
}
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
channel = channelFactory.newChannel();
...
從上述源碼中可以看到channel來自channelFactory的newChannel方法。ChannelFactory是一個接口,我們只能從它的實現類ReflectiveChannelFactory來看下具體實現了:
private final Class<? extends T> clazz;
@Override
public T newChannel() {
try {
return clazz.newInstance();
} catch (Throwable t) {
throw new ChannelException("Unable to create Channel from class " + clazz, t);
}
}
可以看出這個工廠方法的newChannel其實就是根據clazz屬性來反射出具體的對象。那當前代碼中的clazz屬性值是多少呢?回到一開始示例執行的channel(NioServerSocketChannel.class)
這句代碼,這個channel方法實現如下:
public B channel(Class<? extends C> channelClass) {
return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
}
所以ServerBootstrapy引導類中使用的通道就是channel方法中設置的NioServerSocketChannel通道。
NioServerSocketChannel實現
接下來就要來看下Netty的NioServerSocketChannel內部是怎么調用java NIO中的ServerSocketChannel的。
NioServerSocketChannel內部實現步驟如下:
- newSocket() 通過jdk來創建底層jdk channel
- AbstractNioChannel() 方法中調用configureBlocking(false) 設置通道為非阻塞模式
- AbstractNioChannel() 方法創建id,unsafe,pipeline
接下面就上面幾個點來看下NioServerSocketChannel類的源碼。
private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();
public NioServerSocketChannel() {
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
private static ServerSocketChannel newSocket(SelectorProvider provider) {
try {
return provider.openServerSocketChannel();
} catch (IOException e) {
throw new ChannelException("Failed to open a server socket.", e);
}
從NioServerSocketChannel的構造函數可以看出java NIO的SelectorProvider.provider().openServerSocketChannel()生成了ServerSocketChannel通道。
接下來看第二個點,即在哪設置通道的非阻塞模式,NioServerSocketChannel構造函數如下:
public NioServerSocketChannel(ServerSocketChannel channel) {
super(null, channel, SelectionKey.OP_ACCEPT);
...
}
super方法調用了父類AbstractNioChannel的構造函數如下:
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent);
...
ch.configureBlocking(false);
...
所以NioServerSocketChannel的構造函數中將通道設置了非阻塞模式。除了設置為非阻塞模式外,還調用了父類的構造函數super(parent),具體如下:
protected AbstractChannel(Channel parent) {
this.parent = parent;
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
protected DefaultChannelPipeline newChannelPipeline() {
return new DefaultChannelPipeline(this);
}
protected DefaultChannelPipeline(Channel channel) {
this.channel = ObjectUtil.checkNotNull(channel, "channel");
succeededFuture = new SucceededChannelFuture(channel, null);
voidPromise = new VoidChannelPromise(channel, true);
tail = new TailContext(this);
head = new HeadContext(this);
head.next = tail;
tail.prev = head;
}
從上面這段代碼中可以看出在構造函數中創建了一個新的pipeline,并且這個鏈表一開始是空的。
在介紹了ServerBootstrap中如何調用java NIO中的ServerSocketChannel的實現后,我們接下來看ServerBootstrap是如何初始化服務器Channel的。還是從示例中ChannelFuture f = b.bind(8888).sync();
中bind方法中的initAndRegister方法中的代碼開始分析:
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
channel = channelFactory.newChannel();
init(channel);
}
...
}
abstract void init(Channel channel) throws Exception;
之前我們分析了從channelFactory中獲取ServerChannel通道的邏輯,這里再分析下ServerBootstrap中這個init方法:
void init(Channel channel) throws Exception {
final Map<ChannelOption<?>, Object> options = options0();
synchronized (options) {
channel.config().setOptions(options);
}
final Map<AttributeKey<?>, Object> attrs = attrs0();
synchronized (attrs) {
for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
@SuppressWarnings("unchecked")
AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
channel.attr(key).set(e.getValue());
}
}
ChannelPipeline p = channel.pipeline();
final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
final Entry<ChannelOption<?>, Object>[] currentChildOptions;
final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
synchronized (childOptions) {
currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
}
synchronized (childAttrs) {
currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
}
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(Channel ch) throws Exception {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = config.handler();
if (handler != null) {
pipeline.addLast(handler);
}
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}
init方法首先將傳入的childOption,childAttr屬性保存到ServerBootstrap對象中;另外又把用戶通過handler方法設置的handler對象添加到pipeline尾部,然后再在尾部添加一個ServerBootstrapAcceptor對象,里面包裝了用戶通過childHandler方法傳入的對象。
注冊Selector
上面主要學習了netty內部是如何封裝了ServerSocketChannel等NIO相關的操作的,接下來再繼續看下netty內部是如何封裝NIO中注冊選擇器selector的。
回到我們之前提到的initAndRegister方法內部:
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
channel = channelFactory.newChannel();
init(channel);
} catch (Throwable t) {
......
}
ChannelFuture regFuture = config().group().register(channel);
......
}
我們已經講過獲取ServerSocketChannel和初始化通道這兩個部分,接下來再看下面config().group().register(channel);
內部是如何實現注冊Selector的。
由于EventLoopGroup是接口,在本例中實現類為NioEventLoop。
NioEventLoop類實現的register方法:
public void register(final SelectableChannel ch, final int interestOps, final NioTask<?> task) {
// 參數校驗......
try {
ch.register(selector, interestOps, task);
} catch (Exception e) {
throw new EventLoopException("failed to register a channel", e);
}
}
可以看出最終執行的SeverSocketChannel.register(selector,interestOps,task);
方法,即還是執行了java NIO中通道注冊到選擇器selector的方法。
端口綁定
上面我們已經分析了AbstractBootstrap類里面doBind方法中initAndRegister方法的內部實現:創建ServerSocketChannel通道,初始化,注冊通道到選擇器selector上這三步。下面再繼續分析doBind方法中后面關于端口綁定的實現。
從下面代碼中的doBind0方法開始解析:
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;
.......
}
private static void doBind0(
final ChannelFuture regFuture, final Channel channel,
final SocketAddress localAddress, final ChannelPromise promise) {
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
if (regFuture.isSuccess()) {
channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
promise.setFailure(regFuture.cause());
}
}
});
}
從上面源碼中可以看出,在doBind0方法內部執行了ServerSocketChannel的bind方法,即進行端口的綁定操作。