一、概述

WebSocket是一种通过HTTP发起的双向全双工通信协议,通常用于现代Web应用程序中的流数据和其他异步流量。

WebSocket在客户端和服务器之间提供长期的双向通信通道,通道建立后将保持打开状态,以低延迟和低开销提供非常快速的连接。

  • 浏览器支持

所有现代浏览器都支持WebSocket。

  • WebSockets与HTTP的区别

Web浏览器和网站之间的大多数通信都使用HTTP:客户端发送请求,服务器返回响应。通常响应会立即发生,并且事务已完成。HTTP适合客户端发起的偶尔数据交换和交互。

WebSocket连接是通过HTTP发起的,通常具有较长的生存期;消息可以随时向另一方发送,也就是说服务器也可以向客户端发送消息,而无需客户端的请求。WebSocket本质上不是事务性的;另外WebSocket发送消息的数据开销非常小,适合实时和长期通信。

  • 安全的WebSocket

建议使用安全、加密的WebSocket协议:wss://ws://类似于http://,是不安全的。

二、JavaScript WebSocket API

1、简介

WebSocket对象提供了用于创建和管理WebSocket连接、以及可以通过该连接发送和接收数据的API。

  • 构造函数
WebSocket(url[, protocols])

通过构造函数创建一个WebSocket对象。

const socket = new WebSocket('wss://myserver.com/something');
  • 实例属性

    • WebSocket.binaryType

      连接使用的二进制数据类型。

    • WebSocket.bufferedAmount

      队列字节数,只读。

    • WebSocket.extensions

      服务器选择的扩展名,只读。

    • WebSocket.protocol

      服务器选择的子协议,只读。

    • WebSocket.readyState

      连接的当前状态,只读。

    • WebSocket.url

      WebSocket的绝对URL,只读。

  • 实例方法

    • WebSocket.close()

      关闭连接。

    • WebSocket.send()

      将要传输的数据排入队列。

  • 事件

    可以使用addEventListener()或通过onxxx属性来添加事件监听。

      socket.addEventListener("open", function (event) {
          //...
      });
      //或
      socket.onopen = function(event){
          //...
      };
    
    • close

      WebSocket连接关闭时触发。

    • error

      WebSocket连接因错误而关闭时触发,例如无法发送某些数据时。

    • message

      当通过WebSocket收到数据时触发。

    • open

      WebSocket连接成功时触发。

2、样例

  • 服务端

    使用Node.js创建WebSocket服务:

    • 初始化

      初始化工程并安装ws模块:

        npm init
        npm install ws
      
    • 服务端代码

      在根目录创建server.js文件,内容如下:

        const WebSocket = require('ws');
      
        const wss = new WebSocket.Server({ port: 8080 });
      
        wss.on('connection', (ws) => {
            ws.on('message', (message) => {
                console.log(`Received message => ${message}`)
            })
            ws.send('ho!')
        });
      

      目录结构:

  • 客户端

<!DOCTYPE HTML>
<html>
	<head>
		<title>WebSocket</title>
		<script>
			// Create WebSocket connection.
			const socket = new WebSocket("ws://localhost:8080");

			// Connection opened
			socket.addEventListener("open", function (event) {
				socket.send("Hello Server!");
			});

			// Listen for messages
			socket.addEventListener("message", function (event) {
				console.log("Message from server ", event.data);
			});

			socket.addEventListener("close", function (event) {
				console.log("goodbye");
			});
		</script>
	</head>
	<body>
		
	</body>
</html>
  • 运行

启动服务端:

node server.js

打开客户端页面,在控制台可以看到输出:

Message from server  ho!

在服务端可以看到:

Received message => Hello Server!

客户端可以使用send方法发送消息:

socket.send("haha");

服务端输出:

Received message => haha

关闭服务端,客户端输出:

goodbye

三、Java WebSocket API

Java API提供服务器端和客户端组件:Server端相关代码在javax.websocket.server包中,Client端相关代码由javax.websocket包中的客户端API以及服务器和客户端的公共库组成。

1、Endpoint

Endpoint表示一个可访问和使用的WebService服务的终端节点。有两种配置方式:基于注解或基于扩展。使用扩展方式需要继承javax.websocket.Endpoint类,由于注解方式的代码更简洁,因此通常使用注解方式。

WebSocket端点注解和生命周期事件相关注解如下:

  • @ServerEndpoint

使用此注解修饰的类被视为WebSocket服务端,容器将确保类作为监听特定URI空间的WebSocket服务器的可用性。

  • @ClientEndpoint

使用此注解修饰的类被视为WebSocket客户端。

  • @OnOpen

新的WebSocket连接时,容器会调用具有@OnOpen注解的Java方法。

  • @OnMessage

当消息发送到端点时,带有@OnMessage注解的Java方法可以从WebSocket容器接收信息。

  • @OnError

当通信出现问题时,将调用具有@OnError注解的方法。

  • @OnClose

