1. 程式人生 > >websocket實現類似於QQ的聊天功能

websocket實現類似於QQ的聊天功能

使用websocket實現聊天的原因

  • http單向通訊協議,請求只能是客戶端發起,且是無狀態的,而websocket是雙向通訊協議,可以有伺服器發起也可以是客戶端發起,用http實現聊天功能一般是通過輪詢,但是輪詢非常浪費伺服器資源,而且慢,親測過
  • 重點websocket實現簡單,速度快

上程式碼

後臺程式碼:採用websocket的註解

package com.xh.procourt.controller;

/**
 * Created: Administrator
 * author:xmf
 * Date:2018/7/20
 * Time:9:49
 */

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.web.context.ContextLoader;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * @ServerEndpoint 註解是一個類層次的註解,它的功能主要是將目前的類定義成一個websocket伺服器端,value是客戶端連線地址,這裡定義了兩個引數,和springmvc的配置指定格式的URL,
 * 對映到對應的引數是一樣的
 */
@ServerEndpoint(value = "/websocket/{msg_me}")//引數格式 自定義連線標識
public class WebSocketCt {
    //靜態變數,用來記錄當前線上連線數。應該把它設計成執行緒安全的。
    private static int onlineCount = 0;
    //用來存放每個客戶端對應的MyWebSocket物件。若要實現服務端與單一客戶端通訊的話,可以使用Map來存放,其中Key可以為使用者標識類似於扣扣號,map是執行緒不安全的
    private static Map<String, Session> webSocketSet = new HashMap<>();


