导读:
IO API的可伸缩性对Web应用有着极其重要的意义。Java 1.4版以前的API中,阻塞I/O令许多人失望。从J2SE 1.4版本开始,Java终于有了可伸缩的I/O API。本文分析并计算了新旧I/O API在可伸缩性方面的差异。
一、概述
IO API的可伸缩性对Web应用有着极其重要的意义。Java 1.4版以前的API中,阻塞I/O令许多人失望。从J2SE 1.4版本开始,Java终于有了可伸缩的I/O API。本文分析并计算了新旧IO API在可伸缩性方面的差异。Java向Socket写入数据时必须调用关联的OutputStream的write()方法。只有当所有的数据全部写入时,write()方法调用才会返回。倘若发送缓冲区已满且连接速度很低,这个调用可能需要一段时间才能完成。如果程序只使用单一的线程,其他连接就必须等待,即使那些连接已经做好了调用write()的准备也一样。为了解决这个问题,你必须把每一个Socket和一个线程关联起来;采用这种方法之后,当一个线程由于I/O相关的任务被阻塞时,另一个线程仍旧能够运行。
尽管线程的开销不如进程那么大,但是,考虑到底层的操作平台,线程和进程都属于消耗大量资源的程序结构。每一个线程都要占用一定数量的内存,而且除此之外,多个线程还意味着线程上下文的切换,而这种切换也需要昂贵的资源开销。因此,Java需要一个新的API来分离Socket与线程之间过于紧密的联系。在新的Java I/O API(java.nio.*)中,这个目标终于实现了。
本文分析和比较了用新、旧两种I/O API编写的简单Web服务器。由于作为Web协议的HTTP不再象原来那样只用于一些简单的目的,因此这里介绍的例子只包含关键的功能,或者说,它们既不考虑安全因素,也不严格遵从协议规范。
二、用旧API编写的HTTP服务器
首先我们来看看用旧式API编写的HTTP服务器。这个实现只使用了一个类。main()方法首先创建了一个绑定到8080端口的ServerSocket:
public static void main() throws IOException {
ServerSocket serverSocket = new ServerSocket(8080);
for (int i=0; i <integer.parseint i> new Httpd(serverSocket); <br> } <br> } <br> 接下来,main()方法创建了一系列的Httpd对象,并用共享的ServerSocket初始化它们。在Httpd的构造函数中,我们保证每一个实例都有一个有意义的名字,设置默认协议,然后通过调用其超类Thread的start()方法启动服务器。此举导致对run()方法的一次异步调用,而run()方法包含一个无限循环。 <br> 在run()方法的无限循环中,ServerSocket的阻塞性accpet()方法被调用。当客户程序连接服务器的8080端口,accept()方法将返回一个Socket对象。每一个Socket关联着一个InputStream和一个OutputStream,两者都要在后继的handleRequest()方法调用中用到。这个方法将读取客户程序的请求,经过检查和处理,然后把合适的应答发送给客户程序。如果客户程序的请求合法,通过sendFile()方法返回客户程序请求的文件;否则,客户程序将收到相应的错误信息(调用sendError())方法。 <br> while (true) { <br> ... <br> socket = serverSocket.accept(); <br> ... <br> handleRequest(); <br> ... <br> socket.close(); <br> } <br> 现在我们来分析一下这个实现。它能够出色地完成任务吗?答案基本上是肯定的。当然,请求分析过程还可以进一步优化,因为在性能方面StringTokenizer的声誉一直不佳。但这个程序至少已经关闭了TCP延迟(对于短暂的连接来说它很不合适),同时为外发的文件设置了缓冲。而且更重要的是,所有的线程操作都相互独立。新的连接请求由哪一个线程处理由本机的(因而也是速度较快的)accept()方法决定。除了ServerSocket对象之外,各个线程之间不共享可能需要同步的任何其他资源。这个方案速度较快,但令人遗憾的是,它不具有很好的可伸缩性,其原因就在于,很显然地,线程是一种有限的资源。 <br> 三、非阻塞的HTTP服务器 <br> 下面我们来看看另一个使用非阻塞的新I/O API的方案。新的方案要比原来的方案稍微复杂一点,而且它需要各个线程的协作。它包含下面四个类: <br> ·NIOHttpd <br> ·Acceptor <br> ·Connection <br> ·ConnectionSelector <br> NIOHttpd的主要任务是启动服务器。就象前面的Httpd一样,一个服务器Socket被绑定到8080端口。两者主要的区别在于,新版本的服务器使用java.nio.channels.ServerSocketChannel而不是ServerSocket。在利用bind()方法显式地把Socket绑定到端口之前,必须先打开一个管道(Channel)。然后,main()方法实例化了一个ConnectionSelector和一个Acceptor。这样,每一个ConnectionSelector都可以用一个Acceptor注册;另外,实例化Acceptor时还提供了ServerSocketChannel。 <br> public static void main() throws IOException { <br> ServerSocketChannel ssc = ServerSocketChannel.open(); <br> ssc.socket().bind(new InetSocketAddress(8080)); <br> ConnectionSelector cs = new ConnectionSelector(); <br> new Acceptor(ssc, cs); <br> } <br> 为了理解这两个线程之间的交互过程,首先我们来仔细地分析一下Acceptor。Acceptor的主要任务是接受传入的连接请求,并通过ConnectionSelector注册它们。Acceptor的构造函数调用了超类的start()方法;run()方法包含了必需的无限循环。在这个循环中,一个阻塞性的accept()方法被调用,它最终将返回一个Socket对象——这个过程几乎与Httpd的处理过程一样,但这里使用的是ServerSocketChannel的accept()方法,而不是ServerSocket的accept()方法。最后,以调用accept()方法获得的socketChannel对象为参数创建一个Connection对象,并通过ConnectionSelector的queue()方法注册它。 <br> while (true) { <br> ... <br> socketChannel = serverSocketChannel.accept(); <br> connectionSelector.queue(new Connection(socketChannel)); <br> ... <br> } <br> 总而言之:Acceptor只能在一个无限循环中接受连接请求和通过ConnectionSelector注册连接。与Acceptor一样,ConnectionSelector也是一个线程。在构造函数中,它构造了一个队列,并用Selector.open()方法打开了一个java.nio.channels.Selector。Selector是整个服务器中最重要的部分之一,它使得程序能够注册连接,能够获取已经允许读取和写入操作的连接的清单。 <br> 构造函数调用start()方法之后,run()方法里面的无限循环开始执行。在这个循环中,程序调用了Selector的select()方法。这个方法一直阻塞,直到已经注册的连接之一做好了I/O操作的准备,或Selector的wakeup()方法被调用。 <br> while (true) { <br> ... <br> int i = selector.select(); <br> registerQueuedConnections(); <br> ... <br> // 处理连接... <br> } <br> 当ConnectionSelector线程执行select()时,没有一个Acceptor线程能够用该Selector注册连接,因为对应的方法是同步方法,理解这一点是很重要的。因此这里使用了队列,必要时Acceptor线程向队列加入连接。 <br> public void queue(Connection connection) { <br> synchronized (queue) { <br> queue.add(connection); <br> } <br> selector.wakeup(); <br> } <br> 紧接着把连接放入队列的操作,Acceptor调用Selector的wakeup()方法。这个调用导致ConnectionSelector线程继续执行,从正在被阻塞的select()调用返回。由于Selector不再被阻塞,ConnectionSelector现在能够从队列注册连接。在registerQueuedConnections()方法中,其实施过程如下: <br> if (!queue.isEmpty()) { <br> synchronized (queue) { <br> while (!queue.isEmpty()) { <br> Connection connection = <br> (Connection)queue.remove(queue.size()-1); <br> connection.register(selector); <br> } <br> } <br> } <br> <br> IO API的可伸缩性对Web应用有着极其重要的意义。Java 1.4版以前的API中,阻塞I/O令许多人失望。从J2SE 1.4版本开始,Java终于有了可伸缩的I/O API。本文分析并计算了新旧I/O API在可伸缩性方面的差异。 一、概述 IO API的可伸缩性对Web应用有着极其重要的意义。Java 1.4版以前的API中,阻塞I/O令许多人失望。从J2SE 1.4版本开始,Java终于有了可伸缩的I/O API。本文分析并计算了新旧IO API在可伸缩性方面的差异。Java向Socket写入数据时必须调用关联的OutputStream的write()方法。只有当所有的数据全部写入时,write()方法调用才会返回。倘若发送缓冲区已满且连接速度很低,这个调用可能需要一段时间才能完成。如果程序只使用单一的线程,其他连接就必须等待,即使那些连接已经做好了调用write()的准备也一样。为了解决这个问题,你必须把每一个Socket和一个线程关联起来;采用这种方法之后,当一个线程由于I/O相关的任务被阻塞时,另一个线程仍旧能够运行。 尽管线程的开销不如进程那么大,但是,考虑到底层的操作平台,线程和进程都属于消耗大量资源的程序结构。每一个线程都要占用一定数量的内存,而且除此之外,多个线程还意味着线程上下文的切换,而这种切换也需要昂贵的资源开销。因此,Java需要一个新的API来分离Socket与线程之间过于紧密的联系。在新的Java I/O API(java.nio.*)中,这个目标终于实现了。 本文分析和比较了用新、旧两种I/O API编写的简单Web服务器。由于作为Web协议的HTTP不再象原来那样只用于一些简单的目的,因此这里介绍的例子只包含关键的功能,或者说,它们既不考虑安全因素,也不严格遵从协议规范。 二、用旧API编写的HTTP服务器 首先我们来看看用旧式API编写的HTTP服务器。这个实现只使用了一个类。main()方法首先创建了一个绑定到8080端口的ServerSocket: public static void main() throws IOException { ServerSocket serverSocket = new ServerSocket(8080); for (int i=0; i <integer.parseint i connection=" (Connection)queue.remove(queue.size()-1); connection.register(selector); } }" queue void o cs acceptor connectionselector inetsocketaddress ssc="ServerSocketChannel.open(); ssc.socket().bind(new" ioexception throws main static api httpd><br>本文转自 <br><a href="http://www.cn-java.com/www1/?action-viewnews-itemid-3079">http://www.cn-java.com/www1/?action-viewnews-itemid-3079</a></integer.parseint></integer.parseint>
分享到:
相关推荐
7. **性能优化**:书中还涉及了如何通过缓冲、管道、异步I/O等技术来提高Java I/O的性能。 8. **案例研究**:提供了一些实际应用示例,帮助读者理解如何在实际项目中应用Java I/O技术。 9. **错误处理和调试**:...
Java 新I/O,也称为NIO(New Input/Output),是Java平台中对传统I/O模型的一种改进。在Java 1.4版本中引入的NIO库为开发人员提供了更高效、非阻塞的数据处理方式,特别适用于高并发、低延迟的系统。NIO的核心在于...
Java 8引入了Stream API,可以将集合操作与I/O流相结合,实现更高效的文件处理。 通过学习和理解这些知识点,初学者能够熟练地使用Java I/O流进行各种数据操作,无论是简单的文件读写,还是复杂的网络数据交换,都...
Okio 是一个面向 Java 的现代 I/O API,它由 Square 公司开发并维护,作为一个开源项目,Okio 提供了一套高效的、易用的 I/O 操作工具,旨在简化传统 Java I/O 操作的复杂性,提升应用程序的性能。Okio 主要目标是为...
Java I/O系统是Java平台的重要组成部分之一,它提供了丰富的API来支持不同类型的输入输出操作。Java I/O系统主要由以下四类流组成: - **InputStream**: 用于读取原始字节流。 - **OutputStream**: 用于写入原始...
总结,MaglevIO是一个致力于提高Java I/O效率和易用性的库,它建立在NIO之上,提供了一种更高效、更简单的I/O操作方式。对于那些需要处理大量并发连接、追求高性能的Java应用来说,MaglevIO是一个值得考虑的工具。...
自Java 1.4起,Java引入了NIO(New IO)库,提供了非阻塞I/O和通道的概念,提高了I/O性能。`java.nio.file` 包提供了 `Path`、`Files` 等类,可以更方便地进行文件操作。 9. **文件监控** Java 7引入了文件系统...
在进行异步I/O编程时,使用工具如调试器、日志记录、性能分析器等可以帮助理解程序的行为和优化性能。对于初学者来说,阅读和分析开源项目源码也是很好的学习途径,例如查看上述链接的博客文章《异步I/O处理》...
6. **I/O与NIO**:传统的I/O基于字节流和字符流,而从Java 1.4开始引入的New I/O(NIO)提供了一种非阻塞的I/O模型,适用于高并发的网络应用。 7. **国际化(Internationalization, i18n)**:Java提供`java.text`...
学习和理解JDK7的NIO2.0对于提升Java应用的性能和可扩展性至关重要,尤其是在处理大量I/O操作的场景下,如文件服务器、日志系统或者大型数据处理应用。同时,NIO2.0的异步特性也为编写高并发、非阻塞的代码提供了...
Java I/O API之性能分析 IO API的可伸缩性对Web应用有着极其重要的意义。Java 1.4版以前的API中,阻塞I/O令许多人失望。从J2SE 1.4版本开始,Java终于有了可伸缩的I/O API。本文分析并计算了新旧I/O API在可伸缩性...
在Java中,I/O操作被封装在许多类和接口中,形成了丰富的API。以下将详细介绍几个重要的类和概念。 1. **File类**: File类是Java中的核心类,位于java.io包下,用于表示文件和目录。它可以用来创建、删除、重命名...
- 新型I/O(New I/O,NIO):Java 1.4引入的非阻塞I/O模型,包括FileChannel、Selector和Pipe等组件,提高了并发性能。 8. **网络通信**: - Socket编程:创建TCP/IP套接字连接,发送和接收数据。 - ...
除此之外,Java I/O还包含过滤流(Filter Stream),如BufferedInputStream和BufferedOutputStream,它们可以在原有流的基础上增加缓冲功能,提高读写性能。还有PrintStream,用于格式化输出,方便调试。 总的来说...
Java I/O 和 Okio 是Java平台中用于处理输入输出的重要组件。它们主要用于程序与外部世界的数据交换,包括本地文件和网络通信。I/O(Input/Output)操作涉及到内存与外部设备之间的数据传输。 在Java中,I/O的核心...
综上所述,Netty凭借其异步I/O、高效的缓冲区管理、丰富的协议支持和友好的API,成为Java领域中开发网络应用的首选工具。无论你是初学者还是经验丰富的开发者,Netty都能为你带来高效、稳定的网络编程体验。
标题“NIO与I/O的区别”涉及到的是Java编程中关于输入/输出(I/O)模型与新I/O(New I/O,NIO)模型的对比。这两种模型在处理数据流时有不同的特性和适用场景,理解它们的区别对于优化Java程序的性能至关重要。 I/O...
本指南将深入探讨Java中的文件读写API,包括常用的方法、性能分析以及不同方式之间的对比。以下是对相关知识点的详细说明: 1. **File类**: Java中的`java.io.File`类是文件和目录路径名的抽象表示形式。它提供了...