标签:print 只读 auto 处理过程 客户 注意 影响 空白 sock
HTTP异步编程
这意味着如果某个请求非常耗时(比如处理长时间的运算),它将会阻塞线程池并且影响应用程序的响应能力。当然,可以通过增大线程池大小来解决问题,但是这样会造成资源的极大浪费,而且线程池的大小也不可能无止尽地增大。
试想聊天应用程序的例子:浏览器发送一个阻塞的HTTP请求,这个HTTP请求的作用是等待新消息后显示。这种类型的HTTP请求会占用很长时间(通常是好几秒),从而导致线程池的阻塞。如果有100个用户同时连接这个聊天应用程序,那么至少需要提供100个线程。这还是可以接受的,如果有1000个用户呢?甚至有10000个呢?
为了解决这种情况,Play允许临时挂起HTTP请求。挂起的HTTP请求仍然保持连接,但是该请求的执行会被移出线程池并稍后进行尝试。根据需要,Play可以在一段固定的延时后恢复现场,继续执行请求。
下例Action使用now()方法调用ReportAsPDFJob,该Job需要较长的处理时间。按照正常的情况,程序必须等待ReportAsPDFJob执行完后,才能向HTTP发送响应结果:
public static void generatePDF(Long reportId) {
Promise<InputStream> pdf = new ReportAsPDFJob(report).now();
InputStream pdfStream = await(pdf);
renderBinary(pdfStream);
}
在应用中引入Continuations技术是为了使代码的异步处理变得简单化。由于Continuations允许显式地挂起和重用代码,因此可以采用如下方式:
public static void computeSomething() {
Promise<String> delayedResult = veryLongComputation(…);
String result = await(delayedResult);
render(result);
}
public static void loopWithoutBlocking() {
for(int i=0; i<=10; i++) {
Logger.info(i);
await("1s");
}
renderText("Loop finished");
}
Cotinuations主要通过使用控制器调用await()方法实现,该方法接收两种不同类型的参数(事实上有6种重载的方法,但是主要应用场景为2种)。
因此,上述的两种方式都会导致在未来某个时间点触发事件。第一种为预先指定,而第二种需要根据Promise完成的时间。
Play引入Cotinuations,使得编写同类事件结构的代码更加简单:
//相关处理A
await(timeout or promise);//等待promise执行完毕
//相关处理B
在等待处理的过程中,服务器将HTTP线程释放出来,因此Play能够并发处理更多的请求,而且非常高效。当timeout时间到达或者Promise执行完毕,后续的代码会继续执行,并且不需要开发者编写任何与线程唤起相关的方法。
使用timeout的方式来调用await()方法:
public static void useTimeout() {
for(int i=0; i<=10; i++) {
Logger.info(i);
await("1s");
}
renderText("Execute finished");
}
以上这段代码在执行过程中,一共释放了10次线程,并且在每秒等待结束后重新唤起。从开发者的角度看,这个处理过程是非常透明的,并且允许直观地构建应用(而不需要担心创建非阻塞应用,因为这些都交由Play进行处理)。
使用Promise的方式来调用await()方法:
public static void usePromise(){
F.Promise<WS.HttpResponse> promise1=WS.url("http://domain1.com").getAsync();
F.Promise<WS.HttpResponse> promise2=WS.url("http://domain2.com").getAsync();
F.Promise<List<WS.HttpResponse>> promises = F.Promise.waitAll(promise1, promise2);
await(promises);
renderText("Execute finished");
}
上述代码使用了lib.F中的waitAll()方法,需要等待promise1和promise2都处理完成后,才能够继续执行后续处理。类似地,Play还提供了waitAny(),waitEither()等方法。
1.3 HTTP流式响应
由于Play提供在非阻塞的情况下轮询处理请求的功能,读者可能会有这样的设想:服务器端能否实现只要生成了一部分可用的结果数据就马上发送给浏览器。在Play中实现这个功能完全没有问题,而实现的关键就是以Content-Type:Chunked作为HTTP的响应类型。它允许将HTTP响应分成不同的块(chunk)分批发送,只要这些分块一被发出,浏览器立马就能接收到。以下是使用await()方法和Continuations的实现:
public static void generateLargeCSV() {
CSVGenerator generator = new CSVGenerator();
response.contentType = "text/csv";
while(generator.hasMoreData()) {
String someCsvData = await(generator.nextDataChunk());
response.writeChunk(someCsvData);
}
}
1.4 WebSocket介绍
在浏览器端,可以使用“ws://” URL方式建立socket连接:
new WebSocket("ws://localhost:9000/helloSocket?name=Guillaume")
WS /helloSocket MyWebSocket.hello
当客户端(即浏览器)通过ws://localhost:9000/helloSocket 建立socket连接时,Play会调用MyWebSocket控制器中的hello Action方法,一旦MyWebSocket.hello方法结束,该socket连接就会自动关闭:
public class MyWebSocket extends WebSocketController {
public static void hello(String name) {
outbound.send("Hello %s!", name);
}
}
当然,大部分情况下并不需要急于将socket连接关闭,可以使用await()方法进行一些适当的扩展。以下程序使用了Continuations,使服务器具有应答功能:
public class MyWebSocket extends WebSocketController {
public static void echo() {
while(inbound.isOpen()) {
WebSocketEvent e = await(inbound.nextEvent());
if(e instanceof WebSocketFrame) {
WebSocketFrame frame = (WebSocketFrame)e;
if(!e.isBinary) {
if(frame.textData.equals("quit")) {
outbound.send("Bye!");
disconnect();
} else {
outbound.send("Echo: %s", frame.textData);
}
}
}
if(e instanceof WebSocketClose) {
Logger.info("Socket closed!");
}
}
}
}
public static void echo() {
while(inbound.isOpen()) {
WebSocketEvent e = await(inbound.nextEvent());
for(String quit: TextFrame.and(Equals("quit")).match(e)) {
outbound.send("Bye!");
disconnect();
}
for(String msg: TextFrame.match(e)) {
outbound.send("Echo: %s", frame.textData);
}
for(WebSocketClose closed: SocketClosed.match(e)) {
Logger.info("Socket closed!");
}
}
}
开发WebSocket应用,需要使用支持WebSocket的浏览器(比如Chrome)。Firefox和Opera出于安全考虑,无法使用WebSocket协议。与长时间处理的方法不同,WebSocket中的方法需要在预先定义的时间间隔执行针对模型更新的数据库检查,从而触发事件。WebSocket的理念是保持连接状态,等待事件的触发,然后将事件广播给每个需要的用户。因此在Play中实现WebSocket的最佳方式是使用存储在服务器端的状态对象。虽然这样做有点违背无状态(stateless)以及RESTful风格的理念,但这应该是使用WebSocket的最佳实践。在设计WebSocket应用时,开发者需要根据自己的实际情况对代码做进一步的优化:
下面将演示如何创建WebSocket应用。使用play new命令创建新的应用,名称为websocket:
Play new websocket
在app/models/目录中创建StatefulModel.java:
package models;
import play.libs.F;
public class StatefulModel {
public static StatefulModel instance = new StatefulModel();
public final F.EventStream event = new F.EventStream();
private StatefulModel() { }
}
StatefulModel非常简单,由以下几个部分组成:
在这个例子当中,使用了标准的EventStream来访问当前的事件。Play同时提供了ArchiveEventStream(读者可以在samples-and-tests目录中查看Play提供的chat应用示例),可以获取所有可用的信息。打开app/controllers/Application.java文件,添加如下代码:
package controllers;
import play.mvc.*;
import models.*;
public class Application extends Controller {
public static void index() {
render();
}
public static class WebSocket extends WebSocketController {
public static void listen() {
while(inbound.isOpen()) {
String event = await(StatefulModel.instance.event.nextEvent());
outbound.send(event);
}
}
}
}
在Application控制器中增加了继承WebSocketController的静态类。WebSocketController与标准的控制器有所不同:前者是基于inbound/outbound模式,而后者是面向request/response模式的。上述代码的业务逻辑非常简单,只是通过while循环来检查inbound是否处于打开状态(即WebSocket处于连接状态),接着调用nextEvent()方法来等待事件的触发。
早期的Play版本并没有await()方法。await()方法的作用是挂起HTTP请求,释放当前资源让框架以便继续处理其他的请求。直到有新的事件添加到StatefulModel中,程序会从之前离开的地方继续执行,而不是重新开始。
代码将数据从事件发送到outbound,最终返回到浏览器。那么浏览器需要如何处理这些数据呢?创建views/application/index.html模板:
#{extends ‘main.html‘ /}
#{set title:‘Home‘ /}
<div id="socketout"></div>
<script type="text/javascript">
// Create a socket
var socket = new WebSocket(‘@@{Application.WebSocket.listen}‘)
// Message received on the socket
socket.onmessage = function(event) {
$(‘#socketout‘).append(event.data+"<br />");
}
</script>
在模版中,仅仅使用div来显示WebSocket发送的数据。JavaScript的内容为:
在为自定义的WebSocket增加事件之前,先定义好路由:
WS /socket Application.WebSocket.listen
需要注意的是,这里使用WS作为HTTP请求类型来描述WebSocket请求,剩下的部分和之前一样配置。现在WebSocket已经可以运行了,但是这时候开启应用,打开浏览器看到的是空白页面,这是因为服务器并没有返回数据给浏览器,所以最后需要做的是触发事件。创建异步Job,在EventStream中增加一些消息。
在app目录下新建jobs包,然后创建Startup.java文件:
package job;
import play.jobs.*;
import models.StatefulModel;
@OnApplicationStart(async = true)
public class Startup extends Job {
public void doJob() throws InterruptedException {
int i = 0;
while (true) {
i++;
Thread.sleep(1000);
StatefulModel.instance.event.publish("On step " + i);
}
}
}
这个Job会在应用启动的时候执行,并一直循环下去,直到应用停止。每次迭代的时候暂停1秒钟,然后为StatefulModel的EventStream发送一个事件。
开启应用,访问http://localhost:9000/查看效果,当打开页面的时候,可以发现事件会广播到每个监听的浏览器:
标签:print 只读 auto 处理过程 客户 注意 影响 空白 sock
原文地址:http://blog.csdn.net/zyhlal/article/details/53023915