Jetty源码剖析系列(6) - Connector与ServerSocket
本文主要从源代码的角度来分析Jetty的Connector如何通过ServerSocket来绑定和监听网络地址和端口的过程。
Jetty的Connector实现类是ServerConnector,循例从它的doStart()方法开始(至于是如何到达这个方法,请移步本系列的前几篇):
它的doStart方法先调用父类AbstractNetworkConnector的doStart方法:
在父类的doStart方法里面会调用子类实现的open方法,此处是ServerConnector的open方法:
到这里已经可以看到Jetty的Connector与Java NIO的关系了,将会调用openAcceptChannel方法来构建一个NIO的ServerSocketChannel:
从上图的代码我们可以看到,Jetty默认用Java NIO来实现了它的网络框架,另外一点就是,如果你用java来实现一套网络框架,无非就是BIO和NIO两种,返朴归真。
先常规调用ServerSocketChannel的静态方法open方法来打开一个ServerSocketChannel: 因为Java的NIO是基于Selector来实现的,所以在这里会通过
SelectorProvider类的静态方法provider来加载Selector的实现类:
它先尝试从
System Property里加载Selector的实现类并实例化: 如果没有的话,就尝试通过
SPI的方式来加载Selector的实现类并实例化: 如果还是没有的话,就用
Default的Selector,这个是根据底层操作系统来决定,比如,在Windows系统里将会是WindowsSelectorProvider这个类:
WindowsSelectorProvider继承于SelectorProviderImpl这个抽象类,
它的openServerSocketChannel方法将会返回一个新建出来的ServerSocketChannelImpl对象:
到此我们再回过头看ServerConnector的openAcceptChannel方法, 上图通过
ServerSocketChannel.open()方法拿到的就是一个ServerSocketChannelImpl实例对象,
我们接着往下看,
如果你没在启动参数里加--host参数,那它就会去指定所谓的全零地址:
那这个所谓的anyLocalAddress又是怎么来的呢? 这里impl是InetAddressImpl,它是在加载InetAddress类的时候,通过它的类静态块初始化的:
而InetAddressImplFactory会通过一个本地方法isIPv6Supported来判断底层操作系统是否支持IPv6,来加载InetAddressImpl的实现类:
在我的windows系统里,是支持IPv6的,所以返回的InetAddressImpl应该是Inet6AddressImpl对象,再来看它的anyLocalAddress方法:
这里会通过preferIPv6Address这个静态便来来判断应用有没有指定prefer使用IPv6地址,这个静态变量同样在InetAddress类的静态代码块里面初始化了:
我们可以看到,如果应用没有指定了java.net.preferIPv6Addresses这个-D参数的话,就算InetAddressImpl实例对象是Inet6AddressImpl,所谓的anyLocalAddress还是会用Inet4AddressImpl的anyLocalAddress:
而Inet4AddressImpl的anyLocalAddress是大名鼎鼎的0.0.0.0:
好了,扯远了,我们再回到ServerConnector的openAcceptChannel方法: 在这里它就会拿出ServerSocketChannelImpl里的ServerSocket来进行网络地址和端口的绑定和监听! 我们先来看ServerSocketChannelImpl的socket()方法:
它会创建一个ServerSocket的适配对象:
ServerSocketAdaptor继承于ServerSocket。
我们接着来看ServerSocket的bind方法,它接收两个参数,一个是前面分析的InetSocketAddress,此处为IPv4的全零,0.0.0.0地址,一个是接收队列的size,此处为0(这个值在bind方法里面将会会重置),下面进入bind方法: 我们会看到接收队列的size会被重置为50,也就是说TCP接收队列里面最多只能有50个请求。我们接着要关注的是下面两行代码:
上面两行代码就是实现了网络地址的绑定和端口的监听。 我们先来看ServerSocket的getImpl方法:
首次调用的话,它会去调用createImpl方法:
这里impl是一个SocketImpl,它是通过调用setImpl方法来创建:
这里的factory是一个SocketImplFactory的静态变量,默认为null,而一路我们也没有设置,所以它还是为null,所以impl将会被新建为一个SocksSocketImpl的实例对象。
SocksSocketImpl继承于PlainSocketImpl,我们再看PlainSocketImpl的默认构造函数:
我们看到它会根据userDualStackImpl这个静态变量来实例化不同的AbstractPlainSocketImpl对象,而useDUalStackImpl这个静态变量也是在PlainSocketImpl的静态代码块里面初始化:
我们可以看到它是根据两个System Property值来确定的,一个是os.version,系统版本,我当前用的是JDK1.8的windows版, 另一个是java.net.preferIPv4Stack(这个跟我们上文看到的java.net.preferIPv6Addresses参数很相像,一个IPv4,一个IPv6),就是说如果系统版本(此处为windows版本)大于6.0并且应用没有指定参数java.net.preferIPv4Stack的话,useDualStackImpl为true,也就是说将会使用IPv4和IPv6双栈!
双栈的话 ,impl就会是DualStackPlainSocketImpl, 单栈的话impl就是TwoStacksPlainSocketImpl(其实这里我有点不明白,Dual和Two不都是有指两个的意思吗?望有识之士不吝赐教,私信告知。eric_cen@outlook.com), 再回到之前的bind方法,实际上调用的是AbstractPlainSocketImpl的bind方法,
而最终双栈和单栈的区别又在于DualStackPlainSocketImpl和TwoStacksPlainSocketImpl的socketBind方法。 DualStackPlainSocketImpl.socketBind方法:
最终调用的是本地方法bind0. TwoStacksPlainSocketImpl.socketBind方法:
最终调用的是本地方法socketBind。
而监听端口的listen方法最终也是分开DualStackPlainSocketImpl和TwoStacksPlainSocketImpl各自实现。
至此,Jetty的Connector如何绑定网络地址和监听端口分析完毕。其实本文大部分都是在分析Java自带的ServerSocket工作细节。
Written on 22 February 2018