WebSocket

传统的HTTP协议只能以请求/响应的方式进行浏览器和服务器的通信。WebSocket是HTML5规范定义的新功能,它能实现浏览器和服务器之间的全双工通信,这种通信方式非常适合聊天室、游戏等即时应用。JavaEE7规范提供了JSR-356 WebSocket API,这组API定义在javax.websocket.*下,我们可以在JavaEE7兼容的应用服务器上编写和运行WebSocket服务端程序,此外尽管JSR-356实际上不属于Servlet规范不过在Tomcat中也对其进行了支持。

引入Maven依赖

为了使用WebSocketAPI,我们首先要添加JavaEE SDK的Maven依赖。

<dependency>
    <groupId>javax</groupId>
    <artifactId>javaee-api</artifactId>
    <version>8.0.1</version>
    <scope>provided</scope>
</dependency>

不过如果你仅使用WebSocket API,我们也可以单独引入javax.websocket-api这个依赖。

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

WebSocket程序编写

下面我们编写一个包含前端和后端的简单例子,演示WebSocket API的使用。

服务端

在服务端,我们需要使用@ServerEndpoint注解来标注一个WebSocket端点,它是value属性是WebSocket端点的路径。

package com.gacfox.demo.demoweb;

import javax.websocket.Session;

import java.io.IOException;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;

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

@ServerEndpoint(value = "/chat")
public class WebSocketEndpoint {
    private static final String GUEST_PREFIX = "Guest";
    private static final AtomicInteger connectionIds = new AtomicInteger(0);
    private static final Set<WebSocketEndpoint> connections = new CopyOnWriteArraySet<>();
    private final String nickname;
    private Session session;

    public WebSocketEndpoint() {
        nickname = GUEST_PREFIX + connectionIds.getAndIncrement();
    }

    @OnOpen
    public void start(Session session) {
        this.session = session;
        connections.add(this);
        String message = String.format("* %s %s", nickname, "has joined.");
        broadcast(message);
    }

    @OnClose
    public void end() {
        connections.remove(this);
        String message = String.format("* %s %s", nickname, "has disconnected.");
        broadcast(message);
    }

    @OnMessage
    public void incoming(String message) {
        broadcast(message);
    }

    @OnError
    public void onError(Throwable t) throws Throwable {
        System.out.println("Chat Error: " + t.toString());
    }

    private static void broadcast(String msg) {
        for (WebSocketEndpoint client : connections) {
            try {
                client.session.getBasicRemote().sendText(msg);
            } catch (IOException e) {
                System.out.println("Chat Error: Failed to send message to client");
                connections.remove(client);
                try {
                    client.session.close();
                } catch (IOException ignore) {
                }
                String message = String.format("* %s %s", client.nickname, "has been disconnected.");
                broadcast(message);
            }
        }
    }
}

代码中,我们用注解标注了四个方法,分别在用户连接WebSocket,关闭WebSocket,发消息和出错时回调。这里要注意的是该类WebSocketEndpoint是多例的,也就是一个请求会创建一个实例并在新的线程中运行对应部分的逻辑,所以我们需要使用static修饰的connections来记录所有的连接。

客户端

客户端包含一个JSP页面和一个JavaScript文件,这部分我们可以参考HTML5中WebSocket相关的章节。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="zh-CH">
<head>
    <meta charset="UTF-8">
    <title>WebSocket Example</title>
    <script src="resources/app.js"></script>
</head>
<body>
<div>
    <input type="text" placeholder="输入聊天信息" id="chat-input"/>
    <div id="chat-box"></div>
</div>
</body>
</html>
var Chat = {};

window.onload = function () {
    Chat.socket = new WebSocket('ws://localhost:8080/demoweb/chat');
    Chat.chatbox = document.getElementById('chat-box');

    Chat.showMessage = function (msg) {
        var p = document.createElement('p');
        p.innerText = msg;
        this.chatbox.appendChild(p);
    };

    Chat.sendMessage = function (msg) {
        Chat.socket.send(msg);
    };

    Chat.socket.onopen = function () {
        Chat.showMessage('WebSocket open');
    };

    Chat.socket.onclose = function () {
        Chat.showMessage('WebSocket closed');
    };

    Chat.socket.onmessage = function (sock_msg) {
        Chat.showMessage(sock_msg.data);
    };

    document.getElementById('chat-input').addEventListener('keyup', function (event) {
        if (event.keyCode === 13) {
            var msg = document.getElementById('chat-input').value;
            Chat.sendMessage(msg);
        }
    });
};

运行结果如下图。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap