Java NIO学习教程(六)

原文地址: 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原则的说明:
图片16

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( ) 调用返回时,你知道这一行包含年龄等。

正如你所看到的,只有当有新数据要读取时,程序才会进行,并且对于每个步骤,你都知道该数据是什么。一旦执行线程已经超过读取代码中的某个数据片段,该线程就不会在数据中向后移动(通常不会)。此图中还说明了此原则:

图17

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循环:

图18

Summary
NIO允许你仅使用一个(或几个)线程来管理多个通道(网络连接或文件),但成本是解析数据可能比从阻塞流中读取数据时更复杂。

如果你需要同时管理数千个打开的连接,每个链接只发送少量数据,例如聊天服务器,在NIO中实现该服务器可能是一个优势。同样,如果你需要与其他计算机保持大量开放连接,例如在P2P网络中,使用单个线程来管理所有出站连接可能是一个优势。此图中说明了这一个线程,多个连接设计:

图19

如果你的连接较少,带宽很高,一次发送大量数据,那么经典的IO服务器实现可能是最合适的。此图说明了经典的IO服务器设计:

图20
【上篇】Java NIO学习教程(五)==>点击

【下篇】Java NIO学习教程(七)==>点击

发布了137 篇原创文章 · 获赞 32 · 访问量 4万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 技术黑板 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览