Netty服务端启动代码:

  1. public final class EchoServer {
  2.  
  3. static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));
  4.  
  5. public static void main(String[] args) throws Exception {
  6. // Configure the server.
  7. EventLoopGroup bossGroup = new NioEventLoopGroup(1);
  8. EventLoopGroup workerGroup = new NioEventLoopGroup();
  9. final EchoServerHandler serverHandler = new EchoServerHandler();
  10. try {
  11. ServerBootstrap b = new ServerBootstrap();
  12. b.group(bossGroup, workerGroup)
  13. .channel(NioServerSocketChannel.class)
  14. .option(ChannelOption.SO_BACKLOG, 100)
  15. .handler(new LoggingHandler(LogLevel.INFO))
  16. .childHandler(new ChannelInitializer<SocketChannel>() {
  17. @Override
  18. public void initChannel(SocketChannel ch) throws Exception {
  19. ChannelPipeline p = ch.pipeline();
  20. //p.addLast(new LoggingHandler(LogLevel.INFO));
  21. p.addLast(serverHandler);
  22. }
  23. });
  24.  
  25. // Start the server.
  26. ChannelFuture f = b.bind(PORT).sync();
  27.  
  28. // Wait until the server socket is closed.
  29. f.channel().closeFuture().sync();
  30. } finally {
  31. // Shut down all event loops to terminate all threads.
  32. bossGroup.shutdownGracefully();
  33. workerGroup.shutdownGracefully();
  34. }
  35. }
  36. }

1.Channel的创建

  通过Bootstrap.bind(PORT)调用AbstractBootstrap.doBind(),doBind()调用initAndRegister()。

  1.   final ChannelFuture initAndRegister() {
  2. Channel channel = this.channelFactory().newChannel();
  3.  
  4. try {
  5. this.init(channel);
  6. } catch (Throwable var3) {
  7. channel.unsafe().closeForcibly();
  8. return (new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE)).setFailure(var3);
  9. }
  10.  
  11. ChannelFuture regFuture = this.group().register(channel);
  12. if (regFuture.cause() != null) {
  13. if (channel.isRegistered()) {
  14. channel.close();
  15. } else {
  16. channel.unsafe().closeForcibly();
  17. }
  18. }
  19.  
  20. return regFuture;
  21. }

  从该方法可以看出,channel的创建是依赖channelFactory().newChannel()创建的,channelFactory()是获取AbstractBootstrap类的成员变量channelFactory,这个成员变量是在服务器段的Bootstarp.channel(NioServerSocketChannel.class)的时候进行设置的,而channelFactory().newChannel()则是该方法通过反射获取该类的实例变量。

  1. public T newChannel() {
  2. try {
  3. return (Channel)this.clazz.newInstance();
  4. } catch (Throwable var2) {
  5. throw new ChannelException("Unable to create Channel from class " + this.clazz, var2);
  6. }
  7. }

  通过反射调用的是NioServerSocketChannel的默认构造函数。NioServerSocketChannel的类关系如下:

  1. public NioServerSocketChannel() {
  2. this(newSocket(DEFAULT_SELECTOR_PROVIDER));
  3. }

  NioServerSocketChannel通过newSocket创建java nio中的channel,newSocket(DEFAULT_SELECTOR_PROVIDER)通过SelectorProvider.provider().openServerSocketChannel()打开一个ServerSocketChannel,这里的ServerSocketChannel是Java nio中的channel,在linux下,我们可以理解为JVM会调用linux操作系统的socket()函数创建了一个socket。这个地方要注意区别它跟前面NioServerSocketChannel实例的关系,他们都是一个socket channel,但是是两个不同的东西,一个是java nio中的,另外一个是经过netty包装过后的。newSocket(DEFAULT_SELECTOR_PROVIDER)返回的channel更多的是与操作系统socket相关联的一个东西,后面代码中经常遇到的通过javaChannel()返回的channel就是它,所以我们要进行底层的socket操作时,就是通过它。

  然后再调用NioServerSocketChannel的另一个构造函数,调用父类的构造函数,并构造一个配置类NioServerSocketChannelConfig。

  通过层层调用父类的构造函数,设置channel需要监听的事件为OP_ACCEPT,调用ch.configureBlocking(false),将channel设置为非阻塞的,并且调用AbstractChannel的构造函数,设置了channel的成员变量parent,unsafe和pipeline。

  1. protected AbstractChannel(Channel parent) {
  2. this.parent = parent;
  3. this.unsafe = this.newUnsafe();
  4. this.pipeline = new DefaultChannelPipeline(this);
  5. }
  1. public NioServerSocketChannel(java.nio.channels.ServerSocketChannel channel) {
  2. super((Channel)null, channel, 16);
  3. this.config = new NioServerSocketChannel.NioServerSocketChannelConfig(this, this.javaChannel().socket());
  4. }

  这里的javaChannel()获取的是AbstractNioChannel的SelectableChannel,也就是之前创建java nio中的channel。

