对于BIO通道的程序来讲,建立起SSLServerSocket之后,后续的工作就和普通的ServerSocket没有什么区别了,这是因为JDK中通过JSSE的API,封装了SSL通道的实现逻辑,否则,类似于C程序员如果想要编写一个https的加密程序,那他基本得累个半死,所以,我们应该感谢JAVA。

对于NIO通道来讲,我们一贯的思维也是存在一个SSLServerSocketChannel,然后注册到selector中,后续的操作也和普通的ServerSocketChannel没什么区别了,但是,并不存在一个SSLServerSocketChannel,关于这一点,JSSE的官方的Reference Guide中:

翻译出来的原因主要有一下两点:
1.因为JDK类继承限制,无法搞出一个SSLServerSocketChannel,如果需要搞出这样的一个类,需要在源码和API中大动干戈。
2.其次,JSSE的框架设计不想因为封装出一个SSLServerSocketChannel,就将NIO的灵活多变,并且高性能的优势给抹掉。
因此,基于上述的原因,最终JSSE在NIO通道中并没有SSLServerSocketChannel,而是采用NIO + SSLEngine的方式进行实现。

NIO的selector体系这里就不再说了,也即是ServerSocketChannel注册到Selector中,然后进行轮询,如果事件发生了,SelectionKey就被遍历出来,然后判断监听的是register事件,还是read/write事件,事件触发后,将socket的数据读取到缓冲区中。这是NIO的思路,而如果在NIO中使用SSL,也是延续这样的一个套路,不用做任何的改变.
当read/write事件发生之后,普通的socket直接就开始读取数据了,这个时候SSLEngine就开始工作了,首先SSLEngine会调用beginHandShake开始准备握手,然后执行握手,握手的工作是需要通过编程,调用SSLEngine的出栈和入栈来完成的,看下面的一个图:

你可以认为SSLEngine相当于一个黑盒子,它的内部封装了SSL握手,会话等各种协议实现。而我们看到上面的四个蓝色的buffer,左面的两个是应用程序中定义的buffer,供应用程序使用的,右面的两个是需要从socket缓冲区或者socketchannel接收数据并存放的buffer,用于缓存使用的;SSLEngine的一共两个操作,一个是wrap,叫做出栈,发出的出,从应用的buffer中,到网络缓冲区的buffer中,SSLEngine起到的是转接的作用,相反的是unwrap,数据从网络缓冲区流入到应用程序中。
我们为什么要了解SSLEngine这样的一个架构呢?
因为NIO方式的SSL交互,必须要通过SSLEngine来实现,甚至是握手也需要通过这种方法来实现,我们需要编程调用wrap和unwrap,并根据出栈和入栈是否成功,和下一步的需要做的动作,再执行什么动作。
判断的依据是两个状态:
第一个是SSLEngineResult.HandShakeStatus:

这个状态指示,当每一次SSLEngine的wrap或者unwrap之后下一步应该干什么。
例如NEED_WRAP状态,相当于下一步我们需要执行wrap;如果状态是NEED_UNWRAP状态,那么下一步应该执行unwrap;NOT_HANDSHAKING说明握手没有成功,应该抛出异常;如果是是NEED_TASK状态,SSL协议中定义了一些耗时比较长的操作,需要执行delegate操作,这里应该开启一个线程,将这个耗时比较长的操作给执行了,例如CRL的远程校验。
这里可以看到,我们编程的时候,应该会有一个while循环,因为SSL的握手协议不是一下子就完成的,需要客户端和服务器端反复的进行交互,这里面会不断的通过SSLEngine进行wrap和unwrap等其它的操作;
其次,需要对上面的这几种状态进行条件判断,可以使用case语句。

还有一个状态标识是SSLEngineResult.Status

这个属性是指示wrap或者unwrap方法是否执行成功和失败的,可以看到上面,如果buffer字节数没有足够的空间,就会报overflow,因此,对于这个状态的判断,通常出现在异常处理和边界检验中。

我们来看一个例子:
public class SSLHandshakeServer {
    private static Logger logger = Logger.getLogger(SSLHandshakeServer.class.getName());
    private SocketChannel sc;//channel
    private SSLEngine sslEngine;//SSLEngine引擎
    private Selector selector;//NIO通道

