我们在使用Netty的时候的初始化代码一般如下

  1. EventLoopGroup bossGroup = new NioEventLoopGroup();
  2. EventLoopGroup workerGroup = new NioEventLoopGroup();
  3. try {
  4. // 配置服务器的NIO线程组
  5. ServerBootstrap b = new ServerBootstrap();
  6. b.group(bossGroup, workerGroup)
  7. .channel(NioServerSocketChannel.class)
  8. .option(ChannelOption.SO_BACKLOG, 1024)
  9. .childHandler(new ChildChannelHandler());
  10. // 绑定端口,同步等待成功
  11. ChannelFuture f = b.bind(port).sync();
  12. // 等待服务端监听端口关闭
  13. f.channel().closeFuture().sync();
  14. } finally {
  15. // 优雅退出,释放线程池资源
  16. bossGroup.shutdownGracefully();
  17. workerGroup.shutdownGracefully();
  18. }

前面已经说过线程池的启动过程,接下来就是通过builder模式构造启动参数,接下来看看bind的过程。channel的注册和ip:port的绑定都是在bind方法中进行的,bind方法的主要逻辑是

  1. 初始化channel
  2. channel注册到selector

NioServerSocketChannel

先看看channel的初始化,server端使用的NioServerSocketChannel封装了JDK的ServerSocketChannel,初始化过程如下:

  1. // 配置使用的channel的时候会指定对应的channelFactory
  2. public B channel(Class<? extends C> channelClass) {
  3. if (channelClass == null) {
  4. throw new NullPointerException("channelClass");
  5. }
  6. return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
  7. }
  8. final ChannelFuture initAndRegister() {
  9. Channel channel = null;
  10. try {
  11. // channelFactory是ReflectiveChannelFactory
  12. channel = channelFactory.newChannel();
  13. init(channel);
  14. } catch (Throwable t) {
  15. if (channel != null) {
  16. // channel can be null if newChannel crashed (eg SocketException("too many open files"))
  17. channel.unsafe().closeForcibly();
  18. // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
  19. return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
  20. }
  21. // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
  22. return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
  23. }
  24. ChannelFuture regFuture = config().group().register(channel);
  25. if (regFuture.cause() != null) {
  26. if (channel.isRegistered()) {
  27. channel.close();
  28. } else {
  29. channel.unsafe().closeForcibly();
  30. }
  31. }
  32. // If we are here and the promise is not failed, it's one of the following cases:
  33. // 1) If we attempted registration from the event loop, the registration has been completed at this point.
  34. // i.e. It's safe to attempt bind() or connect() now because the channel has been registered.
  35. // 2) If we attempted registration from the other thread, the registration request has been successfully
  36. // added to the event loop's task queue for later execution.
  37. // i.e. It's safe to attempt bind() or connect() now:
  38. // because bind() or connect() will be executed *after* the scheduled registration task is executed
  39. // because register(), bind(), and connect() are all bound to the same thread.
  40. return regFuture;
  41. }

