技术开发 频道

实现非阻塞套接字的一种简单方法

  服务器端的非阻塞套接字和客户端上的没有很大差别。稍微麻烦一点的只是建立接受输入连接的套接字。套接字必须通过从服务器套接字通道派生一个阻塞的服务器套接字绑定到阻塞模式。清单 8 列出了需要做的步骤。

  清单 8. 创建非阻塞的服务器套接字(SocketChannel)

1 ServerSocketChannel ssc = ServerSocketChannel.open();
2 ServerSocket ss = ssc.socket();
3 ss.bind(new InetSocketAddress(port));
4 SocketChannel sc = ssc.accept();
5

  与客户机套接字通道相似,服务器套接字通道也必须打开而不是使用 new 操作符或者构造函数。在打开之后,必须派生服务器套接字对象以便把套接字通道绑定到一个端口。一旦套接字被绑定,服务器套接字对象就可以丢弃了。

  通道使用 accept() 方法接收到来的连接并把它们转给套接字通道。一旦接收了到来的连接并转给套接字通道对象,通信就可以通过 read() 和 write() 方法开始进行了。

  创建替代的非阻塞服务器套接字

  实际上,并非真正的替代。因为服务器套接字通道必须使用服务器套接字对象绑定,为何不完全绕开服务器套接字通道而仅使用服务器套接字对象呢?不过这里的通信不使用 SocketChannel ,而要使用模拟层 nbChannel。

  清单 9. 建立服务器套接字的另一种方法

1 ServerSocket ss = new ServerSocket(port);
2 Socket s = ss.accept();
3 nbChannel socketChannel = new nbChannel(s);
4 ReadableByteChannel rbc = (ReadableByteChannel)socketChannel;
5 WritableByteChannel wbc = (WritableByteChannel)socketChannel;
6

  创建 SSL 连接

  创建SSL连接,我们要分别从客户端和服务器端考察。

  从客户端

  创建 SS L连接的传统方法涉及到使用套接字工厂和其他一些东西。我将不会详细讨论如何创建SSL连接,不过有一本很好的教程,“Secure your sockets with JSSE”(请参阅 参考资料),从中您可以了解到更多的信息。

  创建 SSL 套接字的默认方法非常简单,只包括几个很短的步骤:

  创建套接字工厂。

  创建连接的套接字。

  开始握手。

  派生流。

  通信。

  清单 10 说明了这些步骤:

  清单 10. 创建安全的客户机套接字

1 SSLSocketFactory sslFactory =
2   (SSLSocketFactory)SSLSocketFactory.getDefault();
3 SSLSocket ssl = (SSLSocket)sslFactory.createSocket(host, port);
4 ssl.startHandshake();
5 InputStream is = ssl.getInputStream();
6 OutputStream os = ssl.getOutputStream();
7

  默认方法不包括客户验证、用户证书和其他特定连接可能需要的东西。

  从服务器端

  建立SSL服务器连接的传统方法稍微麻烦一点,需要加上一些类型转换。因为这些超出了本文的范围,我将不再进一步介绍,而是说说支持SSL服务器连接的默认方法。

  创建默认的 SSL 服务器套接字也包括几个很短的步骤:

  创建服务器套接字工厂。

  创建并绑定服务器套接字。

  接受传入的连接。

  开始握手。

  派生流。

  通信。

  尽管看起来似乎与客户端的步骤相似,要注意这里去掉了很多安全选项,比如客户验证。

  清单 11 说明这些步骤:

  清单 11. 创建安全的服务器套接字

1 SSLServerSocketFactory sslssf =
2   (SSLServerSocketFactory)SSLServerSocketFactory.getDefault();
3 SSLServerSocket sslss = (SSLServerSocket)sslssf.createServerSocket(port);
4 SSLSocket ssls = (SSLSocket)sslss.accept();
5 ssls.startHandshake();
6 InputStream is = ssls.getInputStream();
7 OutputStream os = ssls.getOutputStream();
8

  创建安全的非阻塞连接

  要精心实现安全的非阻塞连接,也需要分别从客户端和服务器端来看。

  从客户端

  在客户端建立安全的非阻塞连接非常简单:

  创建并连接 Socket 对象。

  把 Socket 对象添加到模拟层上。

  通过模拟层通信。

  清单 12 说明了这些步骤:

  清单 12. 创建安全的客户机连接

