NIO:与 Buffer 一起使用 Channel

如前文所述,Channel实例代表了一个与设备的连接,通过它可以进行输入输出操作。实际上Channel的基本思想与我们见过的普通套接字非常相似。对于TCP协议,可以使用ServerSocketChannelSocketChannel。还有一些针对其他设备的其他类型信道(如,FileChannel),尽管我们在后文中不会再提及,这里介绍的大部分内容对于它们同样适用。信道(channel)和套接字(socket)之间的不同点之一,可能是信道通常要调用静态工厂方法来获取实例:

SocketChannel clntChan = SocketChannel.open();

ServerSocketChannel servChan =

ServerSocketChannel.open();

Channel使用的不是流,而是缓冲区来发送或读取数据。Buffer类或其任何子类的实例都可以看作是一个定长的Java基本数据类型元素序列。与流不同,缓冲区有固定的、有限的容量,并由内部(但可以被访问)状态记录了有多少数据放入或取出,就像是有限容量的队列一样。Buffer是一个抽象类,只能通过创建它的子类来获得Buffer实例,而每个子类都设计为用来容纳一种Java基本数据类型(boolean除外)。因此,这些实例分别为FloatBufferIntBuffer,或ByteBuffer,等等(ByteBuffer是这些实例中最灵活的,并将在后面很多例子中用到)。在channel中使用Buffer实例通常不是使用构造函数创建的,而是通过调用allocate()方法创建指定容量的Buffer实例,

ByteBuffer buffer = ByteBuffer.allocate(CAPACITY);

或通过包装一个已有的数组来创建:

ByteBuffer buffer = ByteBuffer.wrap(byteArray);

NIO的强大功能部分来自于channel的非阻塞特性。回顾前面介绍的内容可以知道,套接字的某些操作可能会无限期地阻塞。例如,对accept()方法的调用可能会因为等待一个客户端连接而阻塞;对read()方法的调用可能会因为没有数据可读而阻塞,直到连接的另一端传来新的数据。总的来说,创建/接收连接或读写数据等I/O调用,都可能无限期地阻塞等待,直到底层的网络实现发生了什么。慢速的、有损耗的网络,或仅仅是简单的网络故障都可能导致任意时间的延迟。然而不幸的是,在调用一个方法之前无法知道其是否会阻塞。NIOchannel抽象的一个重要特征就是可以通过配置它的阻塞行为,以实现非阻塞式的信道。

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

你可能感兴趣的:(NIO:与 Buffer 一起使用 Channel)