流信道有两个变体:SocketChannel 和ServerSocketChannel。像其对应的Socket 一样,SocketChannel 是相互连接的终端进行通信的信道。
SocketChannel: 创建,连接和关闭
static SocketChannel open(SocketAddress remote)
static SocketChannel open()
boolean connect(SocketAddress remote)
boolean isConnected()
void close()
boolean isOpen()
Socket socket()
调用SocketChannel 的静态工厂方法open()可以创建一个实例。open()方法的第一种形式以SocketAddress(见第2 章)为参数,返回一个连接到指定服务器的SocketChannel 实例。注意,该方法可能会无限期地阻塞下去。open()的无参数形式用于创建一个没有连接的SocketChannel 实例,该实例可以通过调用connect()方法连接到指定终端。当使用完SocketChannel 后,需要调用close()方法将其关闭。有一点很重要,即每个SocketChannel实例都"包裹"了一个基本Java Socket,并可以通过socket()方法对该Socket 进行访问。这就可以通过基本的Socket 方法进行绑定、设置套接字选项等操作。一个SocketChannel 的创建、连接和关闭的例子,见TCPEchoClientNonblocking.java(第113-114 页)。
在创建并连接SocketChannel 后,就可以调用该信道的读写方法进行I/O 操作。
SocketChannel: 读和写
int read(ByteBuffer dst)
long read(ByteBuffer[] dsts)
long read(ByteBuffer[] dsts, int offset, int length)
int write(ByteBuffer src)
long write(ByteBuffer[] srcs)
long write(ByteBuffer[] srcs, int offset, int length)
读操作的最基本形式以一个ByteBuffer 为参数,并将读取的数据填入该缓冲区所有的剩余字节空间中。另一种形式以多个ByteBuffer 为参数(ByteBuffer 数组),并根据其在数组中的顺序,将读取的数据依次填入每个缓冲区的剩余字节空间中。这种方法称为散射式读,因为它将读入的字节分散到了多个缓冲区中。需要注意重要的一点,散射式读不一定会将所有缓冲区填满,这些缓冲区的总空间大小只是一个上限。
写操作的最基本形式以一个ByteBuffer 为参数,并试图将该缓冲区中剩余的字节写入信道。另一种形式以一个ByteBuffer 数组作为参数,并试图将所有缓冲区中的剩余字节都写入信道。这种方法称为聚集式写,因为它把多个缓冲区中的字节聚集起来,一起发送出去。读
写操作的例子见TCPEchoClientNonblocking.java和TCPServerSelector.java 。
与其对应的ServerSocket 一样,ServerSocketChannel 是用来侦听客户端连接的信道。
ServerSocketChannel: 创建,接受和关闭
static ServerSocketChannel open()
ServerSocket socket()
SocketChannel accept()
void close()
boolean isOpen()
调用静态工厂方法open()可以创建一个ServerSocketChannel 实例。每个实例都包裹了一个ServerSocket 实例,并可以通过socket()方法对其访问。正如前面的例子所表明的,必须通过访问底层的ServerSocket 实例来实现绑定指定端口,设置套接字选项等操作。在创建了信道实例并绑定端口后,就可以调用accept()方法来准备接收客户端的连接请求。连接成功则返回一个新的已连接的SocketChannel。在用完ServerSocketChannel 后,需要调用close()方法将其关闭。使用ServerSocket 的例子见TCPServerSelector.java(第116-117 页)。
如前文提到的那样,阻塞式信道除了能够(必须)与Buffer 一起使用外,对于普通套接字来说几乎没有优点。因此,可能总是需要将信道设置成非阻塞式的。
SocketChannel, Server SocketChannel: 设置阻塞行为
SelectableChannel configureBlocking(boolean block)
boolean isBlocking()
通过调用configureBlocking(false)可以将SocketChannel 或ServerSocketChannel 设置为非阻塞模式。configureBlocking()方法将返回一个SelectableChannel,它是SocketChannel 和ServerSocketChannel 父类。
考虑为SocketChannel 设置连接的情况。如果传给SocketChannel 的工厂方法open()一个远程地址,对该方法的调用则将阻塞等待,直到成功建立了连接。要避免这种情况,可以使用open()方法的无参数形式,配置信道为非阻塞模式,再调用connect()方法,指定远程终端地址。如果在没有阻塞的情况下连接已经建立,connect()方法返回true;否则需要有检查套接字是否连接成功的方法。
SocketChannel: 测试连接性
boolean finishConnect()
boolean isConnected()
boolean isConnectionPending()
对于非阻塞SocketChannel 来说,一旦已经发起连接,底层套接字可能即不是已经连接,又不是没有连接,而是连接"正在进行"。由于底层协议的工作机制(见第6 章),套接字可能会在这个状态一直保持下去。finishConnect()方法可以用来检查在非阻塞套接字上试图进行的连接的状态,还可以在阻塞套接字建立连接的过程中阻塞等待,直到连接成功建立。例如,你可能需要将信道配置成非阻塞模式,通过connect()方法发起连接,做完一些其他工作后,又将信道配置成阻塞模式,然后调用finishConnect()方法等待连接建立完成。或者可以让信道保持在非阻塞模式,并反复调用finishConnect()方法,如TCPEchoClientNonblocking.java 中所示。
isConnected()用于检查套接字是否已经建立了连接,从而避免在进行其他操作时抛出NotYetConnectedException 异常(如调在用read()或write()时)。还可以使用isConnectionPending()方法来检查是否有连接在该信道上发起。知道是否有连接发起是必要的,因为如果没有的话,finishConnect()方法将抛出NoConnectionPendingException 异常。