上面使用的是io.netty.channel.ReflectiveChannelFactory#newChannel来创建channel,利用反射创建实例,使用的是NioServerSocketChannel的无参构造方法,在午无参造方法中调用newChannel

  1. // 创建serverChannel的时候先调用newSocket,然后调用下面的构造方法
  2. public NioServerSocketChannel(ServerSocketChannel channel) {
  3. // 设置当前socket监听的事件,由于是server一定要添加accept事件
  4. super(null, channel, SelectionKey.OP_ACCEPT);
  5. config = new NioServerSocketChannelConfig(this, javaChannel().socket());
  6. }
  7. // io.netty.channel.socket.nio.NioServerSocketChannel#newSocket
  8. private static ServerSocketChannel newSocket(SelectorProvider provider) {
  9. try {
  10. /**
  11. * Use the {@link SelectorProvider} to open {@link SocketChannel} and so remove condition in
  12. * {@link SelectorProvider#provider()} which is called by each ServerSocketChannel.open() otherwise.
  13. *
  14. * See <a href="https://github.com/netty/netty/issues/2308">#2308</a>.
  15. */
  16. return provider.openServerSocketChannel();
  17. } catch (IOException e) {
  18. throw new ChannelException(
  19. "Failed to open a server socket.", e);
  20. }
  21. }
  22. ServerSocketChannelImpl(SelectorProvider sp) throws IOException {
  23. super(sp);
  24. // 创建一个socket,返回的是socket对应的文件描述符
  25. this.fd = Net.serverSocket(true);
  26. this.fdVal = IOUtil.fdVal(fd);
  27. this.state = ST_INUSE;
  28. }
  29. // sun.nio.ch.Net#serverSocket
  30. static FileDescriptor serverSocket(boolean stream) {
  31. // socket0是一个native方法,返回的是int类型的linux的文件描述符,使用newFD转化为Java的文件描述符
  32. return IOUtil.newFD(socket0(isIPv6Available(), stream, true));
  33. }
  34. // jdk/src/solaris/native/sun/nio/ch/Net.c
  35. JNIEXPORT int JNICALL
  36. Java_sun_nio_ch_Net_socket0(JNIEnv *env, jclass cl, jboolean preferIPv6,
  37. jboolean stream, jboolean reuse)
  38. {
  39. // 省略中间代码...
  40. // 调用socket方法创建一个socket,并返回对应的文件描述符
  41. fd = socket(domain, type, 0);
  42. if (fd < 0) {
  43. return handleSocketError(env, errno);
  44. }
  45. // 省略中间代码...
  46. return fd;
  47. }

不难看出channel初始化的过程就是创建了一个socket,接下来看看channel的注册

  1. // config()返回的是ServerBootstrapConfig
  2. // group()返回的是parentGroup,对应开始的例子是bossGroup,也就是NioEventLoopGroup
  3. // 所以是调用的是NioEventLoopGroup.register,该方法继承自MultithreadEventLoopGroup
  4. ChannelFuture regFuture = config().group().register(channel);
  5. // io.netty.channel.MultithreadEventLoopGroup#register(io.netty.channel.Channel)
  6. public ChannelFuture register(Channel channel) {
  7. // 使用的是bossGroup,next方法选出第一个NioEventLoop,调用NioEventLoop.register,该方法继承自SingleThreadEventLoop
  8. return next().register(channel);
  9. }
  10. // io.netty.channel.SingleThreadEventLoop#register(io.netty.channel.Channel)
  11. public ChannelFuture register(Channel channel) {
  12. // 注册的还是使用一个promise,可以异步注册
  13. return register(new DefaultChannelPromise(channel, this));
  14. }
  15. // io.netty.channel.SingleThreadEventLoop#register(io.netty.channel.ChannelPromise)
  16. public ChannelFuture register(final ChannelPromise promise) {
  17. ObjectUtil.checkNotNull(promise, "promise");
  18. // channel返回的是NioServerSocketChannel
  19. // unsafe返回的是io.netty.channel.nio.AbstractNioMessageChannel.NioMessageUnsafe
  20. // 所以调用的是NioMessageUnsafe.register,该方法继承自AbstractUnsafe
  21. promise.channel().unsafe().register(this, promise);
  22. return promise;
  23. }
  24. // io.netty.channel.AbstractChannel.AbstractUnsafe#register
  25. public final void register(EventLoop eventLoop, final ChannelPromise promise) {
  26. // 省略中间代码...
  27. // 当前线程是main线程,eventLoop是bossGroup中的一个线程,所以这里返回false,会在新线程中执行register0
  28. if (eventLoop.inEventLoop()) {
  29. register0(promise);
  30. } else {
  31. try {
  32. // 在eventLoop中执行
  33. eventLoop.execute(new Runnable() {
  34. @Override
  35. public void run() {
  36. register0(promise);
  37. }
  38. });
  39. } catch (Throwable t) {
  40. // 省略中间代码...
  41. }
  42. }
  43. }
  44. private void register0(ChannelPromise promise) {
  45. try {
  46. // 省略中间代码...
  47. // 这里面主要是调用ServerSocketChannelImpl.register,注册的过程中主要是将需要监听的文件描述符添加到EPollArrayWrapper中
  48. doRegister();
  49. neverRegistered = false;
  50. registered = true;
  51. // Ensure we call handlerAdded(...) before we actually notify the promise. This is needed as the
  52. // user may already fire events through the pipeline in the ChannelFutureListener.
  53. pipeline.invokeHandlerAddedIfNeeded();
  54. safeSetSuccess(promise);
  55. pipeline.fireChannelRegistered();
  56. // Only fire a channelActive if the channel has never been registered. This prevents firing
  57. // multiple channel actives if the channel is deregistered and re-registered.
  58. if (isActive()) {
  59. if (firstRegistration) {
  60. pipeline.fireChannelActive();
  61. } else if (config().isAutoRead()) {
  62. // This channel was registered before and autoRead() is set. This means we need to begin read
  63. // again so that we process inbound data.
  64. //
  65. // See https://github.com/netty/netty/issues/4805
  66. beginRead();
  67. }
  68. }
  69. } catch (Throwable t) {
  70. // 省略中间代码...
  71. }
  72. }