2.Channel的初始化

  channel的初始化是在initAndRegister中方法中的this.init(channel)完成的,调用了AbstractBootstrap中的init。

  1.   void init(Channel channel) throws Exception {
  2. Map<ChannelOption<?>, Object> options = this.options();
  3. synchronized(options) {
  4. channel.config().setOptions(options);
  5. }
  6.  
  7. Map<AttributeKey<?>, Object> attrs = this.attrs();
  8. synchronized(attrs) {
  9. Iterator i$ = attrs.entrySet().iterator();
  10.  
  11. while(true) {
  12. if (!i$.hasNext()) {
  13. break;
  14. }
  15.  
  16. Entry<AttributeKey<?>, Object> e = (Entry)i$.next();
  17. AttributeKey<Object> key = (AttributeKey)e.getKey();
  18. channel.attr(key).set(e.getValue());
  19. }
  20. }
  21.  
  22. ChannelPipeline p = channel.pipeline();
  23. if (this.handler() != null) {
  24. p.addLast(new ChannelHandler[]{this.handler()});
  25. }
  26.  
  27. final EventLoopGroup currentChildGroup = this.childGroup;
  28. final ChannelHandler currentChildHandler = this.childHandler;
  29. Map var9 = this.childOptions;
  30. final Entry[] currentChildOptions;
  31. synchronized(this.childOptions) {
  32. currentChildOptions = (Entry[])this.childOptions.entrySet().toArray(newOptionArray(this.childOptions.size()));
  33. }
  34.  
  35. var9 = this.childAttrs;
  36. final Entry[] currentChildAttrs;
  37. synchronized(this.childAttrs) {
  38. currentChildAttrs = (Entry[])this.childAttrs.entrySet().toArray(newAttrArray(this.childAttrs.size()));
  39. }
  40.  
  41. p.addLast(new ChannelHandler[]{new ChannelInitializer<Channel>() {
  42. public void initChannel(Channel ch) throws Exception {
  43. ch.pipeline().addLast(new ChannelHandler[]{new ServerBootstrap.ServerBootstrapAcceptor(currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)});
  44. }
  45. }});
  46. }

  初始化主要作用:

  • 将引导类配置的option设置到上一步创建与Channel绑定的Configure类中
  • 设置attrs
  • 将使用引导类handler()设置的handler添加到pipeline上面
  • 配置channelOption,channelOption是为了childChannel进行服务的
  • 将连接器ServerBootstrapAcceptor添加到pipeline上面

  连接器其实也是一种ChannelHandler,在服务器启动完成时候,NioServerSocketChannel的pipeline的结构如下:

  1. Head[I/O] <--> ServerBootstrapAcceptor[IN] <--> Tail[IN]

  这里的连接处理器,作用就是处理客户端的连接。其逻辑比较简单:在服务器启动时调用childHandler方法设置了ServerBootstrap的子Channel的处理器,此时会将childChannelHandler添加到子Channel中(NioSocketChannel会在连接过程中创建);设置子Channel的配置和属性;最后将子Channel注册到子线程池组中。经过这个连接处理器的时候子channel已经被创建好了。

  1. public void channelRead(ChannelHandlerContext ctx, Object msg) {
  2. final Channel child = (Channel)msg;
  3. child.pipeline().addLast(new ChannelHandler[]{this.childHandler});
  4. Entry[] arr$ = this.childOptions;
  5. int len$ = arr$.length;
  6.  
  7. int i$;
  8. Entry e;
  9. for(i$ = 0; i$ < len$; ++i$) {
  10. e = arr$[i$];
  11.  
  12. try {
  13. if (!child.config().setOption((ChannelOption)e.getKey(), e.getValue())) {
  14. ServerBootstrap.logger.warn("Unknown channel option: " + e);
  15. }
  16. } catch (Throwable var10) {
  17. ServerBootstrap.logger.warn("Failed to set a channel option: " + child, var10);
  18. }
  19. }
  20.  
  21. arr$ = this.childAttrs;
  22. len$ = arr$.length;
  23.  
  24. for(i$ = 0; i$ < len$; ++i$) {
  25. e = arr$[i$];
  26. child.attr((AttributeKey)e.getKey()).set(e.getValue());
  27. }
  28.  
  29. try {
  30. this.childGroup.register(child).addListener(new ChannelFutureListener() {
  31. public void operationComplete(ChannelFuture future) throws Exception {
  32. if (!future.isSuccess()) {
  33. ServerBootstrap.ServerBootstrapAcceptor.forceClose(child, future.cause());
  34. }
  35.  
  36. }
  37. });
  38. } catch (Throwable var9) {
  39. forceClose(child, var9);
  40. }
  41.  
  42. }

  

