续(三)
第二章. 阻塞I/O模型
阻塞(或经典)I/O在Java中表现为一个高效的和实用的I/O模型,其能够很好的适应于并发连接数相对适中的高性能应用程序。现代JVM都可进行有效地上下文切换,阻塞I/O模型应当依据原始数据吞吐量提供最好的性能,只要并发连接数低于一千且连接多半忙于数据传输时。然而对于拥有大多数时间处于空闲状态的连接的应用程序来说,上下文切换的开销可能会变大。那么一个非阻塞I/O模型可能会是一个更好的选择。
2.1. 阻塞HTTP连接
HTTP连接负责HTTP消息的序列化和反序列化。应当是很少需要直接使用HTTP连接对象。这儿有一些为HTTP请求的执行和处理而设计的更高层次的协议组件。然而,在某些情况下直接于HTTP连接的交互可能是必要的。例如,要访问类似连接状态、套接字超时或本地和远程地址等属性。
考虑到HTTP连接是线程不安全是很重要的。我们强烈建议在一个线程里跟HTTP连接对象交互。HttpConnection接口和它的子接口里唯一可以安全地从另一个线程中调用的方法是HttpConnection#shutdown()。
2.2.1. 与阻塞HTTP连接一起工作
HttpCore不为打开连接提供完全的支持,因为一个新连接的处理——尤其是在客户端——当涉及到一项或多项认证和代理通道时可能非常复杂。作为替代,阻塞HTTP连接能够被绑定到任意的网络套接字上。
***********************************************************************************
* *
* Socket socket = <...> *
* DefaultBHttpClientConnection conn = new DefaultBHttpClientConnection(8 * 1024); *
* conn.bind(socket); *
* System.out.println(conn.isOpen()); *
* HttpConnectionMetrics metrics = conn.getMetrics(); *
* System.out.println(metrics.getRequestCount()); *
* System.out.println(metrics.getResponseCount()); *
* System.out.println(metrics.getReceivedBytesCount()); *
* System.out.println(metrics.getSentBytesCount()); *
* *
***********************************************************************************
HTTP连接接口,客户端和服务端,消息的发送和接收是分为两个阶段的。消息头首先被传送,取决于消息头的属性,一个消息体紧随其后。请注意为了表示消息的处理已完成,总是要关闭底层内容流是非常重要的。从底层连接的输入流直接流出它们的内容的HTTP实体为了连接的潜在重用必须确保它们完全地消费消息体内容。
在客户端的请求执行的过度简化过程可能看起来像这样:
***********************************************************************************
* *
* Socket socket = <...> *
* DefaultBHttpClientConnection conn = new DefaultBHttpClientConnection(8 * 1024); *
* conn.bind(socket); *
* HttpRequest request = new BasicHttpRequest("GET", "/"); *
* conn.sendRequestHeader(request); *
* HttpResponse response = conn.receiveResponseHeader(); *
* conn.receiveResponseEntity(response); *
* HttpEntity entity = response.getEntity(); *
* if (entity != null) { *
* // Do something useful with the entity and, when done, ensure all *
* // content has been consumed, so that the underlying connection *
* // can be re-used *
* EntityUtils.consume(entity); *
* } *
* *
***********************************************************************************
在服务端的请求处理的过度简化过程可能看起来像这样:
***********************************************************************************
* *
* Socket socket = <...> *
* DefaultBHttpServerConnection conn = new DefaultBHttpServerConnection(8 * 1024); *
* conn.bind(socket); *
* HttpRequest request = conn.receiveRequestHeader(); *
* if (request instanceof HttpEntityEnclosingRequest) { *
* conn.receiveRequestEntity((HttpEntityEnclosingRequest) request); *
* HttpEntity entity = ((HttpEntityEnclosingRequest) request) *
* .getEntity(); *
* if (entity != null) { *
* // Do something useful with the entity and, when done, ensure all *
* // content has been consumed, so that the underlying connection *
* // could be re-used *
* EntityUtils.consume(entity); *
* } *
* } *
* HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, *
* 200, "OK") ; *
* response.setEntity(new StringEntity("Got it") ); *
* conn.sendResponseHeader(response); *
* conn.sendResponseEntity(response); *
* *
***********************************************************************************
请注意可能很少会需要使用这些低层次的方法取传输消息,反而平时应当使用相应的更高层次的HTTP服务的实现以取而代之。
2.1.2. 用阻塞I/O传输内容
HTTP连接使用HttpEntity接口管理内容传输的过程。HTTP连接产生一个实体用以封装到埠消息的内容流。请注意HttpServerConnection#receiveRequestEntity()和HttpClientConnection#receiveResponseEntity()不会获取或缓存任何到埠数据。他们仅仅基于到埠消息的属性而注入一个相应的内容编码机。内容能够使用HttpEntity#getContent()通过读取附加在内容输入流上的实体而被获取到。到埠数据将会被自动地解码并对数据消费方来说是完全透明的。类似地,HTTP连接依赖HttpEntity#writeTo(OutputStream)去产生离埠消息的内容。如果一个离埠消息附加了一个实体,那么内容将会基于消息的属性而被自动地编码。
2.1.3. 支持内容传输机制
HTTP连接默认的实现支持定义于HTTP/1.1规范中的三中内容传输机制:
* Content-Length delimited:内容实体的结尾由Content-Length头的值决定。最大实体长度:long#MAX_VALUE
* Identity coding: 内容实体的结尾被底层连接的关闭定出(尾流条件)。显而易见标识码只能被用用在服务端。最大实体长度:无限制。
* Chunk coding:内容分为很多小块进行发送。最大实体长度:无限制。
相应的内容流类依据消息所附加的实体的属性将被自动地创建。
2.1.4. HTTP连接的终结
HTTP连接要么通过调用HttpConnection#close()能被优雅地关闭要么通过调用HttpConnection#shutdown()能被强制性地关闭。前者在关闭连接前尝试刷新所有的缓存的数据病可能引发死锁。HttpConnection#close()方法是线程不安全的。后者的连接关闭不会伴随内部缓存的刷新同时将尽快返回控制权给调用者并且不会阻塞太久。HttpConnection#shutdown()方法是线程安全的。
2.2. HTTP异常处理
所有的HttpCore组件都可能抛出两种类型的异常:IOException在I/O错误的情况下例如套接字超时或一个套接字重置;HttpException指示一个Http错误例如一个Http协议违反。通常I/O错误被认为是非致命的和可修复的,但是Http协议错误被认为是致命的和不可自动修复的。
2.2.1. 协议异常
ProtocolException表示一个致命的HTTP协议违反。通常这会致使消息处理立刻终结。
2.3. 阻塞HTTP协议处理者
2.3.1. HTTP服务
HttpService是一个基于实现了HTTP协议的基本需求的阻塞I/O模型的用于FRC2616所描述的服务端消息处理的服务端HTTP协议处理者。
HttpService依赖HttpProcessor实例去为所有的离埠消息产生必要性协议头,还有应用公共的、cross-cutting消息变化到所有到埠和离埠消息,但是HTTP请求处理者被期望去关注应用程序特定内容的产生和处理。
**************************************************************
* *
* HttpProcessorhttpproc = HttpProcessorBuilder.create() *
* .add(new ResponseDate()) *
* .add(new ResponseServer("MyServer-HTTP/1.1")) *
* .add(new ResponseContent()) *
* .add(new ResponseConnControl()) *
* .build(); *
* HttpService httpService = new HttpService(httpproc, null); *
* *
**************************************************************
2.3.1.1. HTTP请求处理者
HttpRequestHandler接口代表一组特定HTTP请求处理的一个程序。鉴于单个的请求处理者被期望去关注应用程序特定的HTTP处理,所以HttpService被设计成为关注协议特定方面。一个请求处理者的主要的目的是在针对给定的请求的响应中产生响应对象及其内容实体,并发回到客户端。
**************************************************************************
* *
* HttpRequestHandler myRequestHandler = new HttpRequestHandler() { *
* public void handle( *
* HttpRequest request, *
* HttpResponse response, *
* HttpContext context) throws HttpException, IOException { *
* response.setStatusCode(HttpStatus.SC_OK); *
* response.setEntity( *
* new StringEntity("some important message", *
* ContentType.TEXT_PLAIN)); *
* } *
* }; *
* *
**************************************************************************
2.3.1.2. 请求处理者分解器
HTTP请求处理者通常被HttpRequestHandlerResolver以匹配一个请求URI到一个请求处理者的方式进行管理。
**********************************************************************************
* *
* HttpProcessor httpproc = <...> *
* HttpRequestHandler myRequestHandler1 = <...> *
* HttpRequestHandler myRequestHandler2 = <...> *
* HttpRequestHandler myRequestHandler3 = <...> *
* UriHttpRequestHandlerMapper handlerMapper = new UriHttpRequestHandlerMapper(); *
* handlerMapper.register("/service/*", myRequestHandler1); *
* handlerMapper.register("*.do", myRequestHandler2); *
* handlerMapper.register("*", myRequestHandler3); *
* HttpService httpService = new HttpService(httpproc, handlerMapper); *
* *
**********************************************************************************
用户被鼓励去提供HttpRequestHandlerResolver的更多的精妙的实现——例如,基于正则表达式。
2.3.1.3. 使用HTTP服务去处理请求
当完成初始化和配置,HttpService就能够被用来为活动的HTTP连接执行和处理请求了。HttpService#handleRequest()方法读取一个到埠请求,产生一个响应并发回客户端。在一个持久连接上这个方法可以执行在一个循环中处理多个请求。HttpService#handleREquest()方法在多线程执行时是安全的。这允许在多个连接上同时地处理请求,只要所有的使用协议拦截器和请求处理者的HttpService是线程安全的就行。
*****************************************************
* *
* HttpService httpService = <...> *
* HttpServerConnection conn = <...> *
* HttpContext context = <...> *
* *
* boolean active = true; *
* try { *
* while (active && conn.isOpen()) { *
* httpService.handleRequest(conn, context); *
* } *
* } finally { *
* conn.shutdown(); *
* } *
* *
*****************************************************
未完待续。。。
阅读(2122) | 评论(0) | 转发(0) |