1 /* Create the factory, then the secure socket */
2 SSLSocketFactory sslFactory =
3   (SSLSocketFactory)SSLSocketFactory.getDefault();
4 SSLSocket ssl = (SSLSocket)sslFactory.createSocket(host, port);
5 /* Start the handshake.  Should be done before deriving channels */
6 ssl.startHandshake();
7 /* Put it into the emulation layer and create separate channels */
8 nbChannel socketChannel = new nbChannel(ssl);
9 ReadableByteChannel rbc = (ReadableByteChannel)socketChannel;
10 WritableByteChannel wbc = (WritableByteChannel)socketChannel;
11

  利用前面给出的 模拟层类 就可以实现非阻塞的安全连接。因为安全套接字通道不能使用 SocketChannel 类打开,而 Java API 中又没有完成这项工作的类,所以创建了一个模拟类。模拟类可以实现非阻塞通信,无论使用安全套接字连接还是非安全套接字连接。

  列出的步骤包括默认的安全设置。对于更高级的安全性,比如用户证书和客户验证。

  从服务器端

  在服务器端建立套接字需要对默认安全稍加设置。但是一旦套接字被接收和路由,设置必须与客户端的设置完全相同,如清单 13 所示:

  清单 13. 创建安全的非阻塞服务器套接字

1 /* Create the factory, then the socket, and put it into listening mode */              
2                 
3 SSLServerSocketFactory sslssf =
4   (SSLServerSocketFactory)SSLServerSocketFactory.getDefault();
5 SSLServerSocket sslss = (SSLServerSocket)sslssf.createServerSocket(port);
6 SSLSocket ssls = (SSLSocket)sslss.accept();
7 /* Start the handshake on the new socket */
8 ssls.startHandshake();
9 /* Put it into the emulation layer and create separate channels */
10 nbChannel socketChannel = new nbChannel(ssls);
11 ReadableByteChannel rbc = (ReadableByteChannel)socketChannel;
12 WritableByteChannel wbc = (WritableByteChannel)socketChannel;
13

  同样,要记住这些步骤使用的是默认安全设置。

  集成安全的和非安全的客户机连接

  多数 Internet 客户机应用程序,无论使用 Java 语言还是其他语言编写,都需要提供安全和非安全连接。Java Secure Socket Extensions 库使得这项工作非常容易,我最近在编写一个 HTTP 客户库时就使用了这种方法。

  SSLSocket 类派生自 Socket。 您可能已经猜到我要怎么做了。所需要的只是该对象的一个 Socket 指针。如果套接字连接不使用SSL,则可以像通常那样创建套接字。如果要使用 SSL,就稍微麻烦一点,但此后的代码就很简单了。清单 14 给出了一个例子:

  清单 14. 集成安全的和非安全的客户机连接

1 Socket s;
2 ReadableByteChannel rbc;
3 WritableByteChannel wbc;
4 nbChannel socketChannel;
5 if(!useSSL) s = new Socket(host, port);
6 else
7 {
8     SSLSocketFactory sslsf = SSLSocketFactory.getDefault();
9     SSLSocket ssls = (SSLSocket)SSLSocketFactory.createSocket(host, port);
10     ssls.startHandshake();
11     s = ssls;
12 }
13 socketChannel = new nbChannel(s);
14 rbc = (ReadableByteChannel)socketChannel;
15 wbc = (WritableByteChannel)socketChannel;
16 ...
17 s.close();
18

  创建通道之后,如果套接字使用了SSL,那么就是安全通信,否则就是普通通信。如果使用了 SSL,关闭套接字将导致握手中止。

  这种设置的一种可能是使用两个单独的类。一个类负责处理通过套接字沿着与非安全套接字的连接进行的所有通信。一个单独的类应该负责创建安全的连接,包括安全连接的所有必要设置,无论是否是默认的。安全类应该直接插入通信类,只有在使用安全连接时被调用。

  最简单的集成方法

  本文提出的方法是我所知道的把 JSSE 和 NIO 集成到同一代码中以提供非阻塞安全通信的最简单方法。尽管还有其他方法,但是都需要准备实现这一过程的程序员花费大量的时间和精力。

  一种可能是使用 Java Cryptography Extensions 在 NIO 上实现自己的 SSL 层。另一种方法是修改现有的称为 EspreSSL (以前称为 jSSL)的定制 SSL 层, 把它改到 NIO 库中。我建议只有在您有很充裕的时间时才使用这两种方法。

0
相关文章