WebSocket 简介
一、概述
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.Text
或Encoder.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.Text
或Decoder.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方法。
参考资料:
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