- 浏览: 1530538 次
- 性别:
- 来自: 厦门
文章分类
- 全部博客 (516)
- Java (49)
- Java/Struts 2.0 (25)
- Java/Spring、Spring MVC (11)
- Java/Quartz (3)
- Java/Lucene (6)
- Java/Hibernate (19)
- Java/openJPA (7)
- Java/DWR (7)
- Java/Security、Spring Security/OAuth2 (6)
- Java/Threading (9)
- Java/XML (22)
- java/design pattern (4)
- Android (2)
- JavaScript (46)
- jquery (3)
- DB/MySQL (23)
- DB/Oracle (16)
- PHP (25)
- CSS (20)
- Linux (38)
- C/C++、DLL、Makefile、VC++ (31)
- 正则 (9)
- Eclipse (4)
- 安全、网络等概念 (25)
- 集群 (7)
- 网页 (5)
- 视频\音频 (1)
- HTML (6)
- 计算机数学/算法 (3)
- Virtualbox (1)
- LDAP (2)
- 数据挖掘 (6)
- 工具破解 (1)
- 其他 (13)
- Mail (1)
- 药材 (3)
- 游戏 (2)
- hadoop (13)
- 压力测试 (3)
- 设计模式 (3)
- java/Swing (2)
- 缓存/Memcache (0)
- 缓存/Redis (1)
- OSGI (2)
- OSGI/Gemini (0)
- 文档写作 (0)
- java/Servlet (3)
- MQ/RabbitMQ (2)
- MQ/RocketMQ (0)
- MQ/Kafka (1)
- maven (0)
- SYS/linux (1)
- cache/redis (1)
- DB/Mongodb (2)
- nginx (1)
- postman (1)
- 操作系统/ubuntu (1)
- golang (1)
- dubbo (1)
- 技术管理岗位 (0)
- mybatis-plus (0)
最新评论
-
pgx89112:
大神,请赐我一份这个示例的项目代码吧,万分感谢,1530259 ...
spring的rabbitmq配置 -
string2020:
不使用增强器 怎么弄?
OpenJPA的增强器 -
孟江波:
学习了,楼主,能否提供一份源代码啊,学习一下,十分感谢!!!4 ...
spring的rabbitmq配置 -
eachgray:
...
spring-data-redis配置事务 -
qljoeli:
学习了,楼主,能否提供一份源代码啊,学习一下,十分感谢!!!1 ...
spring的rabbitmq配置
您认为把 NIO 和 Servlet API 组合在一起是不可能的?请再好好想一下。在本文中,Java 开发人员 Taylor Cowan 向您展示了如何把生产者/消费者模型应用到消费者非阻塞 I/O,从而轻松地让 Servlet API 全新地兼容 NIO。在这个过程中,您将会看到采用了什么来创建实际的基于 Servlet 并实现了 NIO 的 Web 服务器;您也将发现在企业环境中,那个服务器是如何以标准的 Java I/O 服务器(Tomcat 5.0)为基础而创建的。
NIO 是带有 JDK 1.4 的 Java 平台的最有名(如果不是最出色的)的添加部分之一。下面的许多文章阐述了 NIO 的基本知识及如何利用非阻塞通道的好处。但它们所遗漏的一件事正是,没有充分地展示 NIO 如何可以提高 J2EE Web 层的可伸缩性。对于企业开发人员来说,这些信息特别密切相关,因为实现 NIO 不像把少数几个 import 语句改变成一个新的 I/O 包那样简单。首先,Servlet API 采用阻塞 I/O 语义,因此默认情况下,它不能利用非阻塞 I/O。其次,不像 JDK 1.0 中那样,线程不再是“资源独占”(resource hog),因此使用较少的线程不一定表明服务器可以处理更多的客户机。
在本文中,为了创建基于 Servlet 并实现了 NIO 的 Web 服务器,您将学习如何解决 Servlet API 与非阻塞 I/O 的不配合问题。我们将会看到在多元的 Web 服务器环境中,这个服务器是如何针对标准 I/O 服务器(Tomcat 5.0)进行伸缩的。为符合企业中生存期的事实,我们将重点放在当保持 socket 连接的客户机数量以指数级增长时,NIO 与标准 I/O 相比较的情况如何。
注意,本文针对某些 Java 开发人员,他们已经熟悉了 Java 平台上 I/O 编程的基础知识。有关非阻塞 I/O 的介绍,请参阅 参考资料 部分。
大家都知道,线程是比较昂贵的。在 Java 平台的早期(JDK 1.0),线程的开销是一个很大负担,因此强制开发人员自定义生成解决方案。一个常见的解决方案是使用 VM 启动时创建的线程池,而不是按需创建每个新线程。尽管最近在 VM 层上提高了线程的性能,但标准 I/O 仍然要求分配惟一的线程来处理每个新打开的 socket。就短期而言,这工作得相当不错,但当线程的数量增加超过了 1K,标准 I/O 的不足就表现出来了。由于要在线程间进行上下文切换,因此 CPU 简直变成了超载。
由于 JDK 1.4 中引入了 NIO,企业开发人员最终有了“单线程”模型的一个内置解决方案:多元 I/O 使得固定数量的线程可以服务不断增长的用户数量。
多路复用(Multiplexing) 指的是通过一个载波来同时发送多个信号或流。当使用手机时,日常的多路复用例子就发生了。无线频率是稀有的资源,因此无线频率提供商使用多路复用技术通过一个频率发送多个呼叫。在一个例子中,把呼叫分成一些段,然后给这些段很短的持续时间,并在接收端重新装配。这就叫做 时分多路复用(time-division multiplexing) ,即 TDM。
在 NIO 中,接收端相当于“选择器”(参阅
java.nio.channels.Selector
)。不是处理呼叫,选择器是处理多个打开的
socket。就像在 TDM 中那样,选择器重新装配从多个客户机写入的数据段。这使得服务器可以用单个线程管理多个客户机。
Servlet API 和 NIO
对于 NIO,非阻塞读写是必要的,但它们并不是完全没有麻烦。除了不会阻塞之外,非阻塞读不能给呼叫方任何保证。客户机或服务器应用程序可能读取完整信息、部分消息或者根本读取不到消息。另外,非阻塞读可能读取到太多的消息,从而强制为下一个呼叫准备一个额外的缓冲区。最后,不像流那样,读取了零字节并不表明已经完全接收了消息。
这些因素使得没有轮询就不可能实现甚至是简单的 readline 方法。所有的 servlet 容器必须在它们的输入流上提供 readline 方法。因此,许多开发人员放弃了创建基于 Servlet 并实现了 NIO 的 Web 应用程序服务器。不过这里有一个解决方案,它组合了 Servlet API 和 NIO 的多元 I/O 的能力。
在下面的几节中,您将学习如何使用 java.io.PipedInput 和 PipedOutputStream 类来把生产者/消费者模型应用到消费者非阻塞 I/O。当读取非阻塞通道时,把它写到正由第二个线程消费的管道。注意,这种分解映射线程不同于大多数基于 Java 的客户机/服务器应用程序。这里,我们让一个线程单独负责处理非阻塞通道(生产者),让另一个线程单独负责把数据作为流消费(消费者)。管道也为应用程序服务器解决了非阻塞 I/O 问题,因为 servlet 在消费 I/O 时将采用阻塞语义。
示例服务器展示了 Servlet API 和 NIO 不兼容的生产者/消费者解决方案。该服务器与 Servlet API 非常相似,可以为成熟的基于 NIO 应用程序服务器提供 POC (proof of concept),是专门编写来衡量 NIO 相对于标准 Java I/O 的性能的。它处理简单的 HTTP get 请求,并支持来自客户机的 Keep-Alive 连接。这是重要的,因为多路复用 I/O 只证明在要求服务器处理大量打开的 scoket 连接时是有意的。
该服务器被分成两个包: org.sse.server 和 org.sse.http 包中有提供主要 服务器 功能的类,比如如下的一些功能:接收新客户机连接、阅读消息和生成工作线程以处理请求。 http 包支持 HTTP 协议的一个子集。详细阐述 HTTP 超出了本文的范围。有关实现细节,请从 参考资料 部分下载代码示例。
现在让我们来看一下 org.sse.server 包中一些最重要的类。
Server 类
Server 类拥有多路复用循环 —— 任何基于 NIO 服务器的核心。在清单 1 中,在服务器接收新客户机或检测到正把可用的字节写到打开的 socket 前, select() 的调用阻塞了。这与标准 Java I/O 的主要区别是,所有的数据都是在这个循环中读取的。通常会把从特定 socket 中读取字节的任务分配给一个新线程。使用 NIO 选择器事件驱动方法,实际上可以用单个线程处理成千上万的客户机,不过,我们还会在后面看到线程仍有一个角色要扮演。
每个 select() 调用返回一组事件,指出新客户机可用;新数据准备就绪,可以读取;或者客户机准备就绪,可以接收响应。server 的 handleKey() 方法只对新客户机( key.isAcceptable() )和传入数据 ( key.isReadable() ) 感兴趣。到这里,工作就结束了,转入 ServerEventHandler 类。
清单 1. Server.java 选择器循环
public void listen() { SelectionKey key = null; try { while (true) { selector.select(); Iterator it = selector.selectedKeys().iterator(); while (it.hasNext()) { key = (SelectionKey) it.next(); handleKey(key); it.remove(); } } } catch (IOException e) { key.cancel(); } catch (NullPointerException e) { // NullPointer at sun.nio.ch.WindowsSelectorImpl, Bug: 4729342 e.printStackTrace(); } }
ServerEventHandler 类
ServerEventHandler 类响应服务器事件。当新客户机变为可用时,它就实例化一个新的 Client 对象,该对象代表了那个客户机的状态。数据是以非阻塞方式从通道中读取的,并被写到 Client 对象中。 ServerEventHandler 对象也维护请求队列。为了处理(消费)队列中的请求,生成了不定数量的工作线程。在传统的生产者/消费者方式下,为了在队列变为空时线程会阻塞,并在新请求可用时线程会得到通知,需要写 Queue 。
为了支持等待的线程,在清单 2 中已经重写了 remove() 方法。如果列表为空,就会增加等待线程的数量,并阻塞当前线程。它实质上提供了非常简单的线程池。
清单 2. Queue.java
public class Queue extends LinkedList { private int waitingThreads = 0; public synchronized void insert(Object obj) { addLast(obj); notify(); } public synchronized Object remove() { if ( isEmpty() ) { try { waitingThreads++; wait();} catch (InterruptedException e) {Thread.interrupted();} waitingThreads--; } return removeFirst(); } public boolean isEmpty() { return (size() - waitingThreads <= 0); } }
工作线程的数量与 Web 客户机的数量无关。不是为每个打开的 socket 分配一个线程,相反,我们把所有请求放到一个由一组 RequestHandlerThread 实例所服务的通用队列中。理想情况下,线程的数量应该根据处理器的数量和请求的长度或持续时间进行调整。如果请求通过资源或处理需求花了很长时间,那么通过添加更多的线程,可以提高感知到的服务质量。
注意,这不一定提高整体的吞吐量,但确实改善了用户体验。即使在超载的情况下,也会给每个线程一个处理时间片。这一原则同样适用于基于标准 Java I/O 的服务器;不过这些服务器是受到限制的,因为会 要求 它们为每个打开的 socket 连接分配一个线程。NIO 服务器完全不用担心这一点,因此它们可以扩展到大量用户。最后的结果是 NIO 服务器仍然需要线程,只是不需要那么多。
请求处理
Client 类有两个用途。首先,通过把传入的非阻塞 I/O 转换成可由 Servlet API 消费的阻塞 InputStream ,它解决了阻塞/非阻塞问题。其次,它管理特定客户机的请求状态。因为当全部读取消息时,非阻塞通道没有给出任何提示,所以强制我们在协议层处理这一情况。 Client 类在任意指定的时刻都指出了它是否正在参与进行中的请求。如果它准备处理新请求, write() 方法就会为请求处理而将该客户机排到队列中。如果它已经参与了请求,它就只是使用 PipedInputStream 和 PipedOutputStream 类把传入的字节转换成一个 InputStream 。
图 1 展示了两个线程围绕管道进行交互。主线程把从通道读取的数据写到管道中。管道把相同的数据作为 InputStream 提供给消费者。管道的另一个重要特性是:它是进行缓冲处理的。如果没有进行缓冲处理,主线程在尝试写到管道时就会阻塞。因为主线程单独负责所有客户机间的多路复用,因此我们不能让它阻塞。
图 1. PipedInput/OutputStream
在 Client 自己排队后,工作线程就可以消费它了。 RequestHandlerThread 类承担了这个角色。至此,我们已经看到主线程是如何连续地循环的,它要么接受新客户机,要么读取新的 I/O。工作线程循环等待新请求。当客户机在请求队列上变为可用时,它就马上被 remove() 方法中阻塞的第一个等待线程所消费。
清单 3. RequestHandlerThread.java
public void run() { while (true) { Client client = (Client) myQueue.remove(); try { for (; ; ) { HttpRequest req = new HttpRequest(client.clientInputStream, myServletContext); HttpResponse res = new HttpResponse(client.key); defaultServlet.service(req, res); if (client.notifyRequestDone()) break; } } catch (Exception e) { client.key.cancel(); client.key.selector().wakeup(); } } }
然后该线程创建新的 HttpRequest 和 HttpResponse 实例,并调用 defaultServlet 的 service 方法。注意, HttpRequest 是用 Client 对象的 clientInputStream 属性构造的。 PipedInputStream 就是负责把非阻塞 I/O 转换成阻塞流。
从现在开始,请求处理就与您在 J2EE Servlet API 中期望的相似。当对 servlet 的调用返回时,工作线程在返回到池中之前,会检查是否有来自相同客户机的另一个请求可用。注意,这里用到了单词 池 (pool)。事实上,线程会对队列尝试另一个 remove() 调用,并变成阻塞,直到下一个请求可用。
运行示例
示例服务器实现了 HTTP 1.1 协议的一个子集。它处理普通的 HTTP get 请求。它带有两个命令行参数。第一个指定端口号,第二个指定 HTML 文件所驻留的目录。在解压文件后, 切换到项目目录,然后执行下面的命令,注意要把下面的 webroot 目录替换为您自己的目录:
java -cp bin org.sse.server.Start 8080 "C:\mywebroot"
还请注意,服务器并没有实现目录清单,因此必须指定有效的 URL 来指向您的 webroot 目录下的文件。
性能结果
示例 NIO 服务器是在重负载下与 Tomcat 5.0 进行比较的。选择 Tomcat 是因为它是基于标准 Java I/O 的纯 Java 解决方案。为了提高可伸缩性,一些高级的应用程序服务器是用 JNI 本机代码优化的,因此它们没有提供标准 I/O 和 NIO 之间的很好比较。目标是要确定 NIO 是否给出了大量的性能优势,以及是在什么条件下给出的。
如下是一些说明:
* Tomcat 是用最大的线程数量 2000 来配置的,而示例服务器只允许用 4 个工作线程运行。
* 每个服务器是针对相同的一组简单 HTTP get 测试的,这些 HTTP get 基本上由文本内容组成。
* 把加载工具(Microsoft Web Application Stress Tool)设置为使用“Keep-Alive”会话,导致了大约要为每个用户分配一个 socket。然后它导致了在 Tomcat 上为每个用户分配一个线程,而 NIO 服务器用固定数量的线程来处理相同的负载。
图 2 展示了在不断增加负载下的“请求/秒”率。在 200 个用户时,性能是相似的。但当用户数量超过 600 时,Tomcat 的性能开始急剧下降。这最有可能是由于在这么多的线程间切换上下文的开销而导致的。相反,基于 NIO 的服务器的性能则以线性方式下降。记住,Tomcat 必须为每个用户分配一个线程,而 NIO 服务器只配置有 4 个工作线程。
图 3 进一步显示了 NIO 的性能。它展示了操作的 Socket 连接错误数/分钟。同样,在大约 600 个用户时,Tomcat 的性能急剧下降,而基于 NIO 的服务器的错误率保持相对较低。
结束语
在本文中您已经学习了,实际上可以使用 NIO 编写基于 Servlet 的 Web 服务器,甚至可以启用它的非阻塞特性。对于企业开发人员来说,这是好消息,因为在企业环境中,NIO 比标准 Java I/O 更能够进行伸缩。不像标准的 Java I/O,NIO 可以用固定数量的线程处理许多客户机。当基于 Servlet 的 NIO Web 服务器用来处理保持和拥有 socket 连接的客户机时,会获得更好的性能。
原文档:
http://www.ibm.com/developerworks/cn/java/j-nioserver/
发表评论
-
HttpRequestClient
2018-11-21 17:45 630http请求工具 public class WebCon ... -
Spring boot 环境下配置CKEditor添加google map
2017-07-05 14:39 1263本文档基于已经搭建好基础ckeditor的环境上。如:ht ... -
post json
2015-12-30 12:35 549方法一: public static String doP ... -
maven打包成可执行的jar包(package a runnable jar)
2015-04-23 22:01 2225第一步:利用maven-jar-plugin生成当前工程的 ... -
自定义ObjectMapper
2015-03-02 16:57 7776d import java.io.IOExcept ... -
JsonObjectMapper
2015-02-05 10:41 1401import com.fasterxml.jackson. ... -
HttpServletRequestWrapper
2014-10-12 08:15 597请求提交后替换HTML字符 import javax.s ... -
配置javamelody
2014-09-12 14:03 1280参考后面地址修改整理。http://my.oschina.n ... -
getTrace()
2014-08-06 09:14 571/** * 将e.printStackTrace() ... -
获取java对象在内存的地址
2014-07-28 14:59 2868引用stackflow的文章:http://stackove ... -
JMeter入门:Java Request实例
2014-05-08 15:41 2358转自:http://softtest.chin ... -
解决在IE下下载时文件名乱码
2014-01-09 11:24 1063String agent = request.getHead ... -
第一节:编译自定义的Hadoop核心包,去除用户权限检查
2013-11-30 11:59 1072本文参考eclipse连接远程hadoop集群开发时0700 ... -
第二节:win 7下编译eclispe hadoop plugin
2013-11-29 19:12 839本文参照后面地址的Linux编译方式:http: ... -
使用log4j为Tomcat输出日志
2013-11-01 16:42 10352转自:http://hi.baidu.com/y ... -
xStream开发HTTP的XML内容
2013-07-03 09:58 10621、编写自定义XPPDriver代码: package ... -
HttpURLConnection or URLConnection post xml content to web server
2013-05-31 01:56 2065客户端示例1: package client; ... -
java nio学习笔记
2013-03-21 21:41 0直接缓冲区被用于与通道和固有I/O例程交互。它们 ... -
Jsch使用
2012-12-10 17:07 2556Jsch shell模式下的代码示例: 参考: ... -
Exe4j破解_Exe4j注册码
2012-12-06 17:35 33557exe4j是个很好的打包利器,官方下载地址:http://ww ...
相关推荐
- **Servlet**:Servlet API定义了服务器端程序的接口,用于处理HTTP请求,常与JSP(JavaServer Pages)一起使用。 - **JPA(Java Persistence API)**:用于对象关系映射,简化了数据库操作,如EntityManager和...
在Java开发中,jar(Java Archive)文件是一种打包格式,它将类文件、资源文件以及其他元数据打包在一起,便于分发和运行。在Tomcat源码编译过程中,需要的jar文件主要包含以下几个方面: 1. **Java标准库**:...
将这些组件整合在一起,可以搭建一个高效、稳定的Java EE开发和运行环境。开发者可以利用Eclipse JEE创建、配置和运行基于Tomcat的Web应用,同时利用Maven管理和构建项目,而JDK 1.8则提供了运行这些应用所需的Java...
Servlet是一种Java编程接口,用于扩展服务器的功能,而JSP则提供了一种更方便的方式来创建动态网页,它将HTML和Java代码结合在一起,使得网页的动态内容生成更加容易。 在Apache Tomcat 9.0.24中,主要包含了以下...
2. **JSP支持**:Tomcat还支持JSP,这是一种声明式编程语言,可以将HTML和Java代码结合在一起,使开发者能够更容易地创建动态网页。JSP 2.1引入了标签库和自定义标签,提高了代码复用性。 3. **部署与管理**:在...
Servlet是Java编程语言中的一个接口,用于扩展服务器的功能,而JSP则是一种用于创建动态网页的技术,将HTML和Java代码结合在一起。Tomcat作为容器,负责加载、执行和管理这些Servlet和JSP。 在Apache Tomcat 8.0.32...
- **JSP(Java Server Pages)**:动态网页技术,将HTML代码和Java代码结合在一起,方便呈现动态内容。 - **过滤器(Filter)和监听器(Listener)**:增强Web应用的功能,如登录验证、日志记录等。 8. **MVC设计...
2. **JSP支持**:除了Servlet,Tomcat还支持JSP,这是一种基于HTML的服务器端脚本语言,使得Web开发者可以将静态内容与动态逻辑结合在一起。JSP文件会被Tomcat编译为Servlet运行。 3. **轻量级**:Tomcat相比其他如...
3. **NIO.2支持**:Tomcat 8采用了Java的NIO.2 API,这提供了更好的非阻塞I/O模型,提高了并发连接的处理能力,尤其是在高流量网站上。 4. **WebSocket**:Tomcat 8支持WebSocket 1.1,这是一种双向通信协议,允许...
在Java SE方面,Java API提供了大量的核心功能,包括基本数据类型、控制结构、异常处理、输入/输出流、多线程、网络编程、集合框架、反射、注解和日期时间API等。例如,`java.util`包包含了各种实用工具类,如...
4. **NIO和NIO2连接器**:Tomcat 8.0改进了连接器实现,特别是增加了对Java NIO2 API的支持,提升了并发处理能力和资源效率,从而改善了整体性能。 5. **更好的安全管理**:Tomcat 8.0.28对安全方面进行了强化,...
Apache Tomcat 7.0.16 是一个广泛使用的开源软件,主要作为Java Servlet和JavaServer Pages(JSP)的容器。它由Apache软件基金会维护,是Java EE Web应用程序部署的关键组成部分。Tomcat的设计目标是小巧、高效且...
7. **IO流和NIO**:Java的IO流系统允许读写文件和网络数据,而NIO(非阻塞I/O)提供了更高效的数据传输方式。理解这两部分将提升你在处理数据输入输出方面的技能。 8. **多线程编程**:Java支持多线程,这对于构建...
3. **封装**:封装是将数据和操作这些数据的方法捆绑在一起的过程,以隐藏实现细节,提供更安全的接口。在Java中,通过访问修饰符(public, private, protected)实现封装。 4. **继承**:继承允许一个类(子类)从...
3. **NIO.2和 APR**:Tomcat 8.0.38 提供了对Java NIO.2 API的支持,以及与Apache Portable Runtime (APR)库的集成。这可以显著提高连接处理能力和性能,尤其是在高并发环境下。 4. **安全性增强**:包括更新的安全...
JSP是Java Web开发的重要组成部分,它将HTML代码和Java代码结合在一起,用于动态生成Web页面。JSP元素包括指令、脚本元素和表达式,以及自定义标签库(TLD)。 11. **Servlet**:Servlet是Java服务器端程序,通常用于...
Servlet是Java编程语言用于生成动态网页的标准API,而JSP则是一种视图技术,允许开发者将静态HTML和动态Java代码结合在一起。 2. **Java EE兼容性**:Tomcat 7.0对应的是Java EE 6规范,这意味着它支持诸如EJB...
Apache Tomcat 是一款广泛应用的开源软件,主要用于部署和运行Java Servlet和JavaServer Pages(JSP)应用程序。在本文中,我们将深入探讨Apache Tomcat 9.0.0.M21这个版本,它作为Tomcat 9系列的一个中间里程碑版本...
Mina通过Java NIO技术实现了对不同类型的网络传输(如TCP/IP和UDP/IP)的支持,并提供了一致且简洁的API接口。除了标准的网络通信外,Mina还支持串行通信(RS232)和虚拟机内的管道通信。这一特点使得Mina成为一个...