SpringBoot+Vue使用WebSocket

一:什么是Websocket?

  • WebSocket是HTML5下一种新的协议(websocket协议本质上是一个基于tcp的协议)
  • 它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯的目的
  • Websocket是一个持久化的协议

二:websocket的原理

        1.websocket约定了一个通信的规范,通过一个握手的机制,客户端和服务器之间能建立一个            类似tcp的连接,从而方便它们之间的通信,在websocket出现之前,web交互一般是基于http            协议的短连接或者长连接
         2.websocket是一种全新的协议,不属于http无状态协议,协议名为"ws"

三:后端代码

        1.导入maven


        
            org.springframework.boot
            spring-boot-starter-websocket
        

      2.配置Config

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
* @Author: 海绵宝宝
* @Explain: 开启webSocket  在线测试地址:http://www.websocket-test.com/
* @DateTime: 2022/5/29 15:54
* @Params:
* @Return
*/
@Configuration
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
}

3.配置Server

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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.HashMap;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
/**
* @Author: 海绵宝宝
* @Explain: WebSocket
* @DateTime: 2022/5/29 15:54
* @Params: WebSocketServer.sendInfo(使用JSON,用户名);
* @Return
*/
@ServerEndpoint("/websocket/{userId}")
@Component
@Slf4j
public class WebSocketServer {

    /**静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。*/
    private static int onlineCount = 0;
    /**concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。*/
    private static ConcurrentHashMap webSocketMap = new ConcurrentHashMap<>();
    /**与某个客户端的连接会话,需要通过它来给客户端发送数据*/
    private Session session;
    /**接收userId*/
    private String userId="";