3.Channel的注册

  1. ChannelFuture regFuture = this.group().register(channel);

  最终调用的是AbstractChannel的register方法,将一个channel和一个eventloop进行绑定。channel和eventloop是多对一的关系,多个channel可以注册到一个eventloop上面。

  1.   public final void register(EventLoop eventLoop, final ChannelPromise promise) {
  2. if (eventLoop == null) {
  3. throw new NullPointerException("eventLoop");
  4. } else if (AbstractChannel.this.isRegistered()) {
  5. promise.setFailure(new IllegalStateException("registered to an event loop already"));
  6. } else if (!AbstractChannel.this.isCompatible(eventLoop)) {
  7. promise.setFailure(new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
  8. } else {
  9. AbstractChannel.this.eventLoop = eventLoop;
  10. if (eventLoop.inEventLoop()) {
  11. this.register0(promise);
  12. } else {
  13. try {
  14. eventLoop.execute(new OneTimeTask() {
  15. public void run() {
  16. AbstractUnsafe.this.register0(promise);
  17. }
  18. });
  19. } catch (Throwable var4) {
  20. AbstractChannel.logger.warn("Force-closing a channel whose registration task was not accepted by an event loop: {}", AbstractChannel.this, var4);
  21. this.closeForcibly();
  22. AbstractChannel.this.closeFuture.setClosed();
  23. this.safeSetFailure(promise, var4);
  24. }
  25. }
  26.  
  27. }
  28. }

  从代码可知,如果当前线程是eventloop的线程,则直接执行register0(promise),但知道现在为止,我们的代码一直在main()函数的线程中执行,所以执行的分支是eventLoop.execute(..);

  继续调用会调用SingleThreadEventExecutor的executor(task)函数。

  1. public void execute(Runnable task) {
  2. if (task == null) {
  3. throw new NullPointerException("task");
  4. } else {
  5. boolean inEventLoop = this.inEventLoop();
  6. if (inEventLoop) {
  7. this.addTask(task);
  8. } else {
  9. this.startThread();
  10. this.addTask(task);
  11. if (this.isShutdown() && this.removeTask(task)) {
  12. reject();
  13. }
  14. }
  15.  
  16. if (!this.addTaskWakesUp && this.wakesUpForTask(task)) {
  17. this.wakeup(inEventLoop);
  18. }
  19.  
  20. }
  21. }

  因为当前线程不是eventloop线程,所以使用startThread()创建eventloop线程,eventloop线程创建并启动后,通过调用addTask()将task任务添加到taskqueue中。

  通过register0(promise)将channel进行注册。

  1. private void register0(ChannelPromise promise) {
  2. try {
  3. if (!promise.setUncancellable() || !this.ensureOpen(promise)) {
  4. return;
  5. }
  6.  
  7. boolean firstRegistration = this.neverRegistered;
  8. AbstractChannel.this.doRegister();
  9. this.neverRegistered = false;
  10. AbstractChannel.this.registered = true;
  11. this.safeSetSuccess(promise);
  12. AbstractChannel.this.pipeline.fireChannelRegistered();
  13. if (firstRegistration && AbstractChannel.this.isActive()) {
  14. AbstractChannel.this.pipeline.fireChannelActive();
  15. }
  16. } catch (Throwable var3) {
  17. this.closeForcibly();
  18. AbstractChannel.this.closeFuture.setClosed();
  19. this.safeSetFailure(promise, var3);
  20. }
  21.  
  22. }
  1.   protected void doRegister() throws Exception {
  2. boolean selected = false;
  3.  
  4. while(true) {
  5. try {
  6. this.selectionKey = this.javaChannel().register(this.eventLoop().selector, 0, this);
  7. return;
  8. } catch (CancelledKeyException var3) {
  9. if (selected) {
  10. throw var3;
  11. }
  12.  
  13. this.eventLoop().selectNow();
  14. selected = true;
  15. }
  16. }
  17. }

  可以看出最终还是通过javaChannel()注册到一个selector上面。在注册完了之后,会将channel注册完毕的事件通知到pipeline上。

  1. AbstractChannel.this.pipeline.fireChannelRegistered();

4.端口绑定

端口绑定是通过doBind中的doBind0()实现的。

  1. doBind0(regFuture, channel, localAddress, promise);
  1.   private static void doBind0(final ChannelFuture regFuture, final Channel channel, final SocketAddress localAddress, final ChannelPromise promise) {
  2. channel.eventLoop().execute(new Runnable() {
  3. public void run() {
  4. if (regFuture.isSuccess()) {
  5. channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
  6. } else {
  7. promise.setFailure(regFuture.cause());
  8. }
  9.  
  10. }
  11. });
  12. }

  Channel的bind函数调用链如下:

  1. // AbstractChannel
  2. public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
  3. return pipeline.bind(localAddress, promise);
  4. }
  5.  
  6. // DefaultChannelPipeline
  7. public final ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
  8. // 因为bind是一个outbound事件,从pipeline链尾tailContext开始执行
  9. return tail.bind(localAddress, promise);
  10. }
  11.  
  12. // tail context的父类AbstractChannelHandlerContext
  13. public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
  14. if (localAddress == null) {
  15. throw new NullPointerException("localAddress");
  16. }
  17. if (isNotValidPromise(promise, false)) {
  18. // cancelled
  19. return promise;
  20. }
  21.  
  22. // 应用程序没有添加outbound的情况下,找到的next context是head context
  23. final AbstractChannelHandlerContext next = findContextOutbound();
  24. EventExecutor executor = next.executor();
  25. if (executor.inEventLoop()) {
  26. next.invokeBind(localAddress, promise);
  27. } else {
  28. safeExecute(executor, new Runnable() {
  29. @Override
  30. public void run() {
  31. next.invokeBind(localAddress, promise);
  32. }
  33. }, promise, null);
  34. }
  35. return promise;
  36. }
  37.  
  38. // head context的父类AbstractChannelHandlerContext
  39. private void invokeBind(SocketAddress localAddress, ChannelPromise promise) {
  40. if (invokeHandler()) {
  41. try {
  42. ((ChannelOutboundHandler) handler()).bind(this, localAddress, promise);
  43. } catch (Throwable t) {
  44. notifyOutboundHandlerException(t, promise);
  45. }
  46. } else {
  47. bind(localAddress, promise);
  48. }
  49. }
  50.  
  51. // 还在head context的父类AbstractChannelHandlerContext
  52. public void bind(
  53. ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise)
  54. throws Exception {
  55. // 通过unsafe调用bind了,意味着会调用JVM的功能,操作底层的一些函数了
  56. unsafe.bind(localAddress, promise);
  57. }
  58.  
  59. // AbstractUnsafe
  60. public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
  61. assertEventLoop();
  62.  
  63. if (!promise.setUncancellable() || !ensureOpen(promise)) {
  64. return;
  65. }
  66.  
  67. // See: https://github.com/netty/netty/issues/576
  68. if (Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) &&
  69. localAddress instanceof InetSocketAddress &&
  70. !((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress() &&
  71. !PlatformDependent.isWindows() && !PlatformDependent.maybeSuperUser()) {
  72. // Warn a user about the fact that a non-root user can't receive a
  73. // broadcast packet on *nix if the socket is bound on non-wildcard address.
  74. logger.warn(
  75. "A non-root user can't receive a broadcast packet if the socket " +
  76. "is not bound to a wildcard address; binding to a non-wildcard " +
  77. "address (" + localAddress + ") anyway as requested.");
  78. }
  79.  
  80. boolean wasActive = isActive();
  81. try {
  82. // 做实际的bind工作
  83. doBind(localAddress);
  84. } catch (Throwable t) {
  85. safeSetFailure(promise, t);
  86. closeIfClosed();
  87. return;
  88. }
  89.  
  90. if (!wasActive && isActive()) {
  91. invokeLater(new Runnable() {
  92. @Override
  93. public void run() {
  94. pipeline.fireChannelActive();
  95. }
  96. });
  97. }
  98.  
  99. safeSetSuccess(promise);
  100. }
  101.  
  102. // NioServerSocketChannel
  103. protected void doBind(SocketAddress localAddress) throws Exception {
  104. if (PlatformDependent.javaVersion() >= 7) {
  105. // 最终通过JVM调用server socket的bind、listen等函数,启动服务端
  106. javaChannel().bind(localAddress, config.getBacklog());
  107. } else {
  108. javaChannel().socket().bind(localAddress, config.getBacklog());
  109. }
  110. }

  当端口绑定完成之后,会调用fireChannelActive()方法,通知端口已经绑定完成。并且会给当前的channel注册accept事件。

  1.   public ChannelPipeline fireChannelActive() {
  2. this.head.fireChannelActive();
  3. if (this.channel.config().isAutoRead()) {
  4. this.channel.read();
  5. }
  6.  
  7. return this;
  8. }

  fireChannelActive()调用channel的read(),由于read是outbound方法,最终会调用NioServerSocketChannel的unsafe的beginRead,在里面注册Accept事件。

  1.   protected void doBeginRead() throws Exception {
  2. if (!this.inputShutdown) {
  3. SelectionKey selectionKey = this.selectionKey;
  4. if (selectionKey.isValid()) {
  5. this.readPending = true;
  6. int interestOps = selectionKey.interestOps();
  7. if ((interestOps & this.readInterestOp) == 0) {
  8. selectionKey.interestOps(interestOps | this.readInterestOp);
  9. }
  10.  
  11. }
  12. }
  13. }

