我阅读了很多有关 HTML5 的内容,并且特别喜欢 Web 套接字,因为它们促进 Web 服务器和 Web 浏览器之间的双向通信。

但我们一直在阅读有关 chrome、opera、firefox、safari 为 html5 做准备的内容。哪个 Web 服务器已准备好使用 Web 套接字功能?我的意思是,从今天开始,网络服务器是否能够发起后续通信?Google 自己的 Appengine 怎么样?

如何编写一个示例 Web 应用程序来利用 Java 中的此功能?


Web 服务器和浏览器之间的双向通信并不是什么新鲜事。如果您正在阅读的问题发布了新答案,Stack Overflow 今天就会执行此操作。使用现有技术实现套接字样式行为有几种不同的策略:

  • AJAX短轮询:连接到服务器并询问是否有新消息。如果没有,请立即断开连接,并在短暂间隔后再次询问。当您不想让大量长时间运行的空闲连接对服务器开放时,这非常有用,但这意味着您只能以轮询间隔的速度接收新消息,并且会产生建立连接的开销。每次轮询时都会有新的 HTTP 连接。
  • AJAX 长轮询:连接到服务器并保持连接打开,直到有新消息可用。这可以让您快速传送新消息并减少 HTTP 连接频率,但会导致服务器上长时间运行的空闲进程增多。
  • iframe 长轮询:与上面相同,只是使用隐藏的 iframe 而不是 XHR 对象。当您想要进行跨站点长轮询时,对于绕过同源策略很有用。
  • 插件:Flash 的 XMLSocket、Java 小程序等可用于建立更接近于浏览器的真正低级持久套接字的东西。

HTML5 套接字并没有真正改变可用的底层策略。大多数情况下,它们只是形式化已经使用的策略,并允许显式识别持久连接,从而更智能地处理。假设您想要向移动浏览器发送基于网络的推送消息。对于正常的长轮询,移动设备需要保持唤醒状态以保持连接。使用 WebSockets,当移动设备想要进入睡眠状态时,它可以将连接移交给代理,当代理收到新数据时,它可以唤醒设备并传回消息。

服务器端是完全开放的。要实现短轮询应用程序的服务器端,您只需要某种按时间顺序排列的消息队列。当客户端连接时,它们可以将新消息移出队列,或者可以传递偏移量并读取比其偏移量更新的任何消息。

实现服务器端长轮询是您的选择范围开始缩小的地方。大多数 HTTP 服务器都是为短期请求而设计的:连接、请求资源,然后断开连接。如果 10 分钟内有 300 人访问您的网站,并且每个人需要 2 秒来连接和下载 HTTP 资源,则您的服务器在任何给定时间将平均打开 1 个 HTTP 连接。使用长轮询应用程序,您会突然保持 300 倍的连接数。

If you're running your own dedicated server you may be able to handle this, but on shared hosting platforms you're likely to bump up against resource limits, and App Engine is no exception. App Engine is designed to handle a high volume of low latency requests, e.g. short polling. You can implement long polling on App Engine, but it's ill-advised; requests that run for longer than 30 seconds will get terminated, and the long running processes will eat up your CPU quota.

App Engine's solution for this is the upcoming Channel API. The channel API implements long polling using Google's existing robust XMPP infrastructure.

Brett Bavar and Moishe Lettvin's Google I/O talk lays out the usage pattern as follows:

App Engine 应用程序在远程服务器上创建一个通道,并返回一个通道 ID,并将其传递给 Web 浏览器。

class MainPage(webapp.RequestHandler):

    def get(self):
        id = channel.create_channel(key)
        self.response.out.write(
            {'channel_id': id})

Web 浏览器将通道 ID 传递到同一远程服务器,以通过 iframe 长轮询建立连接:

<script src='/_ah/channel/jsapi'></script>
<script>
  var channelID = '{{ channel_id }}';
  var channel =
    new goog.appengine.Channel(channelId);
  var socket = channel.open();
  socket.onmessage = function(evt) {
    alert(evt.data);
  }
</script>

当有趣的事情发生时,App Engine 应用程序可以将消息推送到用户的频道,浏览器的长轮询请求将立即收到它:

class OtherPage(webapp.RequestHandler):

    def get(self):
        # something happened
        channel.send_message(key, 'bar')

例如,Jetty 从版本 7 开始就支持此功能:Jetty Websocket Server

Google App Engine 也有这方面的计划。他们甚至在 Google I/O 2010 上提供了该功能的工作演示,但尚未投入生产。请参阅票#377