使用 JSR 356 API 构建 Java WebSocket 应用 已翻译 100%

oschina 投递于 2013/08/08 08:14 (共 13 段, 翻译完成于 08-08)
阅读 9860
收藏 12
3
加载中

大家都知道这样一个事实,那就是HTTP(Hypertext Transfer Protocol)是一个无状态的请求-响应式协议。HTTP协议的这种简单设计使它颇具扩展性却不够高效,并且不适合于频繁交互的实时网络应用。HTTP被设计用来进行文档共享而不是用来建立频繁交互的网络应用。HTTP天生就不太正规,对每一个http请求/响应,都要通过线路传输许多头信息。

在HTTP 1.1版本之前,每一个提交到服务器的请求都会创建一个新的链接。这种情况在HTTP 1.1中通过引入HTTP持久化连接得以改进。持久化连接允许web浏览器复用同样的连接来获取图片,脚本等等。

HTTP被设计成半双工的,这意味着同一时刻只允许向一个方向上传输数据。Walkie-talkie是一个半双工设施的例子,因为一个时刻只能有一个人说话。开发者们已经创造出了一些工作方法或者应对方法来克服HTTP的这个缺点。这些工作方法包括轮询,长效轮询和

lwei
翻译于 2013/08/08 15:33
2

使用轮询时,客户端采用同步调用来从服务器获取信息。如果服务器有新信息,它会在响应中返回数据。否则,就不会有信息返回给客户端,然后客户端会在一段时间之后再次创建一个新的连接。这是一种很低效却很简单的获得实时行为的方式。长效轮询是另一种工作方法,客户端会对服务器发起一个连接,服务器会保持该连接直到有可用数据或者超过了指定超期时间。长效轮询也被称为comet。由于同步的HTTP和这些异步应用之间的不匹配,这些方案易于复杂化,无标准化和低效化。

随着时间的推移,对于在客户端和服务器之间建立基于标准的、双向的、全双工通道的需求,已经增长了。在本文中,我们会看到WebSockets是如何帮助解决这些问题的,然后了解一下如何在Java中使用JSR 356 API来构建基于WebSocket的应用。

请注意本文不会讨论到OpenShift WebSocket支持。如果你想了解OpenShift WebSocket支持,请参考Marek Jelen这篇文章

lwei
翻译于 2013/08/08 16:07
1

什么是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.

WebSocket browser support

lwei
翻译于 2013/08/08 16:48
1

WebSocket是如何工作的?

每一个WebSocket连接的生命都是从一个HTTP请求开始的。HTTP请求跟其他请求很类似,除了它拥有一个Upgrade头信息。Upgrade头信息表示一个客户端希望把连接升级为不同的协议。对WebSockets来说,它希望升级为WebSocket协议。当客户端和服务器通过底层连接第一次握手时,WebSocket连接通过把HTTP协议转换升级为WebSockets协议而得以建立。一旦WebSocket连接成功建立,消息就可以在客户端和服务器之间进行双向发送。

WebSockets带来了性能,简单化和更少带宽消耗

  1. WebSockets比其它工作方式比如轮询更有效也更高效。因为它需要更少的带宽并且降低了延时。
  2. WebSockets简化了实时应用的结构体系。
  3. WebSockets在点到点发送消息时不需要头信息。这显著的降低了带宽。

WebSocket使用案例

一些可能的WebSockets使用案例有:

  • 聊天应用
  • 多人游戏
  • 股票交易和金融应用
  • 文档合作编辑
  • 社交应用
lwei
翻译于 2013/08/08 14:49
1

Java中使用WebSockets

在Java社区中下面的情形很普遍,不同的供应商和开发者编写类库来使用某项技术,一段时间之后当该技术成熟时它就会被标准化,来使开发者可以在不同实现之间互相操作,而不用冒供应商锁定的风险。当JSR 365启动时,WebSocket就已经有了超过20个不同的Java实现。它们中的大多数都有着不同的API。JSR 356是把Java的WebSocket API进行标准化的成果。开发者们可以撇开具体的实现,直接使用JSR 356 API来创建WebSocket应用。WebSocket API是完全由事件驱动的。

