NIO Channel SocketChannel ServerSocketChannel

ServerSocketChannel:

  ServerSocketChannel是一个基于通道的socket监听器。它同我们所熟悉的java.net.ServerSocket执行相同的基本任务,不过它增加了通道语义,因此能够在非阻塞模式下运行。用静态的open( )工厂方法创建一个新的ServerSocketChannel对象,将会返回同一个未绑定的java.net.ServerSocket关联的通道。该对等ServerSocket可以通过在返回的ServerSocketChannel上调用socket( )方法来获取。作为ServerSocketChannel的对等体被创建的ServerSocket对象依赖通道实现。这些socket关联的SocketImpl能识别通道。通道不能被封装在随意的socket对象外面。由于ServerSocketChannel没有bind( )方法,因此有必要取出对等的socket并使用它来绑定到一个端口以开始监听连接。我们也是使用对等ServerSocket的API来根据需要设置其他的socket选项。

核心Api:

 

  同它的对等体java.net.ServerSocket一样,ServerSocketChannel也有accept( )方法。一旦您创建了一个ServerSocketChannel并用对等socket绑定了它,然后您就可以在其中一个上调用accept( )。如果您选择在ServerSocket上调用accept( )方法,那么它会同任何其他的ServerSocket表现一样的行为:总是阻塞并返回一个java.net.Socket对象。如果您选择在ServerSocketChannel上调用accept( )方法则会返回SocketChannel类型的对象,返回的对象能够在非阻塞模式下运行 默认为阻塞状态。假设系统已经有一个安全管理器(security manager),两种形式的方法调用都执行相同的安全检查。如果以非阻塞模式被调用,当没有传入连接在等待时,ServerSocketChannel.accept( )会立即返回null。正是这种检查连接而不阻塞的能力实现了可伸缩性并降低了复杂性。可选择性也因此得到实现。我们可以使用一个选择器实例来注册一个ServerSocketChannel对象以实现新连接到达时自动通知的功能。即只有Channel类型的对象才能实现非阻塞操作。

代码示例如下:

