merlin jffs_Merlin为Java平台带来了非阻塞I / O

服务器在合理的时间内处理大量客户端请求的能力取决于服务器使用I / O流的效率。 同时满足数百个客户端的服务器必须能够同时使用I / O服务。 在JDK 1.4(又名Merlin)之前,Java平台不支持非阻塞I / O调用。 由于线程与客户端的比例几乎为一比一,因此用Java语言编写的服务器容易受到巨大的线程开销,从而导致性能问题和可伸缩性不足。

为了解决此问题,最新版本的Java平台引入了一组新的类。 Merlin的java.nio包塞满了解决线程开销的技巧,其中最重要的是新的SelectableChannelSelector类。 通道表示客户端和服务器之间的通信方式。 选择器类似于Windows消息循环,其中选择器捕获来自不同客户端的各种事件,并将它们分派给各自的事件处理程序。 在本文中,我们将向您展示这两个类如何共同发挥作用,从而为Java平台创建非阻塞I / O机制。

Merlin之前的I / O编程

我们将从一个基本的,在Merlin之前的服务器套接字程序开始。 在ServerSocket类的生存ServerSocket ,重要功能如下:

  • 接受传入的连接
  • 阅读来自客户的请求
  • 服务那些请求

让我们使用代码片段来说明每个步骤。 首先,我们创建一个新的ServerSocket

ServerSocket s = new ServerSocket();

接下来,我们要接受来电。 调用accept()应该可以解决问题,但是您需要注意一些陷阱:

Socket conn = s.accept( );

在服务器套接字接受客户端连接请求之前,对accept()的调用将一直阻塞。 建立连接后,服务器将使用LineNumberReader读取客户端请求。 由于LineNumberReader会分块读取数据,直到缓冲区已满,因此调用将在读取时阻塞。 下面的代码片段显示了LineNumberReader的实际作用(块和全部)。

InputStream in = conn.getInputStream();
InputStreamReader rdr = new InputStreamReader(in);
LineNumberReader lnr = new LineNumberReader(rdr);
Request req = new Request();
while (!req.isComplete() )
{
   String s = lnr.readLine();
   req.addLine(s);
}

InputStream.read()是读取数据的另一种方法。 不幸的是, read也块,直到可用数据的方法,一样的write方法。

图1描述了服务器的典型工作方式。 粗线表示阻止操作。

图1.运行中的典型服务器
阻塞的1 / O图

在JDK 1.4之前,自由使用线程是解决阻塞的最典型方法。 但是此解决方案产生了自己的问题-即线程开销,这会影响性能和可伸缩性。 但是,随着Merlin和java.nio软件包的到来,一切都发生了变化。

在以下各节中,我们将研究java.nio的基础,然后将我们学到的一些知识用于修改上述的服务器套接字示例。

React堆模式

NIO设计背后的主要力量是React堆设计模式。 分布式系统中的服务器应用程序必须处理向其发送服务请求的多个客户端。 但是,在调用特定服务之前,服务器应用程序必须多路分解并将每个传入请求分派到其相应的服务提供者。 Reactor模式恰好可以实现此功能。 它允许事件驱动的应用程序对服务请求进行多路分解和分派,然后将服务请求从一个或多个客户端同时传递到应用程序。

在这方面,Reactor模式与Observer模式密切相关:当单个主题发生更改时,将通知所有从属。 观察者模式与单个事件源关联,但是,React堆模式与多个事件源关联。

请参阅相关主题 ,以了解更多关于React堆模式。

频道和选择器

NIO的非阻塞I / O机制围绕选择器和通道构建。 Channel类表示服务器与客户端之间的通信机制。 与Reactor模式保持一致, Selector类是Channel的多路复用Selector 。 它对传入的客户端请求进行多路分解,并将其分派到各自的请求处理程序。

我们将仔细研究Channel类和Selector类的各自功能,以及两者如何共同创建一个非阻塞I / O实现。

渠道做什么

通道表示与诸如硬件设备,文件,网络套接字或程序组件之类的实体的开放连接,该实体能够执行一个或多个不同的I / O操作(例如,读取或写入)。 NIO通道可以异步关闭和中断。 因此,如果某个线程在某个通道的I / O操作中被阻止,则另一个线程可以关闭该通道。 同样,如果某个线程在通道的I / O操作中被阻塞,则另一个线程可以中断该阻塞的线程。

图2. java.nio.channels的类层次结构
java.nio包的类层次结构图

如图2所示,java.nio.channels包中有很多通道接口。 我们主要关注java.nio.channels.SocketChanneljava.nio.channels.ServerSocketChannel接口。 这些通道可以分别视为java.net.Socketjava.net.ServerSocket替代。 通道可以在阻塞或非阻塞模式下使用,尽管我们当然会着重于在非阻塞模式下使用通道。

创建一个非阻塞通道

为了实现基本的非阻塞套接字读取和写入操作,我们要处理两个新类。 这些是java.net包中的InetSocketAddress类,用于指定要连接的位置;而java.nio.channels包中的SocketChannel类,用于执行实际的读写操作。

本节中的代码片段展示了一种修改后的,无阻塞的创建基本服务器套接字程序的方法。 请注意这些代码示例与第一个示例中使用的代码示例之间的更改,首先要添加两个新类:

String host = ......;
   InetSocketAddress socketAddress = new InetSocketAddress(host, 80);
	
SocketChannel channel = SocketChannel.open();
   channel.connect(socketAddress);

为了使通道成为非阻塞通道,我们在通道上调用configureBlockingMethod(false) ,如下所示:

channel.configureBlockingMethod(false);

在阻塞模式下,线程将在读取或写入时阻塞,直到操作完全完成。 如果在读取期间数据还没有完全到达套接字,则线程将阻塞读取操作,直到所有数据可用为止。

在非阻塞模式下,线程将读取可用数据量,然后返回执行其他任务。 如果将configureBlockingMethod()传递为true,则该通道的行为将与阻塞Socket上的读写完全相同。 上面提到的一个主要区别是,这些阻塞的读写可能会被其他线程中断。

Channel不足以创建非阻塞I / O实现。 Channel类必须与Selector类一起使用才能实现非阻塞I / O。

选择器的作用

Selector类在Reactor模式方案中扮演Reactor的角色。 Selector器在多个SelectableChannels上复用事件。 每个ChannelSelector注册事件。 当事件从客户端到达时, Selector对它们进行解复用,并将事件分派到相应的Channel

创建Selector的最简单方法是使用open()方法,如下所示:

Selector selector = Selector.open();

频道遇到选择器

必须服务客户请求的每个Channel必须首先创建一个连接。 下面的代码创建一个名为ServerServerSocketChannel并将其绑定到本地端口:

ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
InetAddress ia = InetAddress.getLocalHost();
InetSocketAddress isa = new InetSocketAddress(ia, port );
serverChannel.socket().bind(isa);

每个必须处理客户请求的Channel都必须接下来向Selector注册自己。 Channel应根据将处理的事件进行注册。 例如,应注册一个接受传入连接的Channel ,如下所示:

SelectionKey acceptKey = 
    channel.register( selector,SelectionKey.OP_ACCEPT);

ChannelSelector的注册由SelectionKey对象表示。 Key在满足以下三个条件之一之前一直有效:

  • Channel已关闭。
  • Selector已关闭。
  • 通过调用自己的cancel()方法可以取消Key本身。

Selector阻止select()调用。 然后,它等待直到建立了新的连接,另一个线程将其唤醒,或者另一个线程中断了原始被阻塞的线程。

注册服务器

ServerServerSocketChannel ,它向Selector注册以接受所有传入的连接,如下所示:

SelectionKey acceptKey = serverChannel.register(sel, SelectionKey.OP_ACCEPT);

   while (acceptKey.selector().select() > 0 ){

     ......

Server注册后,我们将遍历一组密钥并根据其类型处理每个密钥。 处理密钥后,将其从就绪密钥列表中删除,如下所示:

Set readyKeys = sel.selectedKeys();
    Iterator it = readyKeys.iterator();
while (it.hasNext()) 
{

SelectionKey key = (SelectionKey)it.next();
  it.remove();
  ....
  ....
  ....
 }

如果密钥是可接受的,则接受连接,并为进一步的事件(例如读取或写入操作)注册通道。 如果密钥是可读或可写的,则服务器指示已准备好在其末端读取或写入数据:

SocketChannel socket;
if (key.isAcceptable()) {
    System.out.println("Acceptable Key");
    ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
    socket = (SocketChannel) ssc.accept();
    socket.configureBlocking(false);
    SelectionKey another = 
      socket.register(sel,SelectionKey.OP_READ|SelectionKey.OP_WRITE);
}
if (key.isReadable()) {
    System.out.println("Readable Key");
    String ret = readMessage(key);
    if (ret.length() > 0) {
      writeMessage(socket,ret);
    }
		    
}
if (key.isWritable()) {
    System.out.println("Writable Key");
    String ret = readMessage(key);
    socket = (SocketChannel)key.channel();   
    if (result.length() > 0 ) {
      writeMessage(socket,ret);
    }
    }

Abracadabra-出现非阻塞服务器套接字!

JDK 1.4中的非阻塞I / O简介的最后部分留给您:运行示例。

在这个简单的无阻塞服务器套接字示例中,服务器读取从客户端发送的文件名,显示文件内容,然后将内容写回到客户端。

这是运行示例所需的操作:

  1. 安装JDK 1.4(请参阅参考资料 )。
  2. 将两个源文件复制到您的目录中。
  3. java NonBlockingServer身份编译并运行服务器。
  4. java Client身份编译并运行客户java Client
  5. 在存在类文件的目录中输入文本或Java文件的名称。
  6. 服务器将读取该文件并将其内容发送给客户端。
  7. 客户端将打印出从服务器接收的数据。 (由于这是使用的ByteBuffer的限制,因此将仅读取1024个字节。)
  8. 通过输入要退出或关闭的命令来关闭客户端。

结论

Merlin的新I / O软件包涵盖了广泛的范围。 Merlin的新非阻塞I / O实现的主要优点是双重的:线程不再在读取或写入时阻塞,并且Selector能够处理多个连接,从而大大减少了服务器应用程序的开销。

我们已经强调了新的java.nio包的这两个主要优点。 我们希望您将在这里学到的知识应用到实时应用程序开发工作中。


翻译自: https://www.ibm.com/developerworks/java/library/j-javaio/index.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值