大家都知道这样一个事实,那就是HTTP(Hypertext Transfer Protocol)是一个无状态的请求-响应式协议。HTTP协议的这种简单设计使它颇具扩展性却不够高效,并且不适合于频繁交互的实时网络应用。HTTP被设计用来进行文档共享而不是用来建立频繁交互的网络应用。HTTP天生就不太正规,对每一个http请求/响应,都要通过线路传输许多头信息。 在HTTP 1.1版本之前,每一个提交到服务器的请求都会创建一个新的链接。这种情况在HTTP 1.1中通过引入HTTP持久化连接得以改进。持久化连接允许web浏览器复用同样的连接来获取图片,脚本等等。 HTTP被设计成半双工的,这意味着同一时刻只允许向一个方向上传输数据。Walkie-talkie是一个半双工设施的例子,因为一个时刻只能有一个人说话。开发者们已经创造出了一些工作方法或者应对方法来克服HTTP的这个缺点。这些工作方法包括轮询,长效轮询和流。 | ![]() lwei
|
使用轮询时,客户端采用同步调用来从服务器获取信息。如果服务器有新信息,它会在响应中返回数据。否则,就不会有信息返回给客户端,然后客户端会在一段时间之后再次创建一个新的连接。这是一种很低效却很简单的获得实时行为的方式。长效轮询是另一种工作方法,客户端会对服务器发起一个连接,服务器会保持该连接直到有可用数据或者超过了指定超期时间。长效轮询也被称为comet。由于同步的HTTP和这些异步应用之间的不匹配,这些方案易于复杂化,无标准化和低效化。 随着时间的推移,对于在客户端和服务器之间建立基于标准的、双向的、全双工通道的需求,已经增长了。在本文中,我们会看到WebSockets是如何帮助解决这些问题的,然后了解一下如何在Java中使用JSR 356 API来构建基于WebSocket的应用。 请注意本文不会讨论到OpenShift WebSocket支持。如果你想了解OpenShift WebSocket支持,请参考Marek Jelen的这篇文章。 | ![]() lwei
|
什么是WebSocket?一个WebSocket是通过一个独立的TCP连接实现的、异步的、双向的、全双工的消息传递实现机制。WebSockets不是一个HTTP连接,却使用HTTP来引导一个WebSocket连接。一个全双工的系统允许同时进行双向的通讯。陆地线路电话是一个全双工设施的例子,因为它们允许两个通话者同时讲话并被对方听到。最初WebSocket被提议作为HTML5规范的一部分,HTML5承诺给现代的交互式的web应用带来开发上的便利和网络效率,但是随后WebSocket被移到一个仅用来存放WebSockets规范的独立的标准文档里。它包含两件事情 -- WebSocket协议规范,即2011年12月发布的RFC 6455,和WebSocket JavaScript API。 WebSocket协议利用HTTP 升级头信息来把一个HTTP连接升级为一个WebSocket连接。HTML5 WebSockets 解决了许多导致HTTP不适合于实时应用的问题,并且它通过避免复杂的工作方式使得应用结构很简单。 最新的浏览器都支持WebSockets,如下图所示。该信息来自于http://caniuse.com/#feat=websockets.
| ![]() lwei
|
WebSocket是如何工作的?每一个WebSocket连接的生命都是从一个HTTP请求开始的。HTTP请求跟其他请求很类似,除了它拥有一个Upgrade头信息。Upgrade头信息表示一个客户端希望把连接升级为不同的协议。对WebSockets来说,它希望升级为WebSocket协议。当客户端和服务器通过底层连接第一次握手时,WebSocket连接通过把HTTP协议转换升级为WebSockets协议而得以建立。一旦WebSocket连接成功建立,消息就可以在客户端和服务器之间进行双向发送。 WebSockets带来了性能,简单化和更少带宽消耗
WebSocket使用案例一些可能的WebSockets使用案例有:
| ![]() lwei
|
在Java中使用WebSockets在Java社区中下面的情形很普遍,不同的供应商和开发者编写类库来使用某项技术,一段时间之后当该技术成熟时它就会被标准化,来使开发者可以在不同实现之间互相操作,而不用冒供应商锁定的风险。当JSR 365启动时,WebSocket就已经有了超过20个不同的Java实现。它们中的大多数都有着不同的API。JSR 356是把Java的WebSocket API进行标准化的成果。开发者们可以撇开具体的实现,直接使用JSR 356 API来创建WebSocket应用。WebSocket API是完全由事件驱动的。 | ![]() lwei
|
JSR 356 -- WebSockets的Java APIJSR 356,WebSocket的Java API,规定了开发者把WebSockets 整合进他们的应用时可以使用的Java API — 包括服务器端和Java客户端。JSR 356是即将出台的Java EE 7标准中的一部分。这意味着所有Java EE 7兼容的应用服务器都将有一个遵守JSR 356标准的WebSocket协议的实现。开发者也可以在Java EE 7应用服务器之外使用JSR 356。目前Apache Tomcat 8的开发版本将会增加基于JSR 356 API的WebSocket支持。 一个Java客户端可以使用兼容JSR 356的客户端实现,来连接到WebSocket服务器。对web客户端来说,开发者可以使用WebSocket JavaScript API来和WebSocket服务器进行通讯。WebSocket客户端和WebSocket服务器之间的区别,仅在于两者之间是通过什么方式连接起来的。一个WebSocket客户端是一个WebSocket终端,它初始化了一个到对方的连接。一个WebSocket服务器也是一个WebSocket终端,它被发布出去并且等待来自对方的连接。在客户端和服务器端都有回调监听方法 -- onOpen , onMessage , onError, onClose。后面我们创建一个应用的时候再来更详细的了解这些。 | ![]() lwei
|
Tyrus -- JSR 356 参考实现Tyrus是JSR 356的参考实现。我们会在下一节中以独立模式用Tyrus开发一个简单应用。所有Tyrus组件都是用Java SE 7编译器进行构建的。这意味着,你也至少需要不低于Java SE 7的运行环境才能编译和运行该应用示例。它不能够在Apache Tomcat 7中运行,因为它依赖于servlet 3.1规范。 使用WebSockets开发一个单词游戏现在我们准备创建一个非常简单的单词游戏。游戏者会得到一个字母排序错乱的单词,他或她需要把这个单词恢复原样。我们将为每一次游戏使用一个单独的连接。 本应用的源代码可以从github获取 https://github.com/shekhargulati/wordgame | ![]() lwei
|
步骤 1 : 创建一个模板Maven项目开始时,我们使用Maven原型来创建一个模板Java项目。使用下面的命令来创建一个基于Maven的Java项目。
步骤 2 : 向pom.xml中添加需要的依赖正如上节中提到的,你需要Java SE 7来构建使用Tyrus的应用。要在你的maven项目中使用Java 7,你需要在配置中添加maven编译器插件来使用Java 7,如下所示。
下面,添加对JSR 356 API的依赖。javax.websocket-api的当前版本是 1.0。
下面我们将要添加与Tyrus JSR 356实现相关的依赖。tyrus-server包提供了JSR 356服务端WebSocket API实现,tyrus-client包提供了JSR356客户端WebSocket API实现。
最后,我们添加tyrus-container-grizzly依赖到我们的pom.xml中。这将提供一个独立的容器来部署WebSocket应用。
你可以在这里查看完整的pom.xml文件。 | ![]() lwei
|
步骤 3 : 编写第一个JSR 356 WebSocket服务器终端现在我们的项目已经设置完毕,我们将开始编写WebSocket服务器终端。你可以通过使用@ServerEndpoint注解来把任何Java POJO类声明为WebSocket服务器终端。开发者也可以指定用来部署终端的URI。URI要相对于WebSocket容器的根路径,必须以"/"开头。在如下所示的代码中,我们创建了一个非常简单的WordgameServerEndpoint。
@OnOpen注解用来标注一个方法,在WebSocket连接被打开时它会被调用。每一个连接都有一个和它关联的session。在上面的代码中,当onOpen()方法被调用时我们打印了一下session的id。对每一个WebSocket连接来说,被@OnOpen标注的方法只会被调用一次。 @OnMessage注解用来标注一个方法,每当收到一个消息时它都会被调用。所有业务代码都需要写入该方法内。上面的代码中,当从客户端收到"quit"消息时我们会关闭连接,其它情况下我们只是把消息原封不动的返回给客户端。所以,在收到"quit"消息以前,一个WebSocket连接将会一直打开。当收到退出消息时,我们在session对象上调用了关闭方法,告诉它session关闭的原因。在示例代码中,我们说当游戏结束时这是一个正常的关闭。 @OnClose注解用来标注一个方法,当WebSocket连接关闭时它会被调用。 | ![]() lwei
|
步骤 4 : 编写第一个JSR 356 WebSocket客户端终端@ClientEndpoint注解用来标记一个POJO WebSocket客户端。类似于javax.websocket.server.ServerEndpoint,通过@ClientEndpoint标注的POJO能够使它的那些使用了网络套接字方法级别注解的方法,成为网络套接字生命周期方法。
在上面的代码中,当WebSocket 连接被打开时,我们发送了一个"start"消息给服务器。每当从服务器收到一个消息时,被@OnMessage注解标注的onMessage方法就会被调用。它首先记录下消息让后等待用户的输入。用户的输入随后会被发送给服务器。最后,当WebSocket 连接关闭时,用@OnClose标注的onClose()方法被被调用。正如你所看到的,客户单和服务器端的代码编程模式是相同的。这使得通过JSR 356 API来编写WebSocket应用的开发工作变得很容易。 | ![]() lwei
| ||
其它翻译版本(1) |