Netty源码分析之服务端启动的更多相关文章

  1. Netty源码分析之服务端启动过程

    一.首先来看一段服务端的示例代码: public class NettyTestServer { public void bind(int port) throws Exception{ EventL ...

  2. zookeeper源码分析之一服务端启动过程

    zookeeper简介 zookeeper是为分布式应用提供分布式协作服务的开源软件.它提供了一组简单的原子操作,分布式应用可以基于这些原子操作来实现更高层次的同步服务,配置维护,组管理和命名.zoo ...

  3. zookeeper源码分析之四服务端(单机)处理请求流程

    上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...

  4. Netty 4源码解析:服务端启动

    Netty 4源码解析:服务端启动 1.基础知识 1.1 Netty 4示例 因为Netty 5还处于测试版,所以选择了目前比较稳定的Netty 4作为学习对象.而且5.0的变化也不像4.0这么大,好 ...

  5. zookeeper源码分析之五服务端(集群leader)处理请求流程

    leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...

  6. 4. 源码分析---SOFARPC服务端暴露

    服务端的示例 我们首先贴上我们的服务端的示例: public static void main(String[] args) { ServerConfig serverConfig = new Ser ...

  7. Nacos(二)源码分析Nacos服务端注册示例流程

    上回我们讲解了客户端配置好nacos后,是如何进行注册到服务器的,那我们今天来讲解一下服务器端接收到注册实例请求后会做怎么样的处理. 首先还是把博主画的源码分析图例发一下,让大家对整个流程有一个大概的 ...

  8. Netty源码分析之服务启动

    本节主要分析server的启动过程. Netty是基于Nio实现的,所以也离不开selector.serverSocketChannel.socketChannel和selectKey等,只不过Net ...

  9. TeamTalk源码分析之服务端描述

    TTServer(TeamTalk服务器端)主要包含了以下几种服务器: LoginServer (C++): 登录服务器,分配一个负载小的MsgServer给客户端使用 MsgServer (C++) ...

随机推荐

  1. ambari安装调研

    http://blog.csdn.net/daiyutage/article/details/52210830 ssh-keygen ssh-copy-id -i ~/.ssh/id_rsa.pub ...

  2. 最新版本sublime text3注册码

    TwitterInc 200 User License EA7E-890007 1D77F72E 390CDD93 4DCBA022 FAF60790 61AA12C0 A37081C5 D03164 ...

  3. css 背景图片虚化效果

    转载地址:http://blog.csdn.net/ohehehou/article/details/51975539 需求:一个div设置了background: url,现在需要使图片背景模糊,d ...

  4. background-color:transparent

    background-color没有none值   在工作中发现, 这样是没反应的, 要写这个样式才能去掉背景颜色() background-color属性详细链接: http://www.w3sch ...

  5. 剑指offer——python【第14题】链表中倒数第k个节点

    题目描述 输入一个链表,输出该链表中倒数第k个结点. 思路 注意,看清楚,是输出节点,而不是输出节点值 可以先求出链表总长度,然后正向遍历得到第n个节点 解答 class Solution: def ...

  6. 实战一个职业技术学校。 by:hack某某

    这是我们的目标,某一技术学院,这是一个注入点 上sqlmap 跑出了管理账号密码 扫后台 没有找到,注入就相当鸡肋了 换换其他思路 dba权限,想到了直接写入 找找路径之类的 找到了,运气相当的好 直 ...

  7. day21 二十一、垃圾回收机制、re正则

    一.内存管理 1.垃圾回收机制:不能被程序访问到的数据称之为垃圾 2.引用计数:引用计数是用来记录值的内存地址被记录的次数 每一次对值地址的引用都可以使该值的引用计数 +1 每一次对值地址的释放都可以 ...

  8. 解决键盘输入被JDB占用的问题

    解决键盘输入被JDB占用的问题 本周的任务"迭代和JDB"在使用JDB调试时需要键盘输入数据,但我在正确的位置输入数据后发现JDB提示如图所示的错误. 上网查找后得知该错误的产生是 ...

  9. CH 3101 - 阶乘分解 - [埃筛]

    题目链接:传送门 题解: $(1e6)!$ 这种数字,表示都表示不出来,想直接 $O(\sqrt{N})$ 分解质因数这种事情就不要想了. 考虑 $N!$ 的特殊性,这个数字的所有可能包含的质因子,就 ...

  10. Java图片合并

    /** * 纵向合并图片,ossObject.getObjectContent()返回InputStream对象 */ private BufferedImage mergeImage(List< ...