WebSocket学习
WebSocket理论
WebSocket介绍
- WebSocket是一种网络通信协议,RFC6455定义了它的通信标准
- WebSocket是HTML5开始提供的一种在单个TCP连接上进行全双工通讯的协议
因为HTPP协议是无状态,无连接,单向的应用层协议,他采用的是请求/响应模型,通信请求只能由客户端发起,服务端对请求进行处理及响应,但是我们有时候是需要监测后端数据发生了变化,或者后端主动给前端去推送信息,这个时候使用http的话只能采用长轮询、轮询的方式去处理,效率低且浪费资源,所以有了Websocket协议的产生。
WebSocket协议
协议有两部分:握手和数据传输,握手时基于http协议的。
来自客户端的握手如下形式:
来自服务端的握手如下形式:
内容体说明
| 头名称 | 说明 |
|---|---|
| Connection:Upgrade | 标识该http请求是一个协议升级请求 |
| Upgrade:Websocket | 协议升级为Websocket协议 |
| Sec-WebSocket-Version:13 | 客户端支持WebSocket的版本 |
| Sec-WebSocket-key:xxxfgfsgfdgdfg | 客户端采用base64编码的24位随机字符序列,服务器接收客户端http协议升级的证明,要求服务端响应一个对应加密的Sec-WebSocket-Accept头信息作为应答 |
| Sec-WebSocket-Extensions | 协议扩展类型 |
WebSocket实现
客户端(浏览器)实现
websocket对象
实现Websockets的Web浏览器将通过WebSocket对象公开所有必须的客户端功能(主要指支持Html5的浏览器)。
// url:ws://ip:port/uri
var ws = new WebSocket(url);
websocket事件
| 事件 | 事件处理程序 | 描述 |
|---|---|---|
| open | websocket对象.onopen | 连接建立时触发me |
| message | websocket对象.onmessage | 客户端接收服务端数据时触发 |
| error | websocket对象.onerror | 通信发生错误时触发 |
| close | websocket对象.onclose | 连接关闭时触发 |
websocket方法
| 方法 | 描述 |
|---|---|
| websocket对象.send() | 使用连接向服务端发送数据 |
| websocket对象.close() | 使用连接向服务器发起断开连接 |
服务端实现
概念
Tomcat的7.0.5版本开始支持WebSocket,并且实现了Java WebSocket规范(JSR356)。
Java WebSocket 应用由一系列的WebSocketEndpoint组成,Endpoint是一个java对象,代表WebSocket链接的一端,对于服务端,我们可以视为处理具体WebSocket消息的接口,就像Servlet与http请求一样。
实现方式
我们可以通过以下两种方式定义Endpoint:
- 编程式:继承类javax.websocket.Endpoint实现其方法
- 注解式:定义一个POJO,并添加@ServerEndpoint相关注解
Endpoint生命周期
Endpoint实例在WebSocket握手时创建,并在客户端与服务端链接过程中有效,最后在链接关闭时结束,在Endpoint接口中明确定义了与其生命周期相关的方法,规范实现者确保生命周期的各个阶段调用实例的相关方法,生命周期方法如下:
| 方法 | 含义描述 | 注解 |
|---|---|---|
| onClose | 当会话关闭时调用 | @OnClose |
| onOpen | 当开启一个新的会话时调用,即客户端和服务端握手成功之后调用 | @OnOpen |
| onError | 链接过程中异常时调用 | @OnError |
服务端接收客户端发送的数据
通过为Session添加MessageHandler消息处理器来接收消息,当采用注解方式定义Endpoint时,我们还可以通过@OnMessage注解指定接收消息的方法。
服务端推送数据至客户端
发送消息则由RemoteEndpoint完成,其实例由Session维护,根据使用情况,我们可以通过Session.getBasicRemote获取同步消息发送的实例,然后调用其sendXxxx()方法就可以发送消息,可以通过Session.getAsyncRemote获取异步消息发送实例。
WebSocket案例代码
服务端实现
依赖添加
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
</dependencies>
ServerEndpointExporter配置类
package com.lwp.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* @author: liuwenpu
* @date: 2021/10/1315:41
* @description:
*/
@Configuration
public class WebSocketConfig {
/**
* 该bean会自动注册使用@ServerEndPoint注解声明的Websocket endpoint
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
WebSocketService服务类
package com.lwp.service;
import org.apache.tomcat.websocket.WsSession;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author: liuwenpu
* @date: 2021/10/1315:45
* @description:
*/
@ServerEndpoint("/webSocketService/{userId}")
@Component
public class WebSocketService{
/**
* 使用juc包中的线程安全集合存储,key为token唯一标识,value为session对象
*/
static ConcurrentHashMap<String,Session> webSocketMap;
static {
webSocketMap = new ConcurrentHashMap();
}
/**
* 建立会话时触发
* @param session session会话
* @param userId 用户id参数
*/
@OnOpen
public void onOpen(Session session, @PathParam("userId") String userId){
System.out.println(userId+"--->建立连接-------------");
WsSession ws = (WsSession) session;
//将当前会话存储session
webSocketMap.put(userId,session);
}
@OnClose
public void onClose(Session session, @PathParam("userId") String userId){
System.out.println(userId+"--->连接断开--------");
//移除在线用户
webSocketMap.remove(userId);
}
@OnMessage
public void onMessage(Session session, @PathParam("userId") String userId, String msg){
System.out.println("客户端发过来的消息:"+msg);
try {
//主动往浏览器发送消息
session.getBasicRemote().sendText(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
@OnError
public void onError(Session session,Throwable err){
System.out.println("发生错误,错误的sessionId:"+session.getId());
err.printStackTrace();
}
/**
* 自定义消息发送
* @param userId 用户标识
* @param msg 消息
*/
public void sendMessage(String userId,String msg){
webSocketMap.forEach( (key,value) -> {
if(key.equals(userId)){
onMessage(value,userId,msg);
}
});
}
/**
* 全局消息发送
* @param msg
*/
public void broadCast(String msg){
webSocketMap.forEach( (key,session) -> {
if(session.isOpen()){
//发送同步信息到当前会话
try {
session.getBasicRemote().sendText(msg);
//发送异步消息到当前会话
// session.getAsyncRemote().sendText(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
控制层模拟后端向前端发送数据
package com.lwp.controller;
import com.lwp.service.WebSocketService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/**
* @author: liuwenpu
* @date: 2021/10/1317:01
* @description:
*/
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
private WebSocketService webSocketService;
/**
* 通过token指定消息发送
* @param userId
* @param msg
*/
@GetMapping("/tt/{userId}/{msg}")
public void tt(@PathVariable("userId") String userId, @PathVariable("msg") String msg){
webSocketService.sendMessage(userId,msg);
}
/**
* 发送广播消息
*/
@GetMapping("/allMsg/{msg}")
public void allMsg(@PathVariable("msg") String msg){
webSocketService.broadCast(msg);
}
}
客户端实现
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>websocket测试</title>
<script type="text/javascript">
let ws = null;
function socketConnect(){
let token = prompt("输入你的token");
let url = "ws://127.0.0.1/webSocketService/"+token;
ws = new WebSocket(url);
ws.onopen = function(){
console.log("连接建立");
}
ws.onclose = function(){
console.log("连接关闭");
}
ws.onerror = function(){
console.log("连接异常");
}
ws.onmessage = function(e){
let dataDom = document.getElementById("data");
dataDom.innerHTML = "<h1>服务器发送的消息为:"+e.data+"</h1>";
}
}
function sendMsg(){
let dom = document.getElementById("text");
ws.send(dom.value);
}
function closeWs(){
ws.close();
}
</script>
</head>
<body>
<button onclick="socketConnect()">开启websocket</button><br />
<button onclick="closeWs()">关闭websocket</button><br />
<input id="text" type="text"/> <button onclick="sendMsg()">发送消息</button><br />
<div id="data">
</div>
</body>
</html>

浙公网安备 33010602011771号