参考文章:
WebSocket 是 HTML5 开始提供的一种浏览器与服务器间进行 全双工通讯 的网络技术,使得服务器可以主动向浏览器发送信息。依靠这种技术可以 实现客户端和服务器端的长连接,双向实时通信。
WebSocket 的特点:
- 事件驱动
- 异步
- 使用
ws
或者wss
协议的客户端 - 能够实现真正意义上的推送功能
1 浏览器端
1.1 WebSocket 对象
在 Javascrpt 中可以使用 new WebSocket(url)
来创建一个 WebSocket 对象, 它能使浏览器与服务器建立连接。
其中参数 url
是 ws
或 wss
协议的,如:
const socket = new WebSocket("ws://localhost:8080/websocket/commodity/%7B2%7D/%7B3%7D");
1.1.1 WebSocket 对象的方法
WebSocket 对象有两个方法,分别是:send()
以及 close()
。
send(data)
方法向服务端发送数据,在后端监听接受事件中可以获取到该数据源,可以像普通的AJAX
一样发送一个JSON
类型的数据,作为即时通讯功能的话,这个JSON
对象一般包括三个信息,分别是:发送者ID、接受者ID以及发送内容。close()
方法用于关闭 WebSocket 连接。
1.1.2 WebSocket 对象的监听函数
初始化 WebSocket 对象时,还需要定义了几个监听函数,当这些监听事件发生的时候就会触发这些监听函数,分别是:
onopen()
:当连接初始建立时触发onmessage()
:当 WebSocket 接收到服务器发来的消息的时触发的事件onclose()
:当连接关闭时触发onerror()
:当连接发生错误时触发
1.1.3 WebSocket 对象的 readyState
属性
WebSocket 对象的 readyState
属性表示当前连接的状态:
0
(CONNECTING):表示正在与服务器创建连接1
(OPEN):表示与服务器已经创建了连接2
(CLOSING):表示正在关闭与服务器的连接3
(CLOSED):表示已经关闭与服务器的连接
2 服务器端
java 端使用 WebSocket 可以使用以下三种形式:
- 1、使用 Spring 的底层级 WebSocketAPI 实现(实现
TextWebSocketHandler
接口) - 2、使用 Spring 高级API 实现(使用
SimpMessagingTemplate
方法) - 3、使用 JSR356 定义的 WebSocket 规范实现
个人认为使用 JSP356 的 WebSocket 规范最为简单。
2.1 JSR356定义的 WebSocket 规范
首先引入依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>4.3.10.RELEASE</version>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
JSR356 的 WebSocket 规范使用 javax.websocket.*
的 API,可以将一个普通 Java 对象(POJO)使用 @ServerEndpoint
注释从而作为 WebSocket 服务器的端点,此时客户端浏览器已经可以对 WebSocket 客户端 API 发起 HTTP 长连接了,如:
@ServerEndpoint(value="/websocket/commodity/{userId}", configurator = SpringConfigurator.class)
注释中的参数 value
表示的是 url 路径与 @RequestMapping
注释中的 value
类似,而他表示的是前端创建 WebSocket
对象需要传入的 ws
协议的路径。当中的 {userId}
参数作为当前客户的识别 ID 号,此时客户端传入的 url 应该为:
ws://[Server端IP或域名]:[Server端口]/项目/websocket/commodity/{userId}
注意:如果在要进行对象注入,就必须加上 configurator = SpringConfigurator.class
。
2.1.1 代码示例
在添加完 @ServerEndpoint
注释之后,接着就要声明 @OnOpen
,@OnMessage
,@OnClose
,@OnError
注释的方法了。其实对应的就是前端一些事件函数。
通常的,在 @OnOpen
中把当前连接用户使用 Map
的形式将用户编号与其当前连接的 WebSocketSession
对应缓存下来,以便在该用户接受信息的时候使用。
@OnMessage
中接受信息并根据信息中的用户识别号进行信息的发送,@OnClose
中清除所有该断开连接用户的缓存信息。
eg:
package cn.seiei.webSocket;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.websocket.CloseReason;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import com.alibaba.fastjson.JSON;
import org.springframework.web.socket.server.standard.SpringConfigurator;
@ServerEndpoint(value="/websocket/commodity/{fromUserId}/{toUserId}", configurator = SpringConfigurator.class)
public class WebSocketServerByJSR356 {
// 已经建立链接的对象缓存起来(线性安全)
private static ConcurrentMap<Integer, WebSocketServerByJSR356> serverMap = new ConcurrentHashMap<Integer, WebSocketServerByJSR356>();
// 记录当前 WebSocket 的 session 对象
// 当中 isOpen 方法可以判断该用户是否在线
// 调用 getBasicRemote().sendText(content) 可以发送消息到客户端
private Session currentSession;
/**
* 用户开始连接 webSocket 事件
* @PathParam 解释:https://blog.csdn.net/u011410529/article/details/66974974
* @param session webSocket session 对象
* @param fromUserId url 传入的用户 ID
* @param toUserId url 传入的目标用户 ID
* @throws IOException
*/
@OnOpen
public void onOpen(Session session, @PathParam("fromUserId") int fromUserId, @PathParam("toUserId") int toUserId) throws IOException {
this.currentSession = session;
serverMap.put(fromUserId, this);//建立链接时,缓存对象,这个 this 就是 WebSocketServer 对象
System.out.println("UserId:" + fromUserId + " 连接服务器成功。。。");
System.out.println("session.getRequestURI:" + session.getRequestURI());
System.out.println("session.getQueryString:" + session.getQueryString());
System.out.println("session.getRequestParameterMap:" + session.getRequestParameterMap());
}
/**
* 用户关闭 webSocket 连接事件,清除缓存
* @param session webSocket session 对象
* @param reason 连接关闭原因
*/
@OnClose
public void onClose(Session session, CloseReason reason) {
System.out.println("用户关闭:" + reason.toString());
// 如果缓存中有当前用户的缓存(这里的 this 就是 WebSocketServer 对象)
if (serverMap.containsValue(this)) {
Iterator<Integer> keys = serverMap.keySet().iterator();
int userId = 0;
while(keys.hasNext()) {
userId = keys.next();
if (serverMap.get(userId) == this) {
serverMap.remove(userId, this);//关闭链接时,删除缓存对象
}
}
}
this.currentSession = null;
try {
session.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 连接过后,发送信息
* @param json 信息 JSON 对象,当中包含发送者ID,接受者ID 以及发送信息
*/
@OnMessage
@SuppressWarnings("unchecked")
public void onMessage(String json) {
HashMap<String, String> map = JSON.parseObject(json, HashMap.class);
int fromUserId = Integer.parseInt(map.get("fromUserId"));
int toUserId = Integer.parseInt(map.get("toUserId"));
String content = map.get("content").toString();
WebSocketServerByJSR356 server = serverMap.get(toUserId);
//若存在则用户在线,否在用户不在线
if (server != null && server.currentSession.isOpen()) {
if (fromUserId != toUserId) {
try {
// 发送信息
server.currentSession.getBasicRemote().sendText(content);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 连接发生错误事件
* @param t 错误对象
*/
@OnError
public void onError(Throwable t) {
System.out.println("发生错误事件!!");
t.printStackTrace();
}
}