    /**
     * 連線建立成功呼叫的方法
     *
     * @param session 可選的引數。session為與某個客戶端的連線會話,需要通過它來給客戶端傳送資料 @PathParam用來獲取連線路徑上的引數
     */
    @OnOpen
    public void onOpen(@PathParam(value = "msg_me") String param, Session session) {
        webSocketSet.put(param, session);     //加入map中
        addOnlineCount();           //線上數加1
        System.out.println("標識為----" + param + "----的使用者連線成功,當前線上人數:::" + getOnlineCount());
        //用map構造訊息格式 懶得建立訊息實體類
        Map<String, Object> map = new HashMap<>();
        map.put("senderId", "system");
        map.put("msgContent", "歡迎連線");
        //由於這裡使用的string的訊息型別所以在這裡轉成json,當然還有其它訊息型別
        try {
            sendMessage(JSON.toJSON(map).toString(), session);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 連線關閉呼叫的方法
     */
    @OnClose
    public void onClose(@PathParam(value = "msg_me") String param) {
        //根據自定義的標識移除此使用者的連線
        webSocketSet.remove(param);
        subOnlineCount();   //線上數減1
        System.out.println("標識為----" + param + "----的使用者斷開連線成功,當前線上人數:::" + getOnlineCount());
    }

    /**
     * 收到客戶端訊息後呼叫的方法
     *
     * @param message 客戶端傳送過來的訊息 格式傳送人_接收人_訊息
     * @param session 可選的引數
     */
    @OnMessage
    public void onMessage(String message, Session session, @PathParam(value = "msg_me") String fromId) {
        System.out.println("收到訊息:::" + message.toString());
        //將訊息裝成json物件
        JSONObject msgObj = JSON.parseObject(message);
        //得到前臺傳過來的訊息接收者id
        String toId = msgObj.getString("toId");
        //得到傳送者標識
        String senderId = msgObj.getString("senderId");
        //得到訊息內容
        String msgContent = msgObj.getString("msgContent");
        for (String s : webSocketSet.keySet()) {
            if (s.equals(toId)) {
                Session session1 = webSocketSet.get(s);
                try {
                    //用map構造訊息格式 懶得建立訊息實體類
                    Map<String, Object> map = new HashMap<>();
                    map.put("senderId", fromId);
                    map.put("msgContent", msgContent);
                    //由於這裡使用的string的訊息型別所以在這裡轉成json,當然還有其它訊息型別
                    sendMessage(JSON.toJSON(map).toString(), session1);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return;
            }
        }
    }

    /**
     * 發生錯誤時呼叫
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        error.printStackTrace();
    }

    /**
     * 這個方法與上面幾個方法不一樣。沒有用註解,是根據自己需要新增的方法。作用是傳送訊息
     *
     * @throws java.io.IOException
     * @param: message要傳送的訊息 session接收者的會話
     */
    public static void sendMessage(String message, Session session) throws IOException {
        session.getBasicRemote().sendText(message);
    }

    /**
     * 功能描述:用來返回當前線上的總人數
     *
     * @param:
     * @return:
     * @auther: Administrator
     * @date: 2018/7/20 9:37
     */
    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    /**
     * 功能描述:線上總人數加一
     *
     * @param:
     * @return:
     * @auther: Administrator
     * @date: 2018/7/20 9:37
     */
    public static synchronized void addOnlineCount() {
        WebSocketCt.onlineCount++;
    }


    /**
     * 功能描述:線上總人數減一
     *
     * @param:
     * @return:
     * @auther: Administrator
     * @date: 2018/7/20 9:38
     */
    public static synchronized void subOnlineCount() {
        WebSocketCt.onlineCount--;
    }
}

前端程式碼

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<title>簡易實現點對點聊天</title>
<body>
<div>
    <input id="connectId" type="text">
    <button type="button" id="connect">連線到聊天室</button>
</div>

<div id="msgInfo">
</div>

<div style="margin-top: 50px">
    <div>
        <div><span>接收者id:</span><input type="text" id="toId">
            <span>要傳送的訊息:</span><input type="text" id="msg"></input>
            <button id="send" type="button">傳送訊息</button>
        </div>
    </div>
</div>
</body>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script>
    var websocket = null;
    var conect = null;

    //點選連線
    $("#connect").click(function () {
        //判斷當前連線是否為空
        if (websocket != null) {
            alert("您已經連線了,請不要重複連線!");
            return;
        }
        //自定義的唯一標識,如果兩個人輸入的連線標識相同則會覆蓋原來這個標識對應的連線,這裡只是為了簡單演示
        var connectId = $("#connectId").val();
        if (connectId == null || connectId == '') {
            alert("請先輸入您的連線標識!");
            return
        }
        //開始連線
        // 首先判斷是否 支援 WebSocket
        if ('WebSocket' in window) {
            //路徑ws + ip + port + 自定義路徑
            conect = connectId;
            websocket = new WebSocket("ws://192.168.10.20:8080//websocket/" + connectId);
        } else {
            alert("瀏覽器不支援連線!")
            return;
        }

        // 開啟時
        websocket.onopen = function (evnt) {
            console.log("  websocket.onopen  ");
            alert("連線成功!");
        };

        // 處理訊息時
        websocket.onmessage = function (evnt) {
            //將訊息轉成json格式
            var msg = JSON.parse(evnt.data);
            $("#msgInfo").append("<p>收到--" + msg.senderId + "--給您發訊息:<font color='red'>" + msg.msgContent + "</font></p>");
            console.log("  websocket.onmessage   ");
        };

        websocket.onerror = function (evnt) {
            websocket == null;
            console.log("  websocket.onerror  ");
        };

        websocket.onclose = function (evnt) {
            console.log("  websocket.onclose  ");
            websocket.close();
            alert("連線關閉!");
        };
    });

    //傳送訊息
    $("#send").click(function () {
        //先判斷是否連線
        if (websocket == null) {
            alert("您還沒有連線!!!");
            return;
        }

        //接收者id
        var toId = $("#toId").val();
        //傳送的訊息內容
        var msg = $("#msg").val();
        if (toId == null || toId == '') {
            alert("請先輸入接收者");
            return;
        }
        if (msg == null || msg == '') {
            alert("訊息不能為空!!!");
            return;
        }

        //傳送訊息
        //構造訊息的json格式
        var msgJson = {
            senderId: conect,
            msgContent: msg,
            toId: toId
        };
        websocket.send(JSON.stringify(msgJson));
    });
</script>
</html>

執行效果截圖