下面看看channel注册过程中做了哪些事情

  1. // sun.nio.ch.SelectorImpl#register
  2. // 这里ch是ServerSocketChannelImpl
  3. // attachment是NioServerSocketChannel
  4. // ops是0,这里并不注册需要监听的事件
  5. // selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
  6. protected final SelectionKey register(AbstractSelectableChannel ch,
  7. int ops,
  8. Object attachment)
  9. {
  10. if (!(ch instanceof SelChImpl))
  11. throw new IllegalSelectorException();
  12. // 创建一个SelectionKeyImpl,
  13. SelectionKeyImpl k = new SelectionKeyImpl((SelChImpl)ch, this);
  14. k.attach(attachment);
  15. synchronized (publicKeys) {
  16. // 调用sun.nio.ch.EPollSelectorImpl#implRegister
  17. implRegister(k);
  18. }
  19. // 设置当前channel关注的事件
  20. k.interestOps(ops);
  21. return k;
  22. }
  23. protected void implRegister(SelectionKeyImpl ski) {
  24. if (closed)
  25. throw new ClosedSelectorException();
  26. SelChImpl ch = ski.channel;
  27. int fd = Integer.valueOf(ch.getFDVal());
  28. fdToKey.put(fd, ski);
  29. // poolWrapper是epoll监听事件所需数据结构的java版本
  30. // add方法调用setUpdateEvents来指定当前socket监听的事件
  31. pollWrapper.add(fd);
  32. keys.add(ski);
  33. }
  34. /**
  35. * struct epoll_event {
  36. * __uint32_t events;
  37. * epoll_data_t data;
  38. * };
  39. * 由于一开始并不知道会监听多少个socket,所以jdk默认指定了MAX_UPDATE_ARRAY_SIZE
  40. * 如果小于MAX_UPDATE_ARRAY_SIZE则使用数组eventsLow存储每个socket监听的事件,eventsLow的下标就是socket对应的文件描述符
  41. * 如果大于等于MAX_UPDATE_ARRAY_SIZE个则使用EPollArrayWrapper#eventsHigh,也就是一个map来保存每个socket监听的事件
  42. *
  43. * 注意这个时候调用setUpdateEvents的events参数是0,也就是还没有执行监听的事件类型
  44. */
  45. private void setUpdateEvents(int fd, byte events, boolean force) {
  46. if (fd < MAX_UPDATE_ARRAY_SIZE) {
  47. if ((eventsLow[fd] != KILLED) || force) {
  48. eventsLow[fd] = events;
  49. }
  50. } else {
  51. Integer key = Integer.valueOf(fd);
  52. if (!isEventsHighKilled(key) || force) {
  53. eventsHigh.put(key, Byte.valueOf(events));
  54. }
  55. }
  56. }