/**
* configureBlocking Test
* @throws Exception
*/
@Test
public void serverSocketChannelTest() throws Exception {
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress(8080));
ssc.configureBlocking(false);
assert (ssc.validOps()==16);//return SelectionKey.OP_ACCEPT;
SocketChannel socketChannel = ssc.accept();
assert (socketChannel==null);
//ServerSocket socket =ssc.socket();
//socket.accept();//抛出异常,IllegalBlockingModeException,ssc.configureBlocking(false)之后方可执行
Thread socketThread = new Thread(()->{
while (true){
try {
SocketChannel socketChannel1 = ssc.accept();
if (socketChannel1!=null){
assert (socketChannel1.isBlocking());
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}

});
socketThread.start();
SocketChannel socket = SocketChannel.open();
socket.configureBlocking(false);
socket.connect(new InetSocketAddress("localhost",8080));
socketThread.join();
}

SocketChannel:

  Socket和SocketChannel类封装点对点、有序的网络连接,类似于我们所熟知并喜爱的TCP/IP网络连接。SocketChannel扮演客户端发起同一个监听服务器的连接。直到连接成功,它才能收到数据并且只会从连接到的地址接收。

对应方法如下:

 

  在SocketChannel上并没有一种connect( )方法可以让您指定超时(timeout)值,当connect( )方法在非阻塞模式下被调用时SocketChannel提供并发连接:它发起对请求地址的连接并且立即返回值。如果返回值是true,说明连接立即建立了(这可能是本地环回连接);如果连接不能立即建立,connect( )方法会返回false且并发地继续连接建立过程。

  面向流的的socket建立连接状态需要一定的时间,因为两个待连接系统之间必须进行包对话以建立维护流socket所需的状态信息。跨越开放互联网连接到远程系统会特别耗时。假如某个SocketChannel上当前正由一个并发连接,isConnectPending( )方法就会返回true值。

  Socket通道是线程安全的。并发访问时无需特别措施来保护发起访问的多个线程,不过任何时候都只有一个读操作和一个写操作在进行中(保证数据不丢失而不保证维持数据分组)。请记住,sockets是面向流的而非包导向的。它们可以保证发送的字节会按照顺序到达但无法承诺维持字节分组。某个发送器可能给一个socket写入了20个字节而接收器调用read( )方法时却只收到了其中的3个字节。剩下的17个字节还是传输中。由于这个原因,让多个不配合的线程共享某个流socket的同一侧绝非一个好的设计选择。

 

  调用finishConnect( )方法来完成连接过程(非阻塞模式下必须调用,否则非阻塞模式下isConnected( )永远返回false),该方法任何时候都可以安全地进行调用。假如在一个非阻塞模式的SocketChannel对象上调用finishConnect( )方法,将可能出现下列情形之一:
   connect( )方法尚未被调用。那么将产生NoConnectionPendingException异常。
   连接建立过程正在进行,尚未完成。那么什么都不会发生,finishConnect( )方法会立即返回false值。
   在非阻塞模式下调用connect( )方法之后,SocketChannel又被切换回了阻塞模式。那么如果有必要的话,调用线程会阻塞直到连接建立完成,finishConnect( )方法接着就会返回true值。
   在初次调用connect( )或最后一次调用finishConnect( )之后,连接建立过程已经完成。那么SocketChannel对象的内部状态将被更新到已连接状态,finishConnect( )方法会返回true值,然后SocketChannel对象就可以被用来传输数据了。
   连接已经建立。那么什么都不会发生,finishConnect( )方法会返回true值。
当通道处于中间的连接等待(connection-pending)状态时,您只可以调用finishConnect( )、isConnectPending( )或isConnected( )方法。一旦连接建立过程成功完成,isConnected( )将返回true值。

示例代码如下:

 

 

    /**
     * configureBlocking Test
     * @throws Exception
     */
    @Test
    public void serverSocketChannelTest() throws Exception {
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.socket().bind(new InetSocketAddress(8080));
        ssc.configureBlocking(false);
        assert (ssc.validOps()==16);//return SelectionKey.OP_ACCEPT;
        SocketChannel socketChannel = ssc.accept();
        assert (socketChannel==null);
        //ServerSocket socket  =ssc.socket();
        //socket.accept();//抛出异常,IllegalBlockingModeException,ssc.configureBlocking(false)之后方可执行
        Thread socketThread = new Thread(()->{
            while (true){
                try {
                    SocketChannel socketChannel1 = ssc.accept();
                    if (socketChannel1!=null){
                        assert (socketChannel1.isBlocking());
                        ByteBuffer [] buffers = NIOUtils.getByteBuffers("hello","world");
                        long size = socketChannel1.write(buffers);
                        System.out.println(size);
                        //socketChannel1.close();
                        break;
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        });
        socketThread.start();
        SocketChannel socket = SocketChannel.open();
        socket.configureBlocking(false);
        socket.connect(new InetSocketAddress("localhost",8080));
        assert (socket.validOps()==13);
            /*public final int validOps() {
                return (SelectionKey.OP_READ
                    | SelectionKey.OP_WRITE
                    | SelectionKey.OP_CONNECT);
            }*/
        assert (!socket.isBlocking());

        ByteBuffer[] readableBuffers = NIOUtils.getReadableByteBuffers(3);
        assert (!socket.isConnected());
        assert (socket.isConnectionPending());
        while (true){
            //TimeUnit.SECONDS.sleep(1);
            assert (socket.finishConnect());//本地情况直接返回true
            if (socket.isConnected()){
                long size = socket.read(readableBuffers);
                if (size!=0){
                    break;
                }
            }
        }
        socketThread.join();
        System.out.println(readableBuffers[0]);
        ByteBuffer buffer = ByteBuffer.allocate(5);
        buffer.put("hello".getBytes());
        assert (buffer.equals(readableBuffers[0]));
    }

 

posted @ 2018-01-02 15:59  heapStark  阅读(314)  评论(0编辑  收藏  举报