WebSocket连接关闭时容器调用具有@OnClose注解的Java方法。

  • Encoder/Decoder

编码器可以把Java对象处理成适合消息传输的形式,例如:JSON、XML 或二进制表示形式。可以通过实现Encoder.TextEncoder.Binary接口来使用编码器。

public class Message {

	private String from;
	private String to;
	private String content;
	
	public String getFrom() {
		return from;
	}
	public void setFrom(String from) {
		this.from = from;
	}
	public String getTo() {
		return to;
	}
	public void setTo(String to) {
		this.to = to;
	}
	public String getContent() {
		return content;
	}
	public void setContent(String content) {
		this.content = content;
	}
	
}
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfig;

public class MessageEncoder implements Encoder.Text<Message>{

	public void init(EndpointConfig config) {
		
	}

	public String encode(Message object) throws EncodeException {
		return null;
	}

	public void destroy() {
		
	}
}

解码器与编码器相反,可以将数据转换回Java对象。可以通过实现Decoder.TextDecoder.Binary接口实现解码器。

import javax.websocket.DecodeException;
import javax.websocket.Decoder;
import javax.websocket.EndpointConfig;

public class MessageDecoder implements Decoder.Text<Message>{

	public void init(EndpointConfig config) {
		
	}

	public boolean willDecode(String s) {
		return false;
	}
	
	public Message decode(String s) throws DecodeException {
		return null;
	}

	public void destroy() {
		
	}
}

解码器与编码器需要在Endpoint中配置:

@ServerEndpoint( 
	value="/chat/{username}", 
	decoders = MessageDecoder.class, 
	encoders = MessageEncoder.class )

2、样例

  • 服务端

依赖:

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

服务端程序:

import java.io.IOException;
import java.net.URI;

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

@ServerEndpoint("/hello")
public class HelloServer {

	@OnOpen
	public void onOpen(Session session) throws IOException {
		URI uri = session.getRequestURI();
		System.out.println("Get session and WebSocket connection : " + uri);
	}

	@OnMessage
	public void processGreeting(String message, Session session) {
		System.out.println("Greeting received:" + message);
		try {
			session.getBasicRemote().sendText("hi");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@OnClose
	public void onClose(Session session) throws IOException {
		System.out.println("WebSocket connection closes");
	}

	@OnError
	public void onError(Session session, Throwable throwable) {
		throwable.printStackTrace();
	}
}

服务端程序部署到tomcat的web项目中,并启动tomcat。

  • 客户端

依赖:

<dependency>
	<groupId>javax.websocket</groupId>
	<artifactId>javax.websocket-api</artifactId>
	<version>1.1</version>
</dependency>
<dependency>
	 <groupId>org.glassfish.tyrus.bundles</groupId>
	 <artifactId>tyrus-standalone-client</artifactId>
	 <version>1.9</version>
</dependency>

除了依赖javax.websocket-api外还需要依赖实现,否则运行客户端程序时会提示找不到实现类。

客户端Endpoint:

import java.io.IOException;

import javax.websocket.ClientEndpoint;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;

@ClientEndpoint
public class MyClientEndpoint {

	@OnOpen
	public void onOpen(Session session) {
		System.out.println("Connected to endpoint: " + session.getBasicRemote());
		try {
			session.getBasicRemote().sendText("hello");
		} catch (IOException ex) {
			ex.printStackTrace();
		}
	}

	@OnMessage
	public void processMessage(String message) {
		System.out.println("Received message in client: " + message);
	}

	@OnError
	public void processError(Throwable t) {
		t.printStackTrace();
	}
}

客户端程序:

import java.net.URI;

import javax.websocket.ContainerProvider;
import javax.websocket.WebSocketContainer;

public class ClientApp {

	public static void main(String[] args) {
		try {
			WebSocketContainer container = ContainerProvider.getWebSocketContainer();
			String uri = "ws://localhost:8080/myweb/hello";
			System.out.println("Connecting to " + uri);
			container.connectToServer(MyClientEndpoint.class, URI.create(uri));
			Thread.sleep(5000);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
  • 运行效果

客户端运行后,输出:

Connecting to ws://localhost:8080/myweb/hello
Connected to endpoint: Wrapped: Basic

Received message in client: hi

服务端输出:

Get session and WebSocket connection : ws://localhost:8080/myweb/hello
Greeting received:hello

java.io.IOException: 远程主机强迫关闭了一个现有的连接。
WebSocket connection closes

建立连接后,客户端、服务端分别打印连接信息,服务端打印收到的客户端信息hello,同时发送信息给客户端,客户端打印收到的服务端信息hi,5秒后客户端程序终止,服务端进入onError方法打印异常信息,随后执行onClose方法。

参考资料:

Introduction to WebSockets

WebSocket

What are WebSockets

A Guide to the Java API for WebSocket

Apache Tomcat Websocket Tutorial

java - javax.websocket client simple example - Stack Overflow

Chapter 4. WebSocket API Endpoints, Sessions and MessageHandlers

Could not find an implementation class