    private ByteBuffer myNetData;
    private ByteBuffer myAppData;
    private ByteBuffer peerNetData;
    private ByteBuffer peerAppData;//四个buffer缓冲区

    private ByteBuffer dummy = ByteBuffer.allocate(0);

    private HandshakeStatus hsStatus;//SSLEngineResult.HandShakeStatus
    private Status status;//SSLEngineResult.Status

    public void run() throws Exception {
        char[] password = "123456".toCharArray();
        KeyStore keyStore = KeyStore.getInstance("JKS");
        InputStream in = this.getClass().getResourceAsStream("serverkeystore");
        keyStore.load(in, password);
        KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
        kmf.init(keyStore, password);

        SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(kmf.getKeyManagers(), null, null);
        sslEngine = sslContext.createSSLEngine();
        sslEngine.setUseClientMode(false);

        SSLSession session = sslEngine.getSession();//初始化SSLEngine

        myAppData = ByteBuffer.allocate(session.getApplicationBufferSize());
        myNetData = ByteBuffer.allocate(session.getPacketBufferSize());
        peerAppData = ByteBuffer.allocate(session.getApplicationBufferSize());
        peerNetData = ByteBuffer.allocate(session.getPacketBufferSize());
        peerNetData.clear();//定义四个缓冲区

       //NIO的流程
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        selector = Selector.open();
        ServerSocket serverSocket = serverChannel.socket();
        serverSocket.bind(new InetSocketAddress(443));
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        logger.info("Server listens on port 443... ...");
        while (true) {
            selector.select();
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();
            while (it.hasNext()) {
                SelectionKey selectionKey = it.next();
                it.remove();
                handleRequest(selectionKey);//当SelectionKey有事件进来后,进行NIO的处理
            }
        }
    }
    private void handleRequest(SelectionKey key) throws Exception {
        if (key.isAcceptable()) {
            ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
            SocketChannel channel = ssc.accept();
            channel.configureBlocking(false);
            channel.register(selector, SelectionKey.OP_READ);//当rigister事件发生后,下一步就是读了
        } else if (key.isReadable()) {
            sc = (SocketChannel) key.channel();
            logger.info("Server handshake begins... ...");
            //从这里,SSL的交互就开始了
            sslEngine.beginHandshake();//开始begin握手
            hsStatus = sslEngine.getHandshakeStatus();
            doHandshake();//开始进行正式的SSL握手
            if (hsStatus == HandshakeStatus.FINISHED) {//当握手阶段告一段落,握手完毕
                key.cancel();
                sc.close();
            }
            logger.info("Server handshake completes... ...");
        }
    }
    //这个方法就是服务器端的握手
    private void doHandshake() throws IOException {
        SSLEngineResult result;
        while (hsStatus != HandshakeStatus.FINISHED) {//一个大的while循环,
            logger.info("handshake status: " + hsStatus);
            switch (hsStatus) {//判断handshakestatus,下一步的动作是什么?
            case NEED_TASK://指定delegate任务
                Runnable runnable;
                while ((runnable = sslEngine.getDelegatedTask()) != null) {
                    runnable.run();//因为耗时比较长,所以需要另起一个线程
                }
                hsStatus = sslEngine.getHandshakeStatus();
                break;
            case NEED_UNWRAP://需要进行入站了,说明socket缓冲区中有数据包进来了
                int count = sc.read(peerNetData);//从socket中进行读取
                if (count < 0) {
                    logger.info("no data is read for unwrap.");
                    break;
                } else {
                    logger.info("data read: " + count);
                }
                peerNetData.flip();
                peerAppData.clear();
                do {
                    result = sslEngine.unwrap(peerNetData, peerAppData);//调用SSLEngine进行unwrap操作
                    logger.info("Unwrapping:\n" + result);
                    // During an handshake renegotiation we might need to
                    // perform
                    // several unwraps to consume the handshake data.
                } while (result.getStatus() == SSLEngineResult.Status.OK//判断状态
                        && result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP
                        && result.bytesProduced() == 0);
                if (peerAppData.position() == 0 && result.getStatus() == SSLEngineResult.Status.OK
                        && peerNetData.hasRemaining()) {
                    result = sslEngine.unwrap(peerNetData, peerAppData);
                    logger.info("Unwrapping:\n" + result);
                }
                hsStatus = result.getHandshakeStatus();
                status = result.getStatus();
                assert status != status.BUFFER_OVERFLOW : "buffer not overflow." + status.toString();
                // Prepare the buffer to be written again.
                peerNetData.compact();
                // And the app buffer to be read.
                peerAppData.flip();
                break;
            case NEED_WRAP://需要出栈
                myNetData.clear();
                result = sslEngine.wrap(dummy, myNetData);//意味着从应用程序中发送数据到socket缓冲区中,先wrap
                hsStatus = result.getHandshakeStatus();
                status = result.getStatus();
                while (status != Status.OK) {
                    logger.info("status: " + status);
                    switch (status) {
                    case BUFFER_OVERFLOW:
                        break;
                    case BUFFER_UNDERFLOW:
                        break;
                    }
                }
                myNetData.flip();
                sc.write(myNetData);//最后再发送socketchannel
                break;
            }
        }
    }
    public static void main(String[] args) throws Exception {
        new SSLHandshakeServer().run();
    }

}  


