WebSocket 实现消息推送

1.背景

应用场景为前端需要实时获取后端数据。之前的做法是JS定时器发送Ajax请求,获取后端数据,这样的话需要一直请求后端,有点浪费资源。

使用websocket可以实现后端向前端推送消息,实现双工通信。

2.实现

2.1 pom依赖
       <dependency>
		    <groupId>javax.websocket</groupId>
		    <artifactId>javax.websocket-api</artifactId>
		    <version>1.1</version>
		    <scope>provided</scope>
		</dependency>
2.2 JAVA 服务端
import java.io.IOException;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

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 org.apache.commons.lang.StringUtils;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

import com.alibaba.fastjson.JSONObject;

/**
 * 
 * @ClassName: TestSocket
 * @Description: Websocket服务,推送信息
 * @author: luchenxi
 * @date: 2021年4月7日 上午11:57:06
 * @Copyright:
 */
@ServerEndpoint("/test/{socketParam}")
public class TestSocket {

    private Logger                                   logger      = LogManager.getLogger(TestSocket.class);

    // 客户端统计
    private static int                               onlineCount = 0;

    // 客户端回话缓存
    private static Map<String, TestSocket> clients     = new ConcurrentHashMap<String, TestSocket>();

    // 会话
    private Session                                  session;

    // uid
    private String                                   uid;

    /**
     * 
     * @Title: onOpen
     * @Description: 有连接时的触发函数
     * @param: @param socketParam
     * @param: @param session
     * @param: @throws IOException
     * @return: void
     * @throws
     */
    @OnOpen
    public void onOpen(@PathParam("socketParam")
    String socketParam, Session session) throws IOException {
        logger.info(socketParam);
        // session
        this.session = session;
        // 解析Json参数
        JSONObject param = JSONObject.parseObject(socketParam);
        this.uid = param.getString("uid");
        // 业务逻辑 
        // ..............
        // ..............
        // 客户端数量+1
        addOnlineCount();
        // 缓存回话
        clients.put(this.uid, this);
    }

    /**
     * 
     * @Title: onClose
     * @Description: 连接关闭时的调用方法
     * @param: @throws IOException
     * @return: void
     * @throws
     */
    @OnClose
    public void onClose() throws IOException {
        // 移除回话
        clients.remove(this.uid);
        // 数量-1
        subOnlineCount();
    }

    /**
     * 
     * @Title: onMessage
     * @Description: 收到消息时调用的函数,其中Session是每个websocket特有的数据成员
     * @param: @param message
     * @param: @throws IOException
     * @return: void
     * @throws
     */
    @OnMessage
    public void onMessage(String message) throws IOException {
        logger.info("Client : " + message);
        // 解析Json参数
        JSONObject param = JSONObject.parseObject(message);
        // 业务逻辑 
        // ..............
        // ..............
    }

    /**
     * 
     * @Title: onError
     * @Description: 发生意外错误时调用的函数
     * @param: @param session
     * @param: @param error
     * @return: void
     * @throws
     */
    @OnError
    public void onError(Session session, Throwable error) {
        error.printStackTrace();
    }

    /**
     * 
     * @Title: sendMessageTo
     * @Description: 向客户端推送消息,可以实现客户端A-》B发消息【类似于聊天室】,回话缓存的key再加上AppId,还能实现同账号多客户端登录收消息等。
     * @param: @param message
     * @param: @param To
     * @param: @throws IOException
     * @return: void
     * @throws
     */
    public void sendMessageTo(String message, String client) throws IOException {
        for (TestSocket socket : clients.values()) {
            if (socket.getKey().equals(client))
                socket.session.getAsyncRemote().sendText(message);
        }

    }

    /**
     * 
     * @Title: getOnlineCount
     * @Description: 统计当前连接数
     * @param: @return
     * @return: int
     * @throws
     */
    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    /**
     * 
     * @Title: addOnlineCount
     * @Description: 新增客户端数量
     * @param:
     * @return: void
     * @throws
     */
    public static synchronized void addOnlineCount() {
        onlineCount++;
    }

    /**
     * 
     * @Title: subOnlineCount
     * @Description: 客户端数量减少
     * @param:
     * @return: void
     * @throws
     */
    public static synchronized void subOnlineCount() {
        onlineCount--;
    }

    /**
     * 
     * @Title: getClients
     * @Description: 获取所有客户端
     * @param: @return
     * @return: Map<String,WebSocket>
     * @throws
     */
    public static synchronized Map<String, TestSocket> getClients() {
        return clients;
    }
}

@ServerEndpoint来进行声明接口:@ServerEndpoint(value="/websocket/{paraName}"),Spring中注意包扫描;

“ { } ”用来表示带参数的连接,如果需要获取{}中的参数在参数列表中增加:@PathParam(“paraName”),要实现多参数的可以就向上面一样用json做。

和tomcat用的同一个端口,TCP通信,无需新开端口!!!!

2.3 HTML 前端
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        websocket Demo <br />
        <input id="text" type="text" /> 
        <button onclick="send()"> Send </button>   
        <button   onclick="closeWebSocket()"> Close </button>
        <button   onclick="recv()"> 后台开始推送 </button>
        <br>
    </body>

    <script type="text/javascript">
     //判断当前浏览器是否支持WebSocket
      if('WebSocket' in window){
          // JSON 参数封装
          var scoketParam = {
            pid:'xx215',
            uid:'vis002'
          };
          websocket = new WebSocket("ws://localhost:8080/fpiiclouddata/test/{"+JSON.stringify(scoketParam)+"}");
      }else{
          alert('对不起,您的浏览器不支持webSocket')
      }
      
      //连接发生错误的回调方法
      websocket.onerror = function(){
        alert("websocket 连接异常");
      };
       
      //连接成功建立的回调方法
      websocket.onopen = function(event){
          alert("websocket 连接已建立");
      }
    
      //接收到消息的回调方法
      websocket.onmessage = function(event){
        alert(event.data);
      }
       
      //连接关闭的回调方法
      websocket.onclose = function(){
        alert("websocket 连接已关闭");
      }
       
      //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
      window.onbeforeunload = function(){
          websocket.close();
      }
       
      //关闭连接
      function closeWebSocket(){
          websocket.close();
      }
       
      //给vis001发送消息
      function send(){
          var message = document.getElementById('text').value;
          var scoketParam = {
            target: 'vis001',
            message: message
          };
          websocket.send(JSON.stringify(scoketParam));
      }

      // 通知后台开始推送消息
      function recv(){
          var scoketParam = {
            target: 'self',
          };
          websocket.send(JSON.stringify(scoketParam));
      }
    </script>

</html>

3.总结

实现难度不高,关键在于优化性能。要注意后端多线程下缓存的处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值