JAVA NIO Socket通道

概述

通道提供I/O服务的直接连接,用于缓冲区与文件或者Socket之间传输数据。JAVA中只定义了一个接口来完成对通道的抽象,在这个接口中只定义了关闭与是否打开两个方法。在此接口的基础上又分别抽象了可读通道、可写通道、可中断通道、字节通道等,其类结构图如下:
JAVA NIO Socket通道_第1张图片
ReadableByteChannel:可读取字节的通道
WritableByteChannel:可写入字节的通道
InterruptibleChannel:可被异步关闭和中断的通道
GatheringByteChannel:可从缓冲区序列写入字节的通道 
ScatteringByteChannel:可将字节读入缓冲区序列的通道
SelectableChannel:可通过 Selector 实现多路复用的通道

Socket通道(用于监听的通道ServerSocketChannel、连接套接字的通道SocketChannel和面向数据报套接字的通道DatagramChannel)都是在以上通道的基础上扩展的,这些通道类都可以运行非阻塞模式并且是可选择的。

通道是一个连接I/O服务并提供与该服务交互的方法。就某个socket而言,它不会再次实现与之对应的socket通道类中的socket协议API,通道类在实例化时都会创建一个对等socket对象,对等socket可以通过调用socket()方法从一个通道上获取。虽然每个socket通道都有一个关联的socket对象,却并非所有的socket都有一个关联的通道。如果用传统方式(直接实例化)创建了一个Socket对象,不会有关联的SocketChannel并且它的getChannel( )方法将总是返回null。

传统的socket总是阻塞方式进行工作,严重影响应用的扩展性,这些socket通道类有一个公共的父类SelectableChannel,通过调用父类的configureBlocking和isBlocking方法来设置是否阻塞和检测是否阻塞。


ServerSocketChannel

监听套接字通道,与ServerSocket是一个对等体,ServerSocket中的API在这里也适用,这里主要看看它的accept方法,accept方法主要做了几下几件事:
1、确保此通道是打开的且已被绑定,否则会抛出异常;
2、调用本地方法建立一个socket连接,socket有对应的文件描述符和InetSocketAddress
3、进行安全检查
4、设置为阻塞模式,构建一个SocketChannel,然后返回
因此,不管此通道的阻塞模式如何,此方法返回的套接字通道(如果有)将处于阻塞模式。 
如果此通道处于非阻塞模式,那么在不存在挂起的连接时,此方法将直接返回 null,这样可以提高程序的可伸缩性并降低复杂度,检查是否连接的代码如下:

ServerSocketChannel ssc = ServerSocketChannel.open( );
ssc.socket().bind (new InetSocketAddress (port)); 
//非阻塞
ssc.configureBlocking (false); 

while (true) { 
	SocketChannel sc = ssc.accept( ); 
		
	// 在非阻塞模式下,当没有连接在等待时会返回null
	if (sc == null) { 
		Thread.sleep(TIME);
	else { 
		doSomeThing();
		sc.close();
	}
}


SocketChannel

面向连接的套接字通道,其对等体是socket。通过在通道上直接调用connect( )方法或在通道关联的Socket对象上调用connect( )来将该socket通道连接:
(1)如果选择使用传统方式进行连接,线程在连接建立好或超时过期之前都将保持阻塞;
(2)如果选择在通道上直接调用connect( )方法来建立连接并且通道处于阻塞模式(默认模式),那么连接过程和(1)是一样的;
(3)如果在非阻塞模式下调用通道的connect()方法进行连接会立即返回,如果返回值是true,说明连接立即建立了,如果连接不能立即建立,connect( )方法会返回false且并发地继续连接建立过程。
后续可以通过调用finishConnect( )方法来完成连接过程,该方法任何时候都可以安全地进行调用。

假如在一个非阻塞模式的SocketChannel对象上调用finishConnect( )方法,将可能出现下列情形之一:
1、connect( )方法尚未被调用,那么将产生NoConnectionPendingException异常;
2、连接建立过程正在进行尚未完成,那么什么都不会发生,finishConnect( )方法会立即返回false值;
3、在非阻塞模式下调用connect( )方法之后,SocketChannel又被切换回了阻塞模式,调用线程会阻塞直到连接建立完成,finishConnect( )方法接着就会返回true; 
4、调用后刚好连接过程已经完成,那么SocketChannel对象的内部状态将被更新到已连接状态,finishConnect( )方法会返回true;
5、连接已经建立,再次调用什么都不会发生,finishConnect( )方法会返回true。

这里提下状态更新,在非阻塞下调用connect方法时,状态为ST_PENDING,在此状态下isConnected()方法返回false,由于在进行读写操作前要确保通道是连接的,所以这个状态下的通道还不能进行读写操作,状态由ST_PENDING变为ST_CONNECTED正是在finishConnect方法中完成的。

Socket通道是线程安全的。并发访问时无需采用特别措施来保护发起访问的多个线程。connect( )和finishConnect( )方法是互相同步的(都会锁住readLock和writeLock),并且只要其中一个操作正在进行,任何读或写的方法调用都会被阻塞,即使是在非阻塞模式下。如果不能忍受一个读或写操作在某个通道上阻塞,最好先调用isConnected( )方法检查连接状态。


DatagramChannel

面向数据报套接字通道,与面向流的的socket不同,DatagramChannel既可以发送单独的数据报给不同的目的地址,也可以接收来自任意地址的数据包。DatagramChannel中有两组读写方法read/write和send/receive,前者只能在建立连接后才能使用。
数据报socket的无状态性质不需要同远程系统进行对话来建立连接状态,由于此原因,DatagramChannel上也就没有单独的finishConnect( )方法。我们可以使用isConnected( )方法来测试一个数据报通道的连接状态。不同于SocketChannel(必须连接了才有用并且只能连接一次),DatagramChannel对象可以任意次数地进行连接或断开连接。每次连接都可以到一个不同的远程地址。调用disconnect( )方法可以配置通道,以便它能再次接收来自安全管理器(如果已安装)所允许的任意远程地址的数据或发送数据到这些地址上。

当一个DatagramChannel处于已连接状态时,发送数据将不用提供目的地址而且接收时的源地址也是已知的。这意味着DatagramChannel已连接时可以使用常规的read( )和write( )方法。

以下是send()方法的部分源码,该方法会验证安全管理器的 checkConnect 方法是否允许使用该数据报的目标地址和端口号,避免此项安全检查开销的方法是先通过connect 方法连接该套接字。 
 synchronized (stateLock) {
      if (!isConnected()) {
          if (target == null)
              throw new NullPointerException();
          SecurityManager sm = System.getSecurityManager();
          if (sm != null) {
              if (ia.isMulticastAddress()) {
                  sm.checkMulticast(isa.getAddress());
              } else {
                  sm.checkConnect(isa.getAddress().getHostAddress(),
                                  isa.getPort());
              }
          }
      } else { // Connected case; Check address then write
          if (!target.equals(remoteAddress)) {
              throw new IllegalArgumentException(
                  "Connected address not equal to target address");
          }
          return write(src);
      }
  }
还可以看出对于已连接的send方法就是调用的write方法来实现的。

你可能感兴趣的:(JAVA NIO Socket通道)