上一讲讲解过NIO的框图,可以看来,NIO通道是目前Tomcat7以后的默认的通道的推荐配置,在Tomcat6和以前的配置中,BIO是主流的配置:
只需要修改protocol协议部分即可,而后续还有APR协议,NIO2.0的协议,都是修改这个字段。
对于BIO的整体框图,基本和NIO保持类似,整体流程变化不大:
1.Http11Protocol
与NIO一样,这个Http11Protocol是默认的BIO的http1.1协议的处理类,Tomcat除了有NIO,BIO,其实还有两个通道:
APR是高性能通道,NIO2是基于纯异步IO的通道,这个会在后面的Tomcat中进行讲解。
Http11Protocol类中,依然持有Endpoint和handler的引用:
只不过,BIO对应的Endpoint是JIOEndpoint,对应的handler是Http11ConnectionHandler。
2.JIoEndpoint
JIoEndpoint是BIO的端点类,它和NIO一样,也是维护着线程池,只不过因为没有Selector.select,没有SocketChannel的通道的注册,所以相比NIO模式,没有Poller线程是非常容易理解的,反倒是NIO的三个线程不容易理解,BIO可以看做就是基于Socket进行操作。
首先,初始化的JIoEndpoint的时候,会调用bind方法绑定Serversocket到对应的端口:
bind方法是初始化构造JIoEndpoint的重要步骤,他的主要作用就是建立ServerSocketFactory。
根据SSL信道或者是普通的http的信道,Tomcat都实现了ServerSocketFactory,
普通的http通道的ServerSocketFactory就是DefaultServerSocketFactory类,其工厂方法就是创建ServerSocket,很简单:
对于SSL通道的ServerSocketFactory是JSSEServerSocketFactory,这个类创建的是SSLServerSocket:
其次,JIoEndpoint启动的时候,会将Acceptor线程和工作线程池启动起来:
除此之外,还启动了一个专门的线程,这个线程就是检查异步请求的Timeout的,后续会有专门的介绍针对于Tomcat的异步请求。
工作线程池,使用的就是JDK自带的ThreadPoolExecutor:
可以从线程的堆栈看到,对应的http-bio-8443-exec-n 这种线程都是工作的线程池:
如果在tomcat中没有指定工作线程池的设置,那么都走的是JDK自带的ThreadPoolExecutor的默认值。
3.Acceptor线程
Acceptor线程的主要作用和NIO一样,如果没有网络IO数据,该线程会一直serversocket.accept进行阻塞住。
当有数据的时候,首先将socekt数据设置Connector配置的一些属性,然后将该接力棒传递给工作线程池:
最后一步processSocket方法,非常简单:
直接调用工作线程池,将SocketProcessor作为工作任务传入到工作线程池中,进行执行。
这一步相比NIO的架构,缺少了NIO通道中的PollerEvent一个缓存的队列,NIO中有这样的一个队列是因为需要从Acceptor到Poller线程,中间传递需要一个缓存的地方,而可以看到上述的BIO中的代码,如果工作线程池已经满载了,会根据JDK的ThreadPoolExecutor的策略是缓存,还是直接拒绝,或者是timeout等待,只不过BIO将这块的策略决断交给了ThreadPoolExecutor来做了。
对于Acceptor线程中还有一个重要的作用,就是控制连接的个数,这个在NIO通道的分析中没有讲解,这里看一下
Acceptor线程在while轮询的时候,每一次最开始都会检查一下当前的最大的连接数超出没有,
如果超出了,直接按照上图的序列调用LimitLatch进行锁定。
我们发现,实际上LimitLatch也是模仿JDK中的读写锁,内部持有一个Sync的类,这个类继承了JDK中隐藏功与名的AQS队列,这个AQS队列还是比较著名的,去年在分析JDK源码的时候,多次在n个并发类中都看到过他的踪迹,其实现中几乎全部是CAS锁的实现。
4.SocketProcessor工作任务
SocketProcessor是工作任务,用于传入到工作线程池中,输入就是Acceptor传过来的socketWapper包装:
如果是SSL交互的话,Tomcat开放了握手的这个环节,但是并没有对应的实现,这个是因为SSL下的握手实现在SUN的包中做的,JDK提供的SSLServerSocket的接口已经隐藏了这个细节,我们可以从handshake这个第二步看到:
Tomcat中直接就可以拿到SSLSession,这个类可以获得相当于SSL已经是握手成功了,否则就会出现失败。
对于为什么保留beforeHandShake和handshake这两个步骤,是为了和NIO通道中的SSLEngine交互的接口做个兼容而已。
暂且不用管它,最重要的步骤就是第3步,也就是handler.process这一步,通过Http11ConenctionHandler进行处理http协议,并疯转出Response和Request两个对象,传递给后端的容器。
总结
后续的流程基本上和NIO通道一样,总结一下,BIO的结构因为缺少了selector和轮询,相比NIO少了一部分的内容,整体上就是使用的ServerSocket来进行通信的,一线程一请求的模式,代码看起来清晰易懂,但是,由于BIO的模型比较落后,在大多数的场景下,不如NIO,而现在Tomcat新版本也是NIO是默认的配置。