k.NIO方式SSL通道流程

时间:2022-10-21 20:11:45
在看完NIO和SSLEngine集成的例子后,我们了解到并没有提供一个SSLServerSocketChannel,在SelectionKey事件发生后,通过SSLEngine的wrap和unwrap编程实现握手协议。
Tomcat中也是这种做法,我们结合BIO的思路,来看看NIO是怎么做的。

1.SSL环境准备
对于BIO方式的SSL环境的准备,以SSLServerSocket准备好为信号,通过JSSESocketFactory中设置SSLContext,并读取Tomcat的SSL相关配置,最后初始化SSLContext。
NIO的思路也是差不多,也是在NioEndpoint类中:
k.NIO方式SSL通道流程
同样是读取Connector配置的SSLEnabled属性,作为SSL逻辑在NIO通道中的切入点。
对于JSSE实现来讲,SSLUtil实际上就是JSSESocketFactory,只不过NIO通道使用这个接口调用JSSESocketFactory:
k.NIO方式SSL通道流程
在bind方法中,和BIO的逻辑差不多,先读取Keystore的流,然后基于这个对象生成KeyManagerFactory,由KeyManagerFactory 产生KeyManager;对于TrustManager的生成的流程和KeyManager基本类似,最后将两个参数传入SSLContext.init方法中,这样,SSLContext就实例化出来了。
然后,对于Tomcat中配置的选择的SSL协议的版本,和支持的密钥套件进行,和JSSE实现默认支持的,进行取交集:
k.NIO方式SSL通道流程
和BIO也是类似的,这两个属性暂时缓存下来,在Acceptor通道的请求访问的时候,当创建SSLEngine,设置到SSLEngine中。(在BIO是设置到SSLServerSocket中)。

2.SSL请求访问
对于一个请求过来后,SSL的NIO的线程池前面的接收请求还是普通的socket请求没有什么太大的区别,依然是先走Acceptor线程,Acceptor线程建立的SocketChannel,然后包装成PollerEvent,加入到队列中,由Poller线程轮询遍历队列取出PollerEvent,进行selector.select操作,当SelectionKey事件触发后,直接将任务转给工作线程池。

SSL的过程在这个过程中,在Acceptor线程接到数据包后,调用setSocketOptions的属性进行SocketChannel通道的包装:
k.NIO方式SSL通道流程
在包装SocketChannel时,如果是SSL的话,首先,通过SSLContext进行创建SSLEngine,在这个过程中,会将前面缓存下来的Tomcat配置,如已经过滤的协议支持和密码套件支持设置到SSLEngine中,然后创建SecureNioChannel通道,这个SecureNioChannel通道是Acceptor线程专门为SSL交互包装出来的,普通的socket包装的通道是NioChannel,SecureNioChannel继承与NioChannel。

SecureNioChannel的类主要作用就是前面一节中NIO+SSLEngine的思路,主要实现了handshake握手和SSL通道的数据的发送,但是需要值得注意的是,SecureNioChannel类的handshake握手是放在工作线程中的,而Poller线程中读取SelectionKey是在另一个线程中,这相当于有如下的交互方式:
k.NIO方式SSL通道流程
Acceptor线程主要负责数据包读取,当发现新数据来了之后,包装出来一个SocketChannel通道出来,然后注册OP_READ和OP_WRITE事件,这样就相当于上图中的读写通道已经建立完成了。
Poller线程中维护一个Selector,因为读写通道已经建立完毕,所以Poller线程只管监听读写通道发送数据包,如果是普通的socket,Poller线程接收完之后直接会将请求转给工作线程池中的SocketProcessor,后续继续走对应的流程了。

但是对于SSL通道来讲,SecureNioChannel通道中含有SSLEngine,在传输数据之前,需要进行几次的握手,也就是需要像上图一样来回的发送数据,通过wrap进行入栈,通过unwrap进行出栈,一直监测SSLEngineResult.HandShakeStatus的状态,一直到状态是握手成功后,说明由SecureNioChannel已经建立完毕了安全的SSL通道,之后发送的数据同样是使用SecureNioChannel的read和write进行发送,而加密和解密都有SSLEngine的JDK实现来完成。

我们来看看SecureNioChannel通道的切入的位置,在SocketProcessor的工作任务中:
k.NIO方式SSL通道流程
我们看到,工作线程中,是通过handshake的状态进行判断,当handshake=0的时候,握手结束,后续才有handler继续进行处理,当不是0的时候,如果没有遇到异常,需要执行k.NIO方式SSL通道流程,这一步很关键,也是实现了上面的框图中的Poller线程和工作线程往复交互的行为,将socket和握手状态加入到Poller中,再次关注事件,进行轮询。

最后,再来看看SecureNioChannel类中的handshake实现的内容,基本和前面一节的例子类似:
k.NIO方式SSL通道流程