加载中

In this tutorial we will implement an HTML5 websocket resorting to the Java EE websocket implementation (ServerEndpoint).

1. Introduction

The HTML5 specification brought the capability for web browsers to establish full-duplex TCP connections with websocket compliant servers.

In other words, browsers are now able to establish a connection with a server and keep sending or receiving data through that same established communication channel without the need of the overhead introduced by the HTTP protocol itself.

In this tutorial we will implement a simple websocket server endpoint in a Java EE environment and also the respective client-side infrastructure for sending and receiving data.

This tutorial considers the following environment:

  1. Ubuntu 12.04
  2. JDK 1.7.0.21
  3. Glassfish 4.0
Note: WebSockets support was introduced in Java EE 7

在本教程中,我们将借助于Java EE的WebSocket(服务器端)实现一个HTML5的WebSocket连接。

1. 介绍

HTML5给Web浏览器带来了全双工TCP连接websocket标准服务器的能力。

换句话说,浏览器能够与服务器建立连接,通过已建立的通信信道来发送和接收数据而不需要由HTTP协议引入额外其他的开销来实现。

在本教程中我们将在Java EE环境下实现一个简单的websockect服务器端来和客户端进行数据交互。

本教程需要以下环境:

  1. Ubuntu 12.04
  2. JDK 1.7.0.21
  3. Glassfish 4.0
: Java EE 7中才引入了WebSocket。
 

2. WebSocket server endpoint

Let's define a Java EE websocket server endpoint:

WebSocketTest.java
package com.byteslounge.websockets;

import java.io.IOException;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint("/websocket")
public class WebSocketTest {

  @OnMessage
  public void onMessage(String message, Session session) 
    throws IOException, InterruptedException {
  
    // Print the client message for testing purposes
    System.out.println("Received: " + message);
  
    // Send the first message to the client
    session.getBasicRemote().sendText("This is the first server message");
  
    // Send 3 messages to the client every 5 seconds
    int sentMessages = 0;
    while(sentMessages < 3){
      Thread.sleep(5000);
      session.getBasicRemote().
        sendText("This is an intermediate server message. Count: " 
          + sentMessages);
      sentMessages++;
    }
  
    // Send a final message to the client
    session.getBasicRemote().sendText("This is the last server message");
  }
  
  @OnOpen
  public void onOpen() {
    System.out.println("Client connected");
  }

  @OnClose
  public void onClose() {
    System.out.println("Connection closed");
  }
}
As you may have already noticed we are importing several classes from javax.websocket package.

@ServerEndpoint annotation is used at type level and defines the current class as a websocket server endpoint. The value used in this annotation represents the URL where the endpoint will be listening for client connections.

onOpen and onClose methods are annotated with @OnOpen and @OnClose respectively. These annotations are almost self-explanatory: They define which methods will be called upon a new client connection and disconnection.

Method onMessage is annotated with @OnMessage. This annotation defines which method will be called when a new message is received from the client. Note that this method may be defined with an optional parameter of type javax.websocket.Session (in our case the session parameter). If this parameter is defined the container will inject the session that is associated with the current client that sent the message being handled.

In our case we are just writing the client message content to the standard output. Then we proceed to send a message to the client followed by 3 test messages with a 5 second interval. Finally we send a final message to the client.

2. WebSocket服务器端

让我们定义一个 Java EE websocket服务器端:

WebSocketTest.java

package com.byteslounge.websockets;

import java.io.IOException;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint("/websocket")
public class WebSocketTest {

  @OnMessage
  public void onMessage(String message, Session session) 
    throws IOException, InterruptedException {
  
    // Print the client message for testing purposes
    System.out.println("Received: " + message);
  
    // Send the first message to the client
    session.getBasicRemote().sendText("This is the first server message");
  
    // Send 3 messages to the client every 5 seconds
    int sentMessages = 0;
    while(sentMessages < 3){
      Thread.sleep(5000);
      session.getBasicRemote().
        sendText("This is an intermediate server message. Count: " 
          + sentMessages);
      sentMessages++;
    }
  
    // Send a final message to the client
    session.getBasicRemote().sendText("This is the last server message");
  }
  
  @OnOpen
  public void onOpen() {
    System.out.println("Client connected");
  }

  @OnClose
  public void onClose() {
    System.out.println("Connection closed");
  }
}
你可能已经注意到我们从 javax.websocket包中引入了一些类。

@ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端。注解的值将被用于监听用户连接的终端访问URL地址。

onOpenonClose 方法分别被@OnOpen@OnClose 所注解。这两个注解的作用不言自明:他们定义了当一个新用户连接和断开的时候所调用的方法。

onMessage 方法被@OnMessage所注解。这个注解定义了当服务器接收到客户端发送的消息时所调用的方法。注意:这个方法可能包含一个javax.websocket.Session可选参数(在我们的例子里就是session参数)。如果有这个参数,容器将会把当前发送消息客户端的连接Session注入进去。

本例中我们仅仅是将客户端消息内容打印出来,然后首先我们将发送一条开始消息,之后间隔5秒向客户端发送1条测试消息,共发送3次,最后向客户端发送最后一条结束消息。

3. Client side

Now we need to write the client-side of our websocket test application:

page.html
<!DOCTYPE html>
<html>
<head>
<title>Testing websockets</title>
</head>
<body>
  <div>
    <input type="submit" value="Start" onclick="start()" />
  </div>
  <div id="messages"></div>
  <script type="text/javascript">
    var webSocket = 
      new WebSocket('ws://localhost:8080/byteslounge/websocket');

    webSocket.onerror = function(event) {
      onError(event)
    };

    webSocket.onopen = function(event) {
      onOpen(event)
    };

    webSocket.onmessage = function(event) {
      onMessage(event)
    };

    function onMessage(event) {
      document.getElementById('messages').innerHTML 
        += '<br />' + event.data;
    }

    function onOpen(event) {
      document.getElementById('messages').innerHTML 
        = 'Connection established';
    }

    function onError(event) {
      alert(event.data);
    }

    function start() {
      webSocket.send('hello');
      return false;
    }
  </script>
</body>
</html>
This is a simple test page that contains the JavaScript that will create a websocket connection to out websocket server endpoint.

onOpen method will be called when we establish a connection with the server endpoint.

onError method is called when an error occurs during client-server communication.

onMessage method will be called when a message is received from the server. In our case we are just appending the messages received from the server to the DOM.

We connect to the websocket server endpoint by using the construct new WebSocket() and passing the endpoint URL:

ws://localhost:8080/byteslounge/websocket

3. 客户端

现在我们要来写websocket测试应用的客户端:

page.html

<!DOCTYPE html>
<html>
<head>
<title>Testing websockets</title>
</head>
<body>
  <div>
    <input type="submit" value="Start" onclick="start()" />
  </div>
  <div id="messages"></div>
  <script type="text/javascript">
    var webSocket = 
      new WebSocket('ws://localhost:8080/byteslounge/websocket');

    webSocket.onerror = function(event) {
      onError(event)
    };

    webSocket.onopen = function(event) {
      onOpen(event)
    };

    webSocket.onmessage = function(event) {
      onMessage(event)
    };

    function onMessage(event) {
      document.getElementById('messages').innerHTML 
        += '<br />' + event.data;
    }

    function onOpen(event) {
      document.getElementById('messages').innerHTML 
        = 'Connection established';
    }

    function onError(event) {
      alert(event.data);
    }

    function start() {
      webSocket.send('hello');
      return false;
    }
  </script>
</body>
</html>

这是一个简单的页面,包含有JavaScript代码,这些代码创建了一个websocket连接到websocket服务器端。

onOpen 我们创建一个连接到服务器的连接时将会调用此方法。

onError 当客户端-服务器通信发生错误时将会调用此方法。

onMessage 当从服务器接收到一个消息时将会调用此方法。在我们的例子中,我们只是将从服务器获得的消息添加到DOM。

我们连接到websocket 服务器端,使用构造函数 new WebSocket() 而且传之以端点URL:

ws://localhost:8080/byteslounge/websocket

4. Testing

We may now test our application by accessing the testing page:

http://localhost:8080/byteslounge/page.html

We will see the Connection established message as expected:

http://localhost:8080/byteslounge/page.html

Now as soon as we press the button we will send the initial message to the server through the websocket and receive the subsequent test messages sent by the server:

Messages sent by the server and received by the client

4. 测试

现在我们可以访问测试页面对我们的应用进行测试:

http://localhost:8080/byteslounge/page.html

正如所期望的,我们将看到 Connection established 消息:

http://localhost:8080/byteslounge/page.html

现在只要我们一按按钮,就会通过此websocket发送初始化报文给服务器,而且接下来会收到发自服务器的测试消息:

服务器发送、客户端接收的消息

5. WebSockets Handshake

The TCP connection between the client and the server is established after the occurrence of a handshake over the HTTP protocol. It's easy to observe the handshake by using some HTTP traffic debugger. As soon as we create the WebSocket instance in the client the following request and respective server response will occur:

Note: we will only include HTTP headers that are relevant to the websockets handshake

Request:

GET /byteslounge/websocket HTTP/1.1
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key: wVlUJ/tu9g6EBZEh51iDvQ==

Response:

HTTP/1.1 101 Web Socket Protocol Handshake
Upgrade: websocket
Sec-WebSocket-Accept: 2TNh+0h5gTX019lci6mnvS66PSY=

Note that the client is requesting the protocol to be upgraded to the WebSocket protocol by using Connection: Upgrade and Upgrade: websocket HTTP headers. The server response states that the client request was accepted and it will change protocol to WebSocket (using HTTP status code 101):

HTTP/1.1 101 Web Socket Protocol Handshake

6. Downloadable sample

The example source code is available for download at the end of this page. The test was executed in Glassfish 4 (you will need a Java EE 7 compliant application server).

Download source code from this tutorial

5. WebSockets 握手

客户端和服务器端TCP连接建立在HTTP协议握手发生之后。通过HTTP流量调试,很容易观察到握手。客户端一创建一个 WebSocket实例,就会出现如下请求和服务器端响应:

注意: 我们只录入了WebSocket握手所用到的HTTP头。

请求:

GET /byteslounge/websocket HTTP/1.1
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key: wVlUJ/tu9g6EBZEh51iDvQ==

响应:

HTTP/1.1 101 Web Socket Protocol Handshake
Upgrade: websocket
Sec-WebSocket-Accept: 2TNh+0h5gTX019lci6mnvS66PSY=

注意:进行连接需要将通过Upgrade and Upgrade将协议升级到支持websocket HTTP头的Websocket协议。服务器响应表明请求被接受,协议将转换到WebSocket协议(HTTP状态码101):

HTTP/1.1 101 Web Socket Protocol Handshake

6. 下载源码

在本页的末尾有范例源代码下载链接,源码在Glassfish 4(需要兼容Java EE 7的服务器)上通过测试。
在如下地址下载范例源码:
返回顶部
顶部