    /**
     * 连接建立成功调用的方法
     * */
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) {
        this.session = session;
        this.userId=userId;
        if(webSocketMap.containsKey(userId)){
            webSocketMap.remove(userId);
            //加入set中
        }else{
            webSocketMap.put(userId,this);
            //加入set中
            addOnlineCount();
            //在线数加1
        }

        log.info("用户连接:"+userId+",当前在线人数为:" + getOnlineCount());

        try {
            HashMap map = new HashMap<>();
            map.put("key","连接成功");
            sendMessage(JSON.toJSONString(map));
        } catch (IOException e) {
            log.error("用户:"+userId+",网络异常!!!!!!");
        }
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        if(webSocketMap.containsKey(userId)){
            webSocketMap.remove(userId);
            //从set中删除
            subOnlineCount();
        }
        log.info("用户退出:"+userId+",当前在线人数为:" + getOnlineCount());
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息*/
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("用户消息:"+userId+",报文:"+message);
        //可以群发消息
        //消息保存到数据库、redis
        if(StringUtils.isNotBlank(message)){
            try {
                //解析发送的报文
                JSONObject jsonObject = JSONObject.parseObject(message);
                //追加发送人(防止串改)
                jsonObject.put("fromUserId",this.userId);
                String fromUserId=jsonObject.getString("fromUserId");
                //传送给对应toUserId用户的websocket
                if(StringUtils.isNotBlank(fromUserId) && webSocketMap.containsKey(fromUserId)){
                    webSocketMap.get(fromUserId).sendMessage(jsonObject.toJSONString());
                    //自定义-业务处理

//                    DeviceLocalThread.paramData.put(jsonObject.getString("group"),jsonObject.toJSONString());
                }else{
                    log.error("请求的userId:"+fromUserId+"不在该服务器上");
                    //否则不在这个服务器上,发送到mysql或者redis
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    /**
     *  发生错误时候
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("用户错误:"+this.userId+",原因:"+error.getMessage());
        error.printStackTrace();
    }
    /**
     * 实现服务器主动推送
     */
    public void sendMessage(String message) throws IOException {
        //加入线程锁
        synchronized (session){
            try {
                //同步发送信息
                this.session.getBasicRemote().sendText(message);
            } catch (IOException e) {
                log.error("服务器推送失败:"+e.getMessage());
            }
        }
    }


    /**
     * 发送自定义消息
     * */
    /**
     * 发送自定义消息
     * @param message 发送的信息
     * @param toUserId  如果为null默认发送所有
     * @throws IOException
     */
    public static void sendInfo(String message,String toUserId) throws IOException {
        //如果userId为空,向所有群体发送
        if(StringUtils.isEmpty(toUserId)) {
        //向所有用户发送信息
        Iterator itera = webSocketMap.keySet().iterator();
        while (itera.hasNext()) {
            String keys = itera.next();
            WebSocketServer item = webSocketMap.get(keys);
            item.sendMessage(message);
        }
        }
        //如果不为空,则发送指定用户信息
       else if(webSocketMap.containsKey(toUserId)){
            WebSocketServer item = webSocketMap.get(toUserId);
            item.sendMessage(message);
        }else{
            log.error("请求的userId:"+toUserId+"不在该服务器上");
        }
    }

    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        WebSocketServer.onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        WebSocketServer.onlineCount--;
    }

    public static synchronized ConcurrentHashMap getWebSocketMap(){
        return WebSocketServer.webSocketMap;
    }
}

后端配置好后可以在  http://www.websocket-test.com/ 中测试 :

地址示例: ws://127.0.0.1:8082/websocket/test

四:配置前端

1.在App.vue中

 配置参数


data() {
    return {
      // socket参数
			socket: null,
			timeout: 10 * 1000, // 45秒一次心跳
			timeoutObj: null, // 心跳心跳倒计时
			serverTimeoutObj: null, // 心跳倒计时
			timeoutnum: null, // 断开 重连倒计时
			lockReconnect: false, // 防止
			websocket: null
    }
  },

 在mounted中初始化


 mounted () {

      this.initWebSocket();
    
  },

在methods中写方法

methods: {
    initWebSocket() {
			// WebSocket与普通的请求所用协议有所不同,ws等同于http,wss等同于https
      let wsUrl = 后端地址;
      this.websocket = new WebSocket(wsUrl);
			this.websocket.onopen = this.websocketonopen;
			this.websocket.onerror = this.websocketonerror;
			this.websocket.onmessage = this.setOnmessageMessage;
			this.websocket.onclose = this.websocketclose;
            // 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
            // window.onbeforeunload = that.onbeforeunload
		},
		start() {
			console.log('start');
			console.log(this.$store.getters.name)
			//清除延时器
			this.timeoutObj && clearTimeout(this.timeoutObj);
			this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
			this.timeoutObj = setTimeout(() => {
				if (this.websocket && this.websocket.readyState == 1) {
          let actions = {"test":"12345"};
					this.websocket.send(JSON.stringify(actions));//发送消息,服务端返回信息,即表示连接良好,可以在socket的onmessage事件重置心跳机制函数
				} else {
					this.reconnect();
				}
				//定义一个延时器等待服务器响应,若超时,则关闭连接,重新请求server建立socket连接
				this.serverTimeoutObj = setTimeout(() => {
					this.websocket.close();
				}, this.timeout)
			}, this.timeout)
		},
		reset() { // 重置心跳
			// 清除时间
			clearTimeout(this.timeoutObj);
			clearTimeout(this.serverTimeoutObj);
			// 重启心跳
			this.start();
		},
		// 重新连接
        reconnect() {
			if (this.lockReconnect) return
			this.lockReconnect = true;
			//没连接上会一直重连,设置延迟避免请求过多
            this.timeoutnum && clearTimeout(this.timeoutnum);
            this.timeoutnum = setTimeout(() => {
				this.initWebSocket();
                this.lockReconnect = false;
            }, 5000)
        },
		async setOnmessageMessage(event) {
			// console.log(event.data, '获得消息');
			this.reset();
			// 自定义全局监听事件
			window.dispatchEvent(new CustomEvent('onmessageWS', {
				detail: {
					data: event.data
				}
			}))
			// //发现消息进入    开始处理前端触发逻辑
			// if (event.data === 'success' || event.data === 'heartBath') return
        },
		websocketonopen() {
            //开启心跳
            this.start();
            console.log("WebSocket连接成功!!!"+new Date()+"----"+this.websocket.readyState);
      clearInterval(this.otimer);//停止
		},
		websocketonerror(e) {
			console.log("WebSocket连接发生错误" + e);
		},
		websocketclose(e) {
            this.websocket.close();
            clearTimeout(this.timeoutObj);
			clearTimeout(this.serverTimeoutObj);
            console.log("WebSocket连接关闭");
		},
    websocketsend(messsage) {
        that.websocket.send(messsage)
    },
    closeWebSocket() { // 关闭websocket
        that.websocket.close()
    },
    //  // 收到消息处理
	  // getSocketData (res) {
	  // 	if (res.detail.data === 'success' || res.detail.data === 'heartBath') return
	  // 	// ...业务处理
	  // }
  },

 这样配置好后就可以在自己想要使用的页面添加以下示例就可以用了

mounted() {
      // 添加socket通知监听
	    window.addEventListener('onmessageWS', this.getSocketData)
    },
methods: {
      // 收到消息处理
	    getSocketData (res) {
        console.log(res.detail.data)
        
	    },
}

你可能感兴趣的:(websocket,vue.js,网络协议)