WebSocket学习

WebSocket理论

WebSocket介绍

  1. WebSocket是一种网络通信协议,RFC6455定义了它的通信标准
  2. 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:

  1. 编程式:继承类javax.websocket.Endpoint实现其方法
  2. 注解式:定义一个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>

posted @ 2021-10-13 20:44  幸运刘  阅读(51)  评论(0)    收藏  举报