原文地址: link.
12.Java NIO DatagramChannel
Java NIO DatagramChannel是可以发送和接收UDP数据包的通道。由于UDP是一种无连接的网络协议,因此你不能像从其他通道读写数据报通道一样,在默认情况下读写数据报通道。相反,您发送和接收数据包。
Opening a DatagramChannel
以下是打开DatagramChannel方法:
DatagramChannel channel = DatagramChannel.open();
channel.socket().bind(new InetSocketAddress(9999));
此示例打开一个DatagramChannel,它可以在UDP端口9999上接收数据包。
Receiving Data
通过调用DatagramChannel.receive( )方法接收数据,如下所示:
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
channel.receive(buf);
receive( )方法将接收的数据包的内容复制到给定的Buffer中。如果接收的数据包包含的数据多于缓冲区可以容纳的数据,则会以静默方式丢弃剩余的数据。
Sending Data
通过调用DatagramChannel.send( )方法发送数据,如下所示:
String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
int bytesSent = channel.send(buf, new InetSocketAddress("jenkov.com", 80));
此示例将字符串发送到UDP端口80上的“jenkov.com”服务器。不过,没有人在监听那个端口,所以什么也不会发生。由于UDP不对数据传送做出任何保证,因此不会通知你是否收到了发送数据包。
Connecting to a Specific Address
可以将DatagramChannel连接到网络上的特定地址。由于UDP是无连接的,因此这种连接到地址的方式不会像TCP通道那样创建真正的连接。相反,它会锁定你的DatagramChannel,因此你只能从一个特定地址发送和接收数据包。
这是一个例子:
channel.connect(new InetSocketAddress("jenkov.com", 80));
连接后,还可以使用read( )和write( )方法,就像使用传统频道一样。你只是对发送的数据没有任何保证。这里有一些例子:
int bytesRead = channel.read(buf);
int bytesWritten = channel.write(buf);
13.Java NIO Pipe
Java NIO Pipe是两个线程之间的单向数据连接。管道具有源通道和接收器通道。将数据写入接收器通道。然后可以从源通道读取该数据。。
以下是Pipe原则的说明:
Creating a Pipe
你Pipe通过调用Pipe.open( )方法打开一个。看起来就像这样:
Pipe pipe = Pipe.open();
Writing to a Pipe
要写入Pipe,需要访问接收器通道。看起来就像这样:
Pipe.SinkChannel sinkChannel = pipe.sink();
通过调用SinkChannel.write( )方法写入数据,如下所示:
String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) {
sinkChannel.write(buf);
}
Reading from a Pipe
要从管道读取,您需要访问源通道。这是如何做到的:
Pipe.SourceChannel sourceChannel = pipe.source();
要从源通道读取,请调用其read( )方法,如下所示:
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = sourceChannel.read(buf);
read( )方法返回的int值表示读入缓冲区的字节数。
14.Java NIO & IO
在研究Java NIO和IO API时,很快就会出现一个问题:
我什么时候应该使用IO,什么时候应该使用NIO?
在本文中,我将尝试阐明Java NIO和IO之间的差异,它们的用例以及它们如何影响代码的设计。
Main Differences Betwen Java NIO and IO
下表总结了Java NIO和IO之间的主要区别。我将在表格后面的部分中详细介绍每个区别。
IO | NIO |
---|---|
Stream oriented | Buffer oriented |
Blocking IO | Non blocking IO |
null | Selectors |
Stream Oriented vs. Buffer Oriented
Java NIO和IO之间的第一个重要区别是IO是面向流的,其中NIO是面向缓冲区的。那么,这意味着什么?
以流为导向的Java IO意味着每次从一个流读取一个或多个字节。读字节的处理取决于你自己。它们不在任何地方缓存。此外,不能在流中来回移动数据。如果需要前后移动从流中读取的数据,则需要首先将其缓存在缓冲区中。
Java NIO的面向缓冲区的方法略有不同。数据被读取到一个缓冲区中,然后从该缓冲区进行处理。你可以根据需要在缓冲区中前后移动。这使你在处理过程中更加灵活。但是,你还需要检查缓冲区是否包含你完全处理它所需的所有数据。而且,你需要确保在向缓冲区中读取更多数据时,不会覆盖尚未处理的缓冲区中的数据。
Blocking vs. Non-blocking IO
Java IO的各种流都是阻塞的。这意味着,当一个线程调用一个read( )或write( )时,该线程被阻塞,直到有一些数据需要读取,或者数据被完全写入为止。同时,线程无法执行任何其他操作。
Java NIO的非阻塞模式允许线程请求从通道读取数据,并且只获得当前可用的内容,或者根本没有数据,如果当前没有数据可用。线程可以继续执行其他操作,而不是在数据可供读取之前保持阻塞状态。
对于非阻塞写入也是如此。线程可以请求将某些数据写入通道,但不要等待它完全写入。然后线程可以继续进行,并在同一时间做其他事情。
当在IO调用中没有阻塞时,线程所花费的空闲时间通常可以在其他通道上执行IO操作。也就是说,单个线程现在可以管理多个输入和输出通道。
Selectors
Java NIO的选择器允许单个线程监视多个输入通道。你可以使用选择器注册多个通道,然后使用单个线程“选择”具有可用于处理的输入的通道,或者选择准备好写入的通道。这种选择器机制使得单个线程可以轻松管理多个通道。
How NIO and IO Influences Application Design
无论你选择NIO还是IO,因为你的IO工具包可能会影响应用程序设计的以下方面:
- API调用NIO或IO类。
- 处理数据。
- 用于处理数据的线程数。
The API Calls
当然,使用NIO时的API调用看起来与使用IO时不同。这并不奇怪。不是从输入流中逐字节读取数据,而是必须先将数据读取到缓冲区中,然后再从缓冲区中进行处理。
The Processing of Data
使用纯NIO设计与IO设计时,数据处理也会受到影响。
在IO设计中,你从InputStream或Reader读取字节的数据字节。想象一下,你正在处理基于行的文本数据流。例如:
Name: Anna
Age: 25
Email: anna@mailserver.com
Phone: 1234567890
这个文本行流可以像这样处理:
InputStream input = ... ; // get the InputStream from the client socket
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
String nameLine = reader.readLine();
String ageLine = reader.readLine();
String emailLine = reader.readLine();
String phoneLine = reader.readLine();
注意处理状态是如何由程序执行的程度决定的。换句话说,一旦第一个reader.readLine( )方法返回,你就确定已经读取了整行文本。readLine( )会一直阻塞直到读取到整行,这就是原因。你还知道此行包含姓名。同样,当第二个readLine( ) 调用返回时,你知道这一行包含年龄等。
正如你所看到的,只有当有新数据要读取时,程序才会进行,并且对于每个步骤,你都知道该数据是什么。一旦执行线程已经超过读取代码中的某个数据片段,该线程就不会在数据中向后移动(通常不会)。此图中还说明了此原则:
NIO的实现看起来会有所不同。这是一个简化的例子:
ByteBuffer buffer = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buffer);
注意第二行,它将字节从通道读取到ByteBuffer中。当该方法调用返回时,你不知道所需的所有数据是否都在缓冲区内。你只知道缓冲区包含一些字节。这使得处理更加困难。
想象一下,在第一次read(buffer)调用之后,是否所有读入缓冲区的内容都是半行。例如,“Name:An”。你能处理这些数据吗?并不是的。在处理任何数据之前,你需要等待至少一整行数据进入缓冲区。
那么,你怎么知道缓冲区是否包含足够的数据来进行处理呢?好吧,你没有。唯一能找到的方法就是查看缓冲区中的数据。结果是,在知道所有数据是否都在缓存中之前,你可能需要多次检查缓冲区中的数据。这两者都是低效的,并且在程序设计方面会变得混乱。例如:
ByteBuffer buffer = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buffer);
while(! bufferFull(bytesRead) ) {
bytesRead = inChannel.read(buffer);
}
bufferFull( )方法必须跟踪读入缓冲区的数据量,并根据缓冲区是否满,返回true或false。换句话说,如果缓冲区已准备好进行处理,则认为它已满。
bufferFull( )方法扫描缓冲区,但必须使缓冲区保持与调用bufferfull( )方法之前相同的状态。如果不是,则可能无法在正确的位置读取下一个进入缓冲区的数据。这不是不可能的,但这是另一个需要注意的问题。
如果缓冲区已满,则可以对其进行处理。如果它不满,你可以部分处理那里的任何数据,如果这在你的特定情况下是有意义的。在许多情况下,它没有。
这个图中说明了is-data-in-buffer-ready循环:
Summary
NIO允许你仅使用一个(或几个)线程来管理多个通道(网络连接或文件),但成本是解析数据可能比从阻塞流中读取数据时更复杂。
如果你需要同时管理数千个打开的连接,每个链接只发送少量数据,例如聊天服务器,在NIO中实现该服务器可能是一个优势。同样,如果你需要与其他计算机保持大量开放连接,例如在P2P网络中,使用单个线程来管理所有出站连接可能是一个优势。此图中说明了这一个线程,多个连接设计:
如果你的连接较少,带宽很高,一次发送大量数据,那么经典的IO服务器实现可能是最合适的。此图说明了经典的IO服务器设计:
【上篇】Java NIO学习教程(五)==>点击
【下篇】Java NIO学习教程(七)==>点击