lwei
翻译于 2013/08/08 09:21
1

JSR 356 -- WebSockets的Java API

JSR 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
翻译于 2013/08/08 17:15
1

Tyrus -- JSR 356 参考实现

TyrusJSR 356的参考实现。我们会在下一节中以独立模式用Tyrus开发一个简单应用。所有Tyrus组件都是用Java SE 7编译器进行构建的。这意味着,你也至少需要不低于Java SE 7的运行环境才能编译和运行该应用示例。它不能够在Apache Tomcat 7中运行,因为它依赖于servlet 3.1规范。

使用WebSockets开发一个单词游戏

现在我们准备创建一个非常简单的单词游戏。游戏者会得到一个字母排序错乱的单词,他或她需要把这个单词恢复原样。我们将为每一次游戏使用一个单独的连接。

本应用的源代码可以从github获取 https://github.com/shekhargulati/wordgame

lwei
翻译于 2013/08/08 10:23
1

步骤 1 : 创建一个模板Maven项目

开始时,我们使用Maven原型来创建一个模板Java项目。使用下面的命令来创建一个基于Maven的Java项目。

$ mvn archetype:generate -DgroupId=com.shekhar -DartifactId=wordgame -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

步骤 2 : 向pom.xml中添加需要的依赖

正如上节中提到的,你需要Java SE 7来构建使用Tyrus的应用。要在你的maven项目中使用Java 7,你需要在配置中添加maven编译器插件来使用Java 7,如下所示。

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.1</version>
            <configuration>
                <compilerVersion>1.7</compilerVersion>
                <source>1.7</source>
                <target>1.7</target>
            </configuration>
        </plugin>
    </plugins>
</build>

下面,添加对JSR 356 API的依赖。javax.websocket-api的当前版本是 1.0。

<dependency>
    <groupId>javax.websocket</groupId>
    <artifactId>javax.websocket-api</artifactId>
    <version>1.0</version>
</dependency>

下面我们将要添加与Tyrus JSR 356实现相关的依赖。tyrus-server包提供了JSR 356服务端WebSocket API实现,tyrus-client包提供了JSR356客户端WebSocket API实现。

<dependency>
    <groupId>org.glassfish.tyrus</groupId>
    <artifactId>tyrus-server</artifactId>
    <version>1.1</version>
</dependency>
<dependency>
    <groupId>org.glassfish.tyrus</groupId>
    <artifactId>tyrus-client</artifactId>
    <version>1.1</version>
</dependency>

最后,我们添加tyrus-container-grizzly依赖到我们的pom.xml中。这将提供一个独立的容器来部署WebSocket应用。

<dependency>
    <groupId>org.glassfish.tyrus</groupId>
    <artifactId>tyrus-container-grizzly</artifactId>
    <version>1.1</version>
</dependency>

你可以在这里查看完整的pom.xml文件。

lwei
翻译于 2013/08/08 10:44
1

步骤 3 : 编写第一个JSR 356 WebSocket服务器终端

现在我们的项目已经设置完毕,我们将开始编写WebSocket服务器终端。你可以通过使用@ServerEndpoint注解来把任何Java POJO类声明为WebSocket服务器终端。开发者也可以指定用来部署终端的URI。URI要相对于WebSocket容器的根路径,必须以"/"开头。在如下所示的代码中,我们创建了一个非常简单的WordgameServerEndpoint。

package com.shekhar.wordgame.server;
 
import java.io.IOException;
import java.util.logging.Logger;
 
import javax.websocket.CloseReason;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.CloseReason.CloseCodes;
import javax.websocket.server.ServerEndpoint;
 
@ServerEndpoint(value = "/game")
public class WordgameServerEndpoint {
 