步骤 5: 创建并启动一个WebSocket服务器我们需要一个服务器来部署我们的WebSocket @ServerEndpoint。该服务器是使用如下所示的tyrus服务器API来创建的。它会运行在8025端口。WordgameServerEndpoint可以通过ws://localhost:8025/websockets/game来访问。
如果你在使用Eclipse,你可以通过把它作为一个Java application(ALT+SHIFT+X,J)来运行,来启动该服务器。你会看到如下所示的日志。
| ![]() lwei
|
步骤 6 : 启动WebSocket客户端现在服务器已经启动了并且WebSocket @ServerEndpoint也部署好了,我们准备把客户端作为一个Java应用来启动。我们会创建一个ClientManager实例,然后连接到服务器上的endpoint,如下所示。
我们使用CountDownLatch来确保在代码执行之后,主线程将不再存在。当时间锁去减少onClose()方法里的计数器时,主线程会保持等待。然后程序结束。在main()方法里,我们创建了ClientManager实例,它被用来连接位于ws://localhost:8025/websockets/game的@ServerEndpoint。 把客户端作为一个Java应用来运行(ALT + SHIFT + X , J),你会看到下列日志信息。
随意发送消息比如 "hello world",它会被重复并返回给你,如下所示。
发送"quit"消息,WebSocket连接就会被关闭。
| ![]() lwei
|
步骤 7 : 添加游戏逻辑现在我们准备添加游戏逻辑,发送一个排序错乱的单词给客户端,然后当从客户端接收到一个恢复后单词时,检查该单词是否正确。像下面这样改进WordgameServerEndpoint的代码。
重启服务器和客户端,享受游戏吧。 结论在本文中,我们看到了JSR 356 WebSocket API是如何帮助我们构建实时的全双工的Java应用程序的。JSR 356 WebSocket API非常简单,并且基于注解的开发模式使得构建WebSocket应用非常容易。在下篇博文中,我们会看一下Undertow,一个来自JBoss的Java编写的灵活的高性能web服务器。 |