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])); }