|
|

楼主 |
发表于 2007/8/7 13:39:03
|
显示全部楼层
编写基于 Continuations 的应用程序
作为实际示例场景,我将开发一个基本的 GPS 坐标跟踪 Web 应用程序。它将在不规则的时间间隔内生成随机的经纬度值对。发挥一下想象力,生成的坐标值可能就是临近的一个公共车站、随身携带着 GPS 设备的马拉松选手、汽车拉力赛中的汽车或者运输中的包裹。令人感兴趣的是我将如何告诉浏览器这个坐标。图 1 展示了这个简单的 GPS 跟踪器应用程序的类图:
图 1. 显示 GPS 跟踪器应用程序主要组件的类图
首先,应用程序需要某种方法来生成坐标。这将由 RandomWalkGenerator 完成。从一对初始坐标对开始,每次调用它的私有 generateNextCoord() 方法时,将从该位置移动随机指定的距离,并将新的位置作为 GpsCoord 对象返回。初始化完成后,RandomWalkGenerator 将生成一个线程,该线程以随机的时间间隔调用 generateNextCoord() 方法并将生成的坐标发送给任何注册了 addListener() 的 CoordListener 实例。清单 6 展示了 RandomWalkGenerator 循环的逻辑:
清单 6. RandomWalkGenerator's run() 方法
| public void run() { try { while (true) { int sleepMillis = 5000 + (int)(Math.random()*8000d); Thread.sleep(sleepMillis); dispatchUpdate(generateNextCoord()); } } catch (Exception e) { throw new RuntimeException(e); }} |
CoordListener 是一个回调接口,仅仅定义 onCoord(GpsCoord coord) 方法。在本例中,ContinuationBasedTracker 类实现 CoordListener。ContinuationBasedTracker 的另一个公有方法是 getNextPosition(Continuation, int)。清单 7 展示了这些方法的实现:
清单 7. ContinuationBasedTracker 结构
| public GpsCoord getNextPosition(Continuation continuation, int timeoutSecs) { synchronized(this) { if (!continuation.isPending()) { pendingContinuations.add(continuation); } // Wait for next update continuation.suspend(timeoutSecs*1000); } return (GpsCoord)continuation.getObject();}public void onCoord(GpsCoord gpsCoord) { synchronized(this) { for (Continuation continuation : pendingContinuations) { continuation.setObject(gpsCoord); continuation.resume(); } pendingContinuations.clear(); }} |
当客户机使用 Continuation 调用 getNextPosition() 时,isPending 方法将检查此时的请求是否是第二次执行,然后将它添加到等待坐标的 Continuation 集合中。然后该 Continuation 被暂停。同时,onCoord —— 生成新坐标时将被调用 —— 循环遍历所有处于等待状态的 Continuation,对它们设置 GPS 坐标,并重新使用它们。之后,每个再次执行的请求完成 getNextPosition() 执行,从 Continuation 检索 GpsCoord 并将其返回给调用者。注意此处的同步需求,是为了保护 pendingContinuations 集合中的实例状态不会改变,并确保新增的 Continuation 在暂停之前没有被处理过。
最后一个难点是 servlet 代码本身,如 清单 8 所示:
清单 8. GPSTrackerServlet 实现
| public class GpsTrackerServlet extends HttpServlet { private static final int TIMEOUT_SECS = 60; private ContinuationBasedTracker tracker = new ContinuationBasedTracker(); public void service(HttpServletRequest req, HttpServletResponse res) throws java.io.IOException { Continuation c = ContinuationSupport.getContinuation(req,null); GpsCoord position = tracker.getNextPosition(c, TIMEOUT_SECS); String json = new Jsonifier().toJson(position); res.getWriter().print(json); }} |
如您所见,servlet 只执行了很少的工作。它仅仅获取了请求的 Continuation,调用 getNextPosition(),将 GPSCoord 转换成 JavaScript Object Notation (JSON),然后输出。这里不需要防止重新执行,因此我不必检查 isPending()。清单 9 展示了调用 GpsTrackerServlet 的输出,同样,有五个同步请求而服务器只有一个可用线程:
Listing 9. Output of GPSTrackerServlet
| $ for i in 'seq 1 5' ; do lynx -dump localhost:8080/tracker & done { coord : { lat : 51.51122, lng : -0.08103112 } } { coord : { lat : 51.51122, lng : -0.08103112 } } { coord : { lat : 51.51122, lng : -0.08103112 } } { coord : { lat : 51.51122, lng : -0.08103112 } } { coord : { lat : 51.51122, lng : -0.08103112 } } |
这个示例并不引人注意,但是提供了概念证明。发出请求后,它们将一直保持打开的连接直至生成坐标,此时将快速生成响应。这是 Comet 模式的基本原理,Jetty 使用这种原理在一个线程内处理 5 个并发请求,这都是 Continuations 的功劳。 |
|