总结一下,NIO+SSLEngine的这种方式,可以说是高度的开放,利用SSLEngine这个工具,来完成握手这个过程,而握手的过程需要通过编程和控制状态来实现,实际上,这也是JDK的API设计的缺陷之一,没有封装的好,让我们程序员写这些莫名其妙的代码,而JDK说加不了SSLServerSocketChannel其实应该就是一个说辞而已,不过,既然如此,那我们只需按照他的要求进行编程,也能达到NIO中SSL通道的效果。
SSLEngine也可以用在BIO中,也就是在BIO你也可以这么搞,但是因为BIO中有SSLServerSocket,这个类,一个类就可以搞定了,握手的内部实现和流程控制都在这个JDK的内部来实现,作为程序员完全不用掌握那么多。
Tomcat中的代码基本上和上述的代码类似,在下一节中,我们会重点就这块分析一下Tomcat的NIO通道中的SecurityNioChannel的实现的,非常的相似。

上面的程序握手完毕就拉倒了,双方没有继续发送数据,当然你可以在双方成功握手之后,继续使用SSLEngine来发送数据,而再次发送的数据就是进行加密传输的了,这些都是SSLEngine的实现了,需要分析JDK的代码了。

最后给出上面的服务器端对应的客户端程序,可以比对一下,其实差不太多:
public class SSLHandshakeClient {
    private static Logger logger = Logger.getLogger(SSLHandshakeClient.class.getName());
    private SocketChannel sc;
    private SSLEngine sslEngine;
    private Selector selector;
    private HandshakeStatus hsStatus;
    private Status status;
    private ByteBuffer myNetData;
    private ByteBuffer myAppData;
    private ByteBuffer peerNetData;
    private ByteBuffer peerAppData;
    private ByteBuffer dummy = ByteBuffer.allocate(0);
    public void run() throws Exception {
        char[] password = "123456".toCharArray();
        KeyStore trustStore = KeyStore.getInstance("JKS");
        InputStream in = this.getClass().getResourceAsStream("clienttruststore.jks");
        trustStore.load(in, password);
        TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
        tmf.init(trustStore);
        SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(null, tmf.getTrustManagers(), null);
        sslEngine = sslContext.createSSLEngine();
        sslEngine.setUseClientMode(true);
        SSLSession session = sslEngine.getSession();
        myAppData = ByteBuffer.allocate(session.getApplicationBufferSize());
        myNetData = ByteBuffer.allocate(session.getPacketBufferSize());
        peerAppData = ByteBuffer.allocate(session.getApplicationBufferSize());
        peerNetData = ByteBuffer.allocate(session.getPacketBufferSize());
        peerNetData.clear();
        SocketChannel channel = SocketChannel.open();
        channel.configureBlocking(false);
        selector = Selector.open();
        channel.register(selector, SelectionKey.OP_CONNECT);
        channel.connect(new InetSocketAddress("localhost", 443));
        sslEngine.beginHandshake();
        hsStatus = sslEngine.getHandshakeStatus();
        while (true) {
            selector.select();
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();
            while (it.hasNext()) {
                SelectionKey selectionKey = it.next();
                it.remove();
                handleSocketEvent(selectionKey);
            }
        }
    }
    private void handleSocketEvent(SelectionKey key) throws IOException {
        if (key.isConnectable()) {
            sc = (SocketChannel) key.channel();
            if (sc.isConnectionPending()) {
                sc.finishConnect();
            }
            doHandshake();
            sc.register(selector, SelectionKey.OP_READ);
        }
        if (key.isReadable()) {
            sc = (SocketChannel) key.channel();
            doHandshake();
            if (hsStatus == HandshakeStatus.FINISHED) {
                logger.info("Client handshake completes... ...");
                key.cancel();
                sc.close();
            }
        }
    }
    private void doHandshake() throws IOException {
        SSLEngineResult result;
        int count = 0;
        while (hsStatus != HandshakeStatus.FINISHED) {
            logger.info("handshake status: " + hsStatus);
            switch (hsStatus) {
            case NEED_TASK:
                Runnable runnable;
                while ((runnable = sslEngine.getDelegatedTask()) != null) {
                    runnable.run();
                }
                hsStatus = sslEngine.getHandshakeStatus();
                break;
            case NEED_UNWRAP:
                count = sc.read(peerNetData);
                if (count < 0) {
                    logger.info("no data is read for unwrap.");
                    break;
                } else {
                    logger.info("data read: " + count);
                }
                peerNetData.flip();
                peerAppData.clear();
                do {
                    result = sslEngine.unwrap(peerNetData, peerAppData);
                    logger.info("Unwrapping:\n" + result);
                    // During an handshake renegotiation we might need to
                    // perform
                    // several unwraps to consume the handshake data.
                } while (result.getStatus() == SSLEngineResult.Status.OK
                        && result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP
                        && result.bytesProduced() == 0);
                if (peerAppData.position() == 0 && result.getStatus() == SSLEngineResult.Status.OK
                        && peerNetData.hasRemaining()) {
                    result = sslEngine.unwrap(peerNetData, peerAppData);
                    logger.info("Unwrapping:\n" + result);
                }
                hsStatus = result.getHandshakeStatus();
                status = result.getStatus();
                assert status != status.BUFFER_OVERFLOW : "buffer not overflow." + status.toString();
                // Prepare the buffer to be written again.
                peerNetData.compact();
                // And the app buffer to be read.
                peerAppData.flip();
                break;
            case NEED_WRAP:
                myNetData.clear();
                result = sslEngine.wrap(dummy, myNetData);
                hsStatus = result.getHandshakeStatus();
                status = result.getStatus();
                while (status != Status.OK) {
                    logger.info("status: " + status);
                    switch (status) {
                    case BUFFER_OVERFLOW:
                        break;
                    case BUFFER_UNDERFLOW:
                        break;
                    }
                }
                myNetData.flip();
                count = sc.write(myNetData);
                if (count <= 0) {
                    logger.info("No data is written.");
                } else {
                    logger.info("Written data: " + count);
                }
                break;
            }
        }
    }
    public static void main(String[] args) throws Exception {
        new SSLHandshakeClient().run();
    }

}  