    private Logger logger = Logger.getLogger(this.getClass().getName());
 
    @OnOpen
    public void onOpen(Session session) {
        logger.info("Connected ... " + session.getId());
    }
 
    @OnMessage
    public String onMessage(String message, Session session) {
        switch (message) {
        case "quit":
            try {
                session.close(new CloseReason(CloseCodes.NORMAL_CLOSURE, "Game ended"));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            break;
        }
        return message;
    }
 
    @OnClose
    public void onClose(Session session, CloseReason closeReason) {
        logger.info(String.format("Session %s closed because of %s", session.getId(), closeReason));
    }
}

@OnOpen注解用来标注一个方法,在WebSocket连接被打开时它会被调用。每一个连接都有一个和它关联的session。在上面的代码中,当onOpen()方法被调用时我们打印了一下session的id。对每一个WebSocket连接来说,被@OnOpen标注的方法只会被调用一次。

@OnMessage注解用来标注一个方法,每当收到一个消息时它都会被调用。所有业务代码都需要写入该方法内。上面的代码中,当从客户端收到"quit"消息时我们会关闭连接,其它情况下我们只是把消息原封不动的返回给客户端。所以,在收到"quit"消息以前,一个WebSocket连接将会一直打开。当收到退出消息时,我们在session对象上调用了关闭方法,告诉它session的原因。在示例代码中,我们说当游戏结束时这是一个正常的关闭。

@OnClose注解用来标注一个方法,当WebSocket连接关闭时它会被调用。

lwei
翻译于 2013/08/08 17:54
2

步骤 4 : 编写第一个JSR 356 WebSocket客户端终端

@ClientEndpoint注解用来标记一个POJO WebSocket客户端。类似于javax.websocket.server.ServerEndpoint,通过@ClientEndpoint标注的POJO能够使它的那些使用了网络套接字方法级别注解的方法,成为网络套接字生命周期方法。

package com.shekhar.wordgame.client;
 
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.CountDownLatch;
import java.util.logging.Logger;
 
import javax.websocket.ClientEndpoint;
import javax.websocket.CloseReason;
import javax.websocket.DeploymentException;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
 
import org.glassfish.tyrus.client.ClientManager;
 
@ClientEndpoint
public class WordgameClientEndpoint {
 
    private Logger logger = Logger.getLogger(this.getClass().getName());
 
    @OnOpen
    public void onOpen(Session session) {
        logger.info("Connected ... " + session.getId());
        try {
            session.getBasicRemote().sendText("start");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
 
    @OnMessage
    public String onMessage(String message, Session session) {
        BufferedReader bufferRead = new BufferedReader(new InputStreamReader(System.in));
        try {
            logger.info("Received ...." + message);
            String userInput = bufferRead.readLine();
            return userInput;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
 
    @OnClose
    public void onClose(Session session, CloseReason closeReason) {
        logger.info(String.format("Session %s close because of %s", session.getId(), closeReason));
    }
 
 
}

在上面的代码中,当WebSocket 连接被打开时,我们发送了一个"start"消息给服务器。每当从服务器收到一个消息时,被@OnMessage注解标注的onMessage方法就会被调用。它首先记录下消息让后等待用户的输入。用户的输入随后会被发送给服务器。最后,当WebSocket 连接关闭时,@OnClose标注的onClose()方法被被调用。正如你所看到的,客户单和服务器端的代码编程模式是相同的。这使得通过JSR 356 API来编写WebSocket应用的开发工作变得很容易。

lwei
翻译于 2013/08/08 11:14
1
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(3)

qwfys
qwfys
ok
g
golastroom
We used CountDownLatch to make sure that main thread does not exit after executing the code.

我们使用CountDownLatch来确保在代码执行之后,主线程将不再存在。

这句翻译是不是有点问题?
我小叶
我小叶
。。。
返回顶部
顶部
返回顶部
顶部