工作时候,有用过Netty写过网络库。最近想研究下RPC框架,就想着写几篇博客,梳理下Netty的源码。(研究的源码版本是4.1.x)
最简单的示例
首先还是拿Netty 官网的UserGuide做例子
/**
* Handles a server-side channel.
*/
public class DiscardServerHandler extends ChannelInboundHandlerAdapter { // (1)
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2)
// Discard the received data silently.
((ByteBuf) msg).release(); // (3)
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)
// Close the connection when an exception is raised.
cause.printStackTrace();
ctx.close();
}
}
/**
* Discards any incoming data.
*/
public class DiscardServer {
private int port;
public DiscardServer(int port) {
this.port = port;
}
public void run() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap(); // (2)
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class) // (3)
.childHandler(new ChannelInitializer() { // (4)
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new DiscardServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128) // (5)
.childOption(ChannelOption.SO_KEEPALIVE, true); // (6)
// Bind and start to accept incoming connections.
ChannelFuture f = b.bind(port).sync(); // (7)
// Wait until the server socket is closed.
// In this example, this does not happen, but you can do that to gracefully
// shut down your server.
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port = 8080;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
}
new DiscardServer(port).run();
}
}
Netty的源码还是很复杂的,所以研究时候有种无处下手的感觉。但是Netty作为一个NIO的网络框架,其底层肯定是使用了java jdk 的nio编程。所以我们可以通过对比java nio编程 与 Netty源码,来了解Netty究竟对java 的nio做了什么样的封装。
一个java nio编程的例子
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
serverSocketChannel.configureBlocking(false);
while(true){
SocketChannel socketChannel =
serverSocketChannel.accept();
if(socketChannel != null){
//do something with socketChannel...
}
}
例子是网上随便粘的,当然简化了很多很多东西,这篇博客只会分析服务端channel的创建,所以这些暂时也够了。
总结下:
1)创建ServerSocketChannel
2)绑定某个端口
3)设置非阻塞
4)监听新的连接
后面分析源码也会试着找下Netty对应的源码在哪
创建一个NioServerSocketChannel
具体的调用流程如下图
private ChannelFuture doBind(final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
//省略....
}
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
channel = channelFactory.newChannel();
init(channel);
}
//省略...
}
ReflectiveChannelFactory
public T newChannel() {
try {
return clazz.newInstance(); //这里的clazz就是当时从ServerBootstrap.channel方法里传进去的NioServerSocketChannel
} catch (Throwable t) {
throw new ChannelException("Unable to create Channel from class " + clazz, t);
}
}
代码流程还是很清晰的,总结下就是 Netty会在ServerBootstrap里,通过工厂创建一个ServerChannel
在本例中,工厂是默认的 ReflectiveChannelFactory(通过反射创建Channel),创造出的Channel类型是NioServerSocketChannel
NioServerSocketChannel 的初始化
分析完了NioServerSocketChannel接下来看看NioServerSocketChannel究竟在初始化时候做了啥。
public NioServerSocketChannel() {
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
private static ServerSocketChannel newSocket(SelectorProvider provider) {
try {
/**
* Use the {@link SelectorProvider} to open {@link SocketChannel} and so remove condition in
* {@link SelectorProvider#provider()} which is called by each ServerSocketChannel.open() otherwise.
*
* See #2308.
*/
return provider.openServerSocketChannel();
} catch (IOException e) {
throw new ChannelException(
"Failed to open a server socket.", e);
}
}
1、创建一个对应的jdk channel
通过 SelectorProvider.openServerSocketChannel() 创建了一个ServerSocketChannel 这个ServerSocketChannel就是 上文,nio编程的例子里的jdk channel
2、AbstractNioChannel构造方法
public NioServerSocketChannel(ServerSocketChannel channel) {
super(null, channel, SelectionKey.OP_ACCEPT);
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
1)NioServerSocketChannel最终会调用父类AbstractNioChannel的构造函数
2)绑定一个 NioServerSocketChannelConfig
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent);
this.ch = ch;
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);
}
}
1)保存所创建的jdk channel
2)配置configureBlocking 为false
调用父类 AbstractChannel的构造方法
protected AbstractChannel(Channel parent) {
this.parent = parent;
id = newId(); //绑定一个id
unsafe = newUnsafe(); //绑定一个NioSocketChannelUnsafe
pipeline = newChannelPipeline(); //绑定一个DefaultChannelPipeline
}
这一步非常重要:
1)绑定一个 id
2)绑定一个 NioSocketChannelUnsafe
3)绑定一个DefaultChannelPipeline
这几个组件,在Netty中起着非常重要的作用,后面博客里讲到了会详细分析下
总结下,ServerSocketChannel做了这样几件事
1)创建一个jdk channel,并记录下来
2)配置jdk channel 的 configureBlocking 为false
3)绑定一个NioServerSocketChannelConfig
4)绑定id、NioSocketChannelUnsafe、DefaultChannelPipeline 三个组件
初始化ServerChannel
再回到ServerBootStrap里,在创建了ServerChannel(即我们这的NioServerSocketChannel)后,下面一步,是对其进行初始化
@Override
void init(Channel channel) throws Exception {
//获取我们在client代码里配置的channel
final Map, Object> options = options0();
//最终调用 channel.config().setOption((ChannelOption
流程还挺长的,总结下:
1)把client端设置的option缓存到NioServerSocketChannelConfig里
2)把client端设置的attr设置到channel里
3)保存配置的childOptions,配置的childAttrs 到ServerBootstrapAcceptor里
4)往NioSocketChannel的pipeline中添加一个ServerBootstrapAcceptor
注册 ServerChannel
ServerChannel的注册流程比较长,下面一篇博客再单独介绍吧。
总结
这篇博客分析了下Netty的服务端启动流程,总结下:
1)Netty会在ServerBootstrap中创建一个ServerChannel
2)ServerChannel底层会绑定一个jdk 的channel
3)ServerChannel创建时,会绑定一个id、NioSocketChannelUnsafe、DefaultChannelPipeline、NioServerSocketChannelConfig
4)初始化ServerChannel时,会把我们自定义配置的option、attr设置进去
5)最终会往 NioSocketChannel 的pipeline里添加一个 ServerBootstrapAcceptor
6)注册ServerChannel(下面一篇博客介绍)