需要注意的时候上面并没有设置当前channel监听的事件,真正设置监听的事件类型是在beginRead方法里面,在当前channel被激活的时候会调用beginRead方法

  1. // io.netty.channel.nio.AbstractNioChannel#doBeginRead
  2. protected void doBeginRead() throws Exception {
  3. // Channel.read() or ChannelHandlerContext.read() was called
  4. final SelectionKey selectionKey = this.selectionKey;
  5. if (!selectionKey.isValid()) {
  6. return;
  7. }
  8. readPending = true;
  9. final int interestOps = selectionKey.interestOps();
  10. if ((interestOps & readInterestOp) == 0) {
  11. // readInterestOp是16,在NioServerSocketChannel构造方法里面指定了这个channel需要监听accept事件
  12. // 这里才是真正设置socket监听事件的地方
  13. // 下面这个方法最后会调用到sun.nio.ch.EPollArrayWrapper#setInterest
  14. selectionKey.interestOps(interestOps | readInterestOp);
  15. }
  16. }
  17. // sun.nio.ch.EPollArrayWrapper#setInterest
  18. void setInterest(int fd, int mask) {
  19. synchronized (updateLock) {
  20. // record the file descriptor and events
  21. int oldCapacity = updateDescriptors.length;
  22. if (updateCount == oldCapacity) {
  23. int newCapacity = oldCapacity + INITIAL_PENDING_UPDATE_SIZE;
  24. int[] newDescriptors = new int[newCapacity];
  25. System.arraycopy(updateDescriptors, 0, newDescriptors, 0, oldCapacity);
  26. updateDescriptors = newDescriptors;
  27. }
  28. updateDescriptors[updateCount++] = fd;
  29. // events are stored as bytes for efficiency reasons
  30. byte b = (byte)mask;
  31. assert (b == mask) && (b != KILLED);
  32. // 上面已经说过这个方法了,把当前socket对应的文件描述符监听的事件设置为b
  33. setUpdateEvents(fd, b, false);
  34. }
  35. }

到这里一个serverSocketChannel注册成功了,而且也设置了关注的事件,接下来看看完成ip:port的绑定

  1. public ServerSocketChannel bind(SocketAddress local, int backlog) throws IOException {
  2. synchronized (lock) {
  3. // 省略中间代码...
  4. // 调用native方法的bind,最后调用linux的bind方法
  5. Net.bind(fd, isa.getAddress(), isa.getPort());
  6. // 最后调用listen方法完成监听serverSocket的文件描述符
  7. Net.listen(fd, backlog < 1 ? 50 : backlog);
  8. synchronized (stateLock) {
  9. localAddress = Net.localAddress(fd);
  10. }
  11. }
  12. return this;
  13. }

总结

server在bind的过程中主要初始化了NioServerSocketChannel,并将channel注册到selector,添加了channel需要监听的事件,接下来该socketChannel就可以监听端口接受来自客户端的请求了。

