如前文所述,Channel实例代表了一个与设备的连接,通过它可以进行输入输出操作。实际上Channel的基本思想与我们见过的普通套接字非常相似。对于TCP协议,可以使用ServerSocketChannel和SocketChannel。还有一些针对其他设备的其他类型信道(如,FileChannel),尽管我们在后文中不会再提及,这里介绍的大部分内容对于它们同样适用。信道(channel)和套接字(socket)之间的不同点之一,可能是信道通常要调用静态工厂方法来获取实例:
SocketChannel clntChan = SocketChannel.open();
ServerSocketChannel servChan =
ServerSocketChannel.open();
Channel使用的不是流,而是缓冲区来发送或读取数据。Buffer类或其任何子类的实例都可以看作是一个定长的Java基本数据类型元素序列。与流不同,缓冲区有固定的、有限的容量,并由内部(但可以被访问)状态记录了有多少数据放入或取出,就像是有限容量的队列一样。Buffer是一个抽象类,只能通过创建它的子类来获得Buffer实例,而每个子类都设计为用来容纳一种Java基本数据类型(boolean除外)。因此,这些实例分别为FloatBuffer,或IntBuffer,或ByteBuffer,等等(ByteBuffer是这些实例中最灵活的,并将在后面很多例子中用到)。在channel中使用Buffer实例通常不是使用构造函数创建的,而是通过调用allocate()方法创建指定容量的Buffer实例,
ByteBuffer buffer = ByteBuffer.allocate(CAPACITY);
或通过包装一个已有的数组来创建:
ByteBuffer buffer = ByteBuffer.wrap(byteArray);
NIO的强大功能部分来自于channel的非阻塞特性。回顾前面介绍的内容可以知道,套接字的某些操作可能会无限期地阻塞。例如,对accept()方法的调用可能会因为等待一个客户端连接而阻塞;对read()方法的调用可能会因为没有数据可读而阻塞,直到连接的另一端传来新的数据。总的来说,创建/接收连接或读写数据等I/O调用,都可能无限期地阻塞等待,直到底层的网络实现发生了什么。慢速的、有损耗的网络,或仅仅是简单的网络故障都可能导致任意时间的延迟。然而不幸的是,在调用一个方法之前无法知道其是否会阻塞。NIO的channel抽象的一个重要特征就是可以通过配置它的阻塞行为,以实现非阻塞式的信道。
clntChan.configureBlocking(false);
在非阻塞式信道上调用一个方法总是会立即返回。这种调用的返回值指示了所请求的操作完成的程度。例如,在一个非阻塞式ServerSocketChannel上调用accept()方法,如果有连接请求在等待,则返回客户端SocketChannel,否则返回null。下面我们来创建一个非阻塞式TCP回显客户端。可能阻塞的I/O操作包括建立连接,读和写。通过使用非阻塞式信道,这些操作都将立即返回。我们必须反复调用这些操作,直到所有I/O操作都成功完成。
TCPEchoClientNonblocking.java
0 import java.net.InetSocketAddress;
1 import java.net.SocketException;
2 import java.nio.ByteBuffer;
3 import java.nio.channels.SocketChannel;
4
5 public class TCPEchoClientNonblocking {
6
7 public static void main(String args[]) throws Exception
{
8
9 if ((args.length < 2) || (args.length > 3)) // Test for
correct # of args
10 throw new IllegalArgumentException("Parameter(s):
11
12 String server = args[0]; // Server name or IP address
13 // Convert input String to bytes using the default
charset
14 byte[] argument = args[1].getBytes();
15
16 int servPort = (args.length == 3) ?
Integer.parseInt(args[2]) : 7;
17
18 // Create channel and set to nonblocking
19 SocketChannel clntChan = SocketChannel.open();
20 clntChan.configureBlocking(false);
21
22 // Initiate connection to server and repeatedly poll
until complete
23 if (!clntChan.connect(new InetSocketAddress(server,
servPort))) {
24 while (!clntChan.finishConnect()) {
25 System.out.print("."); // Do something else
26 }
27 }
28 ByteBuffer writeBuf = ByteBuffer.wrap(argument);
29 ByteBuffer readBuf =
ByteBuffer.allocate(argument.length);
30 int totalBytesRcvd = 0; // Total bytes received so far
31 int bytesRcvd; // Bytes received in last read
32 while (totalBytesRcvd < argument.length) {
33 if (writeBuf.hasRemaining()) {
34 clntChan.write(writeBuf);
35 }
36 if ((bytesRcvd = clntChan.read(readBuf)) == -1) {
37 throw new SocketException("Connection closed
prematurely");
38 }
39 totalBytesRcvd += bytesRcvd;
40 System.out.print("."); // Do something else
41 }
42
43 System.out.println("Received: " + // convert to String
per default charset
44 new String(readBuf.array(), 0, totalBytesRcvd));
45 clntChan.close();
46 }
47 }
TCPEchoClientNonblocking.java
1.获取并转换参数:第9-16行
2.创建非阻塞式SocketChannel:第19-20行
3.连接服务器:第23-27行
由于该套接字是非阻塞式的,因此对connect()方法的调用可能会在连接建立之前返回,如果在返回前已经成功建立了连接,则返回true,否则返回false。对于后一种情况,任何试图发送或接收数据的操作都将抛出NotYetConnectedException异常,因此,我们通过持续调用finishConnect()方法来"轮询"连接状态,该方法在连接成功建立之前一直返回false。打印操作演示了在等待连接建立的过程中,程序还可以执行其他任务。不过,这种忙等的方法非常浪费系统资源,这里这样做只是为了演示该方法的使用。
4.创建读写缓冲区:第28-29行
我们分别使用了两种方法来创建将要用来读写数据的ByteBuffer实例。一是通过包装包含了要发送数据的byte[]数组,另一个方法是调用allocate()方法,创建具有与前面byte[]数组大小相同缓冲区的ByteBuffer实例。
5.反复循环直到发送和接收完所有字节:第32-41行
只要输出缓冲区中还留有数据,就调用write()方法。对read()方法的调用不会阻塞等待,但是当没有数据可读时该方法将返回0。这里,打印语句再次举例说明了在等待通信完成的过程中,程序可以执行其他任务。
6.打印接收到的数据:第43-44行
7.关闭信道:第45行
与套接字类似,信道在完成其任务后也需要关闭。
相关下载:
Java_TCPIP_Socket编程(doc)
http://download.csdn.net/detail/undoner/4940239
文献来源:
UNDONER(小杰博客) :http://blog.csdn.net/undoner
LSOFT.CN(琅软中国) :http://www.lsoft.cn