二、阻塞IO模型
Java 的阻塞式I/O 模型,对于那些并行连接数较少的对性能要求较高的应用程序是非常高效和方便的。现在的JVM具有高效的上下文切换能力,只要并行连接数较少并且连接都忙于传输数据,那么阻塞式I/O模型在原始数据吞吐量上就会有很好的性能。
(一)、阻塞式HTTP连接
HTTP连接主要用于HTTP消息的序列化和反序列化,很少直接使用HTTP连接对象。有更高层次的协议组件来执行和处理HTTP请求,然而,与HTTP连接进行交互有时候也是必要的,例如去访问连接状态、Socket超时、本地或远程地址等属性。
注意:HTTP连接不是线程安全的,我们在使用的过程中,应该将与HTTP连接对象的所有交互限制为一个线程,在HttpConnection 接口和它的子接口中只有shutdown()方法是线程安全的
1、阻塞式连接的使用
HttpCore并没有对打开连接做完整的支持,因为当一个连接涉及到认证或隧道技术的时候建立一个连接是非常复杂的,相反,阻塞式的HTTP连接可以绑定到任意的网络套接字
我们可以利用HttpConnection的对象拿到连接的一些信息(本地及远程主机地址和端口号),同时可以拿到一个封装了连接使用过程中的统计信息的HttpConnectionMetrics的对象,从而获取与连接相关的一些统计信息(请求和响应数量,接受和发送的数据字节数等)
示例代码
public void httpConnectionBindSocketTest() throws Exception {
//定义一个Socket
Socket socket = new Socket("127.0.0.1", 8080);
//定义一个有固定缓冲区的Http连接对象
DefaultBHttpClientConnection connection = new DefaultBHttpClientConnection(8*1024);
//将http连接对象绑定到套接字上,执行此方法后,这个套接字将被连接用来发送和接受数据,
//连接的状态会变为打开,isOpen()将返回true
connection.bind(socket);
//获取连接的一些相关信息,例如:连接是否打开、连接的本地及远程主机地址和端口号等
System.out.println(connection.isOpen());
System.out.println(connection.getLocalPort());
System.out.println(connection.getRemoteAddress());
//HttpConnectionMetrics封装了使用该连接的过程中的一些统计信息,例如:请求和响应数量,发送和接收到的字节数量等
HttpConnectionMetrics metrics = connection.getMetrics();
System.out.println(metrics.getRequestCount());
System.out.println(metrics.getResponseCount());
System.out.println(metrics.getReceivedBytesCount());
System.out.println(metrics.getSentBytesCount());
}
包括客户端和服务端在内的HttpConnection接口分两个阶段发送和接收消息,首先传输消息头,之后传输消息体。为了标记消息处理结束,我们必须记得及时关闭底层内容流,以便此连接能够被重用
客户端对请求的处理过程大致如下:
public void clientConnectionTest() throws Exception {
Socket socket = new Socket("127.0.0.1", 8080);
DefaultBHttpClientConnection connection = new DefaultBHttpClientConnection(2048*1024);
//将连接绑定到特定的socket上
connection.bind(socket);
HttpRequest request = new BasicHttpRequest("GET","/",HttpVersion.HTTP_1_1);
//发送请求头
connection.sendRequestHeader(request);
//如果有请求体的话,发送完请求头应发送请求体
//接收服务端的响应头,并将其封装到一个响应中
HttpResponse response = connection.receiveResponseHeader();
System.out.println(response);
//接收响应体,并将其封装在之前的响应中
connection.receiveResponseEntity(response);
//获取封装在响应中的实体
HttpEntity entity = response.getEntity();
//解析实体,拿到需要的信息,并关闭底层流
if(entity!=null) {
System.out.println(EntityUtils.toString(entity));
//处理实体,获取需要的信息
EntityUtils.consume(entity);
}
}
服务端对请求处理的过程大致如下:
public void serverConnectionTest() throws Exception {
Socket socket = new Socket("127.0.0.1", 8081);
DefaultBHttpServerConnection connection = new DefaultBHttpServerConnection(8*1024);
//将特定的Socket绑定到服务端的Connection中
connection.bind(socket);
//接收客户端请求的请求头,并将其封装到一个请求对象中
HttpRequest request = connection.receiveRequestHeader();
if(request instanceof HttpEntityEnclosingRequest) {
//接收客户端请求的请求体
connection.receiveRequestEntity((HttpEntityEnclosingRequest) request);
HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
if(entity!=null) {
//处理请求中的实体,拿到需要的信息
//关闭底层流
EntityUtils.consume(entity);
}
}
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, 200, "OK");
response.setEntity(new StringEntity("Get it"));
//发送响应头
connection.sendResponseHeader(response);
//发送响应体
connection.sendResponseEntity(response);
}
注意:一般情况下很少使用这些较底层的方法来传递Http 消息,通常应使用一些更高级别的Http 服务来实现
2、通过阻塞式I/O传输内容
Http 连接通过使用HttpEntity接口来管理内容传输的过程,Http 连接会生成一个封装了传入消息内容流的entity对象。
HttpServerConnection#receiveRequestEntity 和 HttpClientConnection#receiveResponseEntity 都不会取出或者缓存任何的传入数据,它们仅仅根据消息的属性注入合适的编解码器。内容被取出是通过HttpEntity.getContent()方法来读取封闭实体的输入流来完成的,传入的数据会自动解码并且完全透明的传递给数据的使用者。
同样,Http连接依赖HttpEntity.writeTo(OutputStream)方法产生传出消息的内容,当一个传出消息要封装成一个实体时,那么内容将会被基于消息属性自动编码。
3、支持的内容传输机制
http connection 的默认实现支持被HTTP/1.1 所定义的三种传输机制
- Content-Length delimited:内容实体的结束由Content-Length header 所决定 最大实体长度为Long.MAX_VALUE
- Identity coding:通过关闭底层的连接(流结束条件)来限定内容实体的结束,Identity coding 只能在服务器端使用,最大实体长度没有限制
- Chunk coding:内容以小块的形式发送,最大实体长度没有限制
4、终止Http连接
可以通过调用HttpConnection#close() 或者 HttpConnection#shutdown() 来终止http 连接
close()方法会在终止连接之前尝试刷新缓冲区的所有数据,这可能造成无限期的阻塞。此方法不是线程安全的。
shutdown()方法在不刷新缓冲区的情况下终止连接,此方法会尽可能快的将控制权返回给调用者,不会长时间的阻塞,此方法是线程安全的。
之后内容见下一篇博客,HttpCore 教程(四)
之前内容见前一篇博客 HttpCore 教程(二)