Netty源码—二、server启动(2)的更多相关文章

  1. Netty源码解析—客户端启动

    Netty源码解析-客户端启动 Bootstrap示例 public final class EchoClient { static final boolean SSL = System.getPro ...

  2. Netty源码—一、server启动(1)

    Netty作为一个Java生态中的网络组件有着举足轻重的位置,各种开源中间件都使用Netty进行网络通信,比如Dubbo.RocketMQ.可以说Netty是对Java NIO的封装,比如ByteBu ...

  3. Netty源码分析 (三)----- 服务端启动源码分析

    本文接着前两篇文章来讲,主要讲服务端类剩下的部分,我们还是来先看看服务端的代码 /** * Created by chenhao on 2019/9/4. */ public final class ...

  4. Netty源码剖析-启动服务

    参考文献:极客时间傅健老师的<Netty源码剖析与实战>Talk is cheap.show me the code! --1主线分两步: 一:首先在our thread里,如果写在mai ...

  5. Netty源码阅读(一) ServerBootstrap启动

    Netty源码阅读(一) ServerBootstrap启动 转自我的Github Netty是由JBOSS提供的一个java开源框架.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速 ...

  6. netty源码分析之二:accept请求

    我在前面说过了server的启动,差不多可以看到netty nio主要的东西包括了:nioEventLoop,nioMessageUnsafe,channelPipeline,channelHandl ...

  7. Netty 源码(一)服务端启动

    Netty 源码(一)服务端启动 Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) ServerBootstap 创建时序图如 ...

  8. Netty源码分析第1章(Netty启动流程)---->第1节: 服务端初始化

    Netty源码分析第一章:  Server启动流程 概述: 本章主要讲解server启动的关键步骤, 读者只需要了解server启动的大概逻辑, 知道关键的步骤在哪个类执行即可, 并不需要了解每一步的 ...

  9. Netty源码分析第1章(Netty启动流程)---->第2节: NioServerSocketChannel的创建

    Netty源码分析第一章:  Server启动流程 第二节:NioServerSocketChannel的创建 我们如果熟悉Nio, 则对channel的概念则不会陌生, channel在相当于一个通 ...

随机推荐

  1. 用post请求方式实现对地图服务的基本操作

    ArcGIS Server REST API 中的很多操作都可以用以下方式实现,具体参数的设置请查看其中的详细说明 public List<string> getGeometry(stri ...

  2. 基于DP的LCS(最长公共子序列)问题

    最长公共子序列,即给出两个序列,给出最长的公共序列,例如: 序列1 understand 序列2 underground 最长公共序列undernd,长度为7 一般这类问题很适合使用动态规划,其动态规 ...

  3. 页面标准文档流、浮动层、float属性(转)

    CSS float 浮动属性介绍 float属性:定义元素朝哪个方向浮动. 1.页面标准文档流.浮动层.float属性 1.1 文档流 HTML页面的标准文档流(默认布局)是:从上到下,从左到右,遇块 ...

  4. Python Cook函数笔记 【第一章】

    2017年4月28日 19:29:52 解压赋值给多个变量 可迭代的对象(list,tuple,string,文件对象,迭代器,生成器等),都可以进行解压赋值给多个对象. #!/usr/bin/env ...

  5. [转]关于python中带下划线的变量和函数的意义

    Python 的代码风格由 PEP 8 描述.这个文档描述了 Python 编程风格的方方面面.在遵守这个文档的条件下,不同程序员编写的 Python 代码可以保持最大程度的相似风格.这样就易于阅读, ...

  6. springboot+mybatis+ehcache实现缓存数据

    一.springboot缓存简介 在 Spring Boot中,通过@EnableCaching注解自动化配置合适的缓存管理器(CacheManager),Spring Boot根据下面的顺序去侦测缓 ...

  7. 由HashMap哈希算法引出的求余%和与运算&转换问题

    1.引出问题 在前面讲解HashMap 的源码实现时,有如下几点: ①.初始容量为 1<<4,也就是24 = 16 ②.负载因子是0.75,当存入HashMap的元素占比超过整个容量的75 ...

  8. spawn-fcgi运行fcgiwrap

    http://linuxjcq.blog.51cto.com/3042600/718002 标签:休闲 spawn-fcgi fcgiwarp fcgi 职场 原创作品,允许转载,转载时请务必以超链接 ...

  9. ATM机

    ATM 要求 示例代码: https://github.com/triaquae/py_training/tree/master/sample_code/day5-atm

  10. Github管理自己的代码-远程篇

    一.名词解释 Git Git是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目. Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版 ...