j.一个NIO与SSLEngine结合的例子的更多相关文章

  1. 这是一个hibernate 联合主键的例子

    package com.bird.entity; import java.io.Serializable; import javax.persistence.Entity; import javax. ...

  2. 从零讲解搭建一个NIO消息服务端

    本文首发于本博客,如需转载,请申明出处. 假设 假设你已经了解并实现过了一些OIO消息服务端,并对异步消息服务端更有兴趣,那么本文或许能带你更好的入门,并了解JDK部分源码的关系流程,正如题目所说,笔 ...

  3. 【ABAP系列】SAP 一个完整的SAP的Abap例子(idoc,edi文件的相互转换)

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[ABAP系列]SAP 一个完整的SAP的Aba ...

  4. 一个javascript继承和使用的例子

    继承可以帮助我们实现代码的重用,把对象的属性写入构造函数,对象的方法写入原型后,以下例子演示继承的使用: 示例的css和js在后 父实例,得到一个间隔1s的轮播: <!DOCTYPE html& ...

  5. Delphi多线程的OnTerminate属性(附加一个关于临界区线程同步的例子)

    首先看TThread源码中关于OnTerminate的代码: public .... property OnTerminate: TNotifyEvent read FOnTerminate writ ...

  6. 一个简单的Object Hook的例子(win7 32bit)

    Object Hook简单的来说就是Hook对象,这里拿看雪上的一个例子,因为是在win7 32位上的,有些地方做了些修改. _OBJECT_HEADER: kd> dt _OBJECT_HEA ...

  7. 一个简单的Spring测试的例子

    在做测试的时候我们用到Junit Case,当我们的项目中使用了Sring的时候,我们应该怎么使用spring容器去管理我的测试用例呢?现在我们用一个简单的例子来展示这个过程. 1 首先我们新建一个普 ...

  8. 关于封装的一个小问题和TA的例子

    写个小例子吧 --  很多细节(如校验.判断等等)都略了 其实不是有意写成这样,而是很多朋友都这么写(当然里面也有点夸张的写法) 这么写其实也没什么不好,简单明了,不用动脑子,一看就很直白, 但是如果 ...

  9. 一个关于css3背景透明的例子

    大家都知道使用opacity调节透明度不仅是背景透明了而且选择区域的文字也跟着透明了, 这是我们不想要的效果,于是强大的css3便有了只让背景透明的功能 那就是background:rgba(0,0, ...

随机推荐

  1. Python开发【第三章】:Python函数介绍

    一. 函数介绍 1.函数是什么? 在学习函数之前,一直遵循面向过程编程,即根据业务逻辑从上到下实现功能,其往往用一长段代码来实现指定功能,开发过程中最常见的操作就是粘贴复制,也就是将之前实现的代码块复 ...

  2. 关于HTML中标签<a>使用js的注意事项

    以下两点都不可取: 1.<a href="#" onClick="popUp('http://www.baidu.com');return false;" ...

  3. XML中文本节点存储任意字符的方法

    XML xml是一种可扩展标签语言, 为众多浏览器支持解析, ajax更是利用xml来完成服务器和客户端之前的通信. xml基本元素为 <label>xxx</label>, ...

  4. lua OOP实现对象的链式调用

    数学中的链式法则 http://sx.zxxk.com/ArticleInfo.aspx?InfoID=164649 链式微分法则:实数运算的链式法则:对数运算的链式法则:平行公理的链式法则:向量运算 ...

  5. VMware下安装虚拟机Ubuntu14.04 Server设置桥接方式

    我本地的采用的上网方式的拨号上网,IP段是一公网下的通过路由设置的局域网,网段182.18.1.* 本地连接包含以下: 其中无线上网卡的.WMware桥接是自定义的局域网IP段:192.168.253 ...

  6. iftop命令命令详解

    iftop命令命令详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 在Linux命令中有很多内置命令,和外置命令,但是内部命令的功能毕竟是有限的,比如ifconfig,它就不能看 ...

  7. session 存储方式

    Session 的存储方式 在 php.ini 文件中,进行配置. 涉及配置参数: - session.save_handler - session.save_path 注意:这两个参数可以在 PHP ...

  8. Android -- 使用ViewPager实现画廊效果

    1,今天在微信推送文章看到实现画廊效果,感觉挺不错的,就来写写试试,先来看一下效果图: 上面的效果基本上可以用两个功能点来包含:ViewPager的切换动画.ImageView的倒影的实现 嗯,先来将 ...

  9. transient关键字

    transient关键字的英文意思是:瞬态,由此可见是瞬间的,不可固定的. 会不会与对象的状态等等有关系呢? 网上找了一下资料是跟对象的序列化有关. transient的作用 一个对象只要实现了Ser ...

  10. Android动画Drawable Animation

    Drawable Animation是逐帧动画,那么使用它之前必须先定义好各个帧.我们可以通过代码定义,也可以使用xml文件定义,一般使用后者.如下: <?xml version="1 ...