10.在HTTP下进行异步编程
来源:互联网 发布:淘宝魔镜插件免费版 编辑:程序博客网 时间:2024/05/21 11:08
10.在HTTP下进行异步编程
本节将介绍在play里如何进行异步处理以实现典型的长轮询(long-polling)、流以及其他Comet-style类型的应用程序以支持上千个同时发生的连接。
暂停http请求
Play使用的是短小的请求,它使用固定的线程池来处理http连接者的查询请求。为了达到最佳效果,线程池应该尽可能小。我们通常使用的最适合的数字就是处理器数量+1来设置默认池大小。
也就是说如果请求的处理时间很长的话(比如等待一个长时的计算),这个请求就会耗尽线程池,使应用程序的响应变得很慢。当然可以扩大线程池,但这样会浪费资源,而且线程池不可能是无限的。
考虑一下聊天室应用,浏览器发送一个阻塞式的http请求用于等待显示新的信息。这个请求将会非常这长(比如几分钟),并且会打破线程池。如果计划允许100个用户同时连接聊天室程序,那么我们就需要提供至少100个线程,当然,这是可行的。但如果是1000个,或100000个呢?
在这种情况下,play允许你暂停一个请求。http请求将停留在连接状态,但请求执行将被从线程池中弹出,过会再试。你即可在固定等待的时间内告诉play去测试一下该请求,也可等待一个允许值变为可能情况。
小提示: 请看一下真实的示例samples-and-tests/chat
比如,下面这个动作将要加载一个非常耗时的job并且一直等到job完成返回结果:
public static void generatePDF(Long reportId) {
Promise<InputStream> pdf = new ReportAsPDFJob(report).now();
InputStreampdfStream = await(pdf);
renderBinary(pdfStream);
}
这里,我们使用await(…)来让Play暂停请求,直到Promise<InputStream> 值返回redeemed。
Continuations
因为框架需要收回线程以便为其他请求服务,因此play就必须暂停你的代码。在之前的play版本中await(…) 等价于waitFor(…),用于暂停你的action,之后又重新调用。
为了易于约定我们介绍的异步代码,Continuations允许代码被暂停和被透明恢复,因此书写如下代码是非常必要的:
public static void computeSomething() {
Promise<String> delayedResult = veryLongComputation(…);
String result =await(delayedResult);
render(result);
}
在这里,事实上你的代码将分成两步用两个不同的线程来执行。但这些代码对你来说是完全透明的。
使用await(…)和continuations,你可能需要写一个循环:
public static void loopWithoutBlocking() {
for(int i=0;i<=10; i++) {
Logger.info(i);
await("1s");
}
renderText("Loop finished");
}
当使用一个线程处理这个请求时,在默认的开发模式下,Play能够同时为不同的请求运行这些循环。
更实际的示例是异步从远程URL获取内容。下面将并行运行三个远程http请求:每个都调用play.libs.WS.WSRequest.getAsync()方法来执行一个GET请求,异步返回一个play.libs.F.Promise。action方法通过调用三个Promise组合实例的await(…)方法来暂停进入的http请求。当三个远程调用返回结果后,线程将自动恢复并且渲染response。
public class AsyncTest extends Controller {
public staticvoid remoteData() {
F.Promise<WS.HttpResponse> r1 =WS.url("http://example.org/1").getAsync();
F.Promise<WS.HttpResponse> r2 =WS.url("http://example.org/2").getAsync();
F.Promise<WS.HttpResponse> r3 =WS.url("http://example.org/3").getAsync();
F.Promise<List<WS.HttpResponse>> promises =F.Promise.waitAll(r1, r2, r3);
//暂停处理,直到所有三个远程调用结束
List<WS.HttpResponse> httpResponses = await(promises);
render(httpResponses);
}
}
回调Callbacks
还可以使用回调实现上面的示例。这次,await(…)方法包含了play.libs.F.Action实现,当三个远程调用结束后就调用这个回调方法。
public class AsyncTest extends Controller {
public staticvoid remoteData() {
F.Promise<WS.HttpResponse> r1 =WS.url("http://example.org/1").getAsync();
F.Promise<WS.HttpResponse> r2 =WS.url("http://example.org/2").getAsync();
F.Promise<WS.HttpResponse> r3 =WS.url("http://example.org/3").getAsync();
F.Promise<List<WS.HttpResponse>> promises =F.Promise.waitAll(r1, r2, r3);
//暂停处理,直到所有三个远程调用结束
await(promises,new F.Action<List<WS.HttpResponse>>() {
public voidinvoke(List<WS.HttpResponse> httpResponses) {
render(httpResponses);
}
});
}
}
HTTP response流streaming
既然不用中心请求也可执行循环,你或许会希望向浏览器发送结果变量的部分数据(不是全部)。这就是Content-Type:Chunked HTTP response大量httpresponse类型。它允许你多次使用多个块来发送http response。浏览器将实时接收这些块。
使用await(…)和continuations,就可以实现这个功能:
public static void generateLargeCSV() {
CSVGeneratorgenerator = new CSVGenerator();
response.contentType = "text/csv";
while(generator.hasMoreData()) {
StringsomeCsvData = await(generator.nextDataChunk());
response.writeChunk(someCsvData);
}
}
即使CSV生成需要1个小时,play也能同时使用单个线程处理多个请求,一旦为客户端的数据生成好后,play就会向客户端发送。
使用WebSockets
WebSockets是一种在浏览器和应用程序间实现双向通信的途径。在浏览器端使用“ws://” url:
newSocket("ws://localhost:9000/helloSocket?name=Guillaume")
在play端需要声明一条WS路由:
WS /helloSocket MyWebSocket.hello
MyWebSocket是一个WebSocketController。一个 WebSocket控制器和一个标准的http控制器很相似,但处理的内容不同:
- 它有一个请求对象,但没有response对象
- 它有一个可访问的session,但是只读的
- 它没有renderArgs, routeArgs和flash域
- 它只能从路由模式和QueryString里读取params
- 它拥有两个通信通道:一进一出
当客户连接到ws://localhost:9000/helloSocket套接字时, Play将调用MyWebSocket.hello动作方法。一旦MyWebSocket.hello动作方法存在,套接字就会被关闭
因此一个非常基础的套接字示例应该是这个样子:
public class MyWebSocket extends WebSocketController {
public staticvoid hello(String name) {
outbound.send("Hello %s!", name);
}
}
在这里,当客户端连接到socket时,它将接收到‘Hello Guillaume’消息,play随后将关闭这个socket。
当然,通常情况下你不需要立即关闭socket,用await(…)和continuations也能实现。
比如一个基础的Echo服务器:
public class MyWebSocket extends WebSocketController {
public staticvoid echo() {
while(inbound.isOpen()) {
WebSocketEvent e = await(inbound.nextEvent());
if(einstanceof WebSocketFrame) {
WebSocketFrameframe = (WebSocketFrame)e;
if(!e.isBinary) {
if(frame.textData.equals("quit")) {
outbound.send("Bye!");
disconnect();
} else {
outbound.send("Echo: %s", frame.textData);
}
}
}
if(einstanceof WebSocketClose) {
Logger.info("Socket closed!");
}
}
}
}
在上面的示例里,嵌套的‘if’和‘cast’很乏味,而且容易出错。即使这个简单示例也不容易处理。更复杂的多个联合线程的情况下将会有更多的事件类型,这将是一个恶梦。
这就是为什么我们要向你介绍在play.libs.F库里的基础的模式匹配的原因。
现在我们重写一下echo示例:
public static void echo() {
while(inbound.isOpen()) {
WebSocketEvent e = await(inbound.nextEvent());
for(Stringquit: TextFrame.and(Equals("quit")).match(e)) {
outbound.send("Bye!");
disconnect();
}
for(Stringmsg: TextFrame.match(e)) {
outbound.send("Echo: %s", frame.textData);
}
for(WebSocketClose closed: SocketClosed.match(e)) {
Logger.info("Socket closed!");
}
}
}
- 10.在HTTP下进行异步编程
- 在Android中使用Volley进行异步HTTP请求
- 在 C++ 中使用 PPL 进行异步编程
- 在Linux下使用管道进行编程
- 在Linux下使用管道进行编程
- 在DOS下如何进行汇编语言编程
- 使用委托进行异步编程
- 在MacOS下使用Charles进行Http请求分析
- Wininet编程之在windows环境下http协议编程
- 使用PHP进行异步HTTP请求
- Java利用httpasyncclient进行异步HTTP请求
- Java利用httpasyncclient进行异步HTTP请求
- 在Visual C++中用ADO进行数据库编程(下)
- 在LINUX下利用pthread库进行多线程编程
- 在Visual C++中用ADO进行数据库编程(下)
- 在LINUX下利用pthread库进行多线程编程
- 在Visual C++中用ADO进行数据库编程(下)
- 在vxworks下进行OpenGL编程的环境搭建
- bulid签名apk出错
- 如何重置CentOS 7的Root密码
- Spring公共头部信息,基于本地验证的
- NYOJ +-字符串
- Linux入门(3)——Ubuntu16.04下安装VMware
- 10.在HTTP下进行异步编程
- Android开发AndroidStudio多渠道打包
- Retrofit2.0使用
- linux c/c++ 面试题目整理(三)
- Petalinux BSP使用中的问题及注意事项(持续更新)
- nginx 服务器重启命令,关闭
- TypeError: 'builtin_function_or_method' object has no attribute '__getitem__'(已解决)
- TP5在虚拟主机上部署遇到的坑
- 十七、Git使用详解与Github上传代码