SringBoot+WebSocket Deamo

 

WebSocket協議是基於TCP的一種新的網絡協議。它實現了瀏覽器與服務器全雙工(full-duplex)通信——允許服務器主動發送信息給客戶端。


爲什麼選擇 WebSocket?

初次接觸 WebSocket 的人,都會問同樣的問題:我們已經有了 HTTP 協議,爲什麼還需要另一個協議?它能帶來什麼好處?

    答案很簡單,因爲 HTTP 協議有一個缺陷:通信只能由客戶端發起,HTTP 協議做不到服務器主動向客戶端推送信息。


舉例來說,我們想要查詢當前的排隊情況,只能是頁面輪詢向服務器發出請求,服務器返回查詢結果。

輪詢的效率低,非常浪費資源(因爲必須不停連接,或者 HTTP 連接始終打開)。

因此WebSocket 就是這樣出現了。

不多說了直接上代碼:

maven依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

服務器端一般只需要做webSocket的服務端,客戶端的代碼需要的情況很少

配置文件

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

/**
 * kcm
 */
@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

server代碼

package org.bangying.websocket.config;

import lombok.extern.slf4j.Slf4j;
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.CopyOnWriteArraySet;

/**
 * kcm
 */
@Component
@ServerEndpoint("/websocket/{sid}")
@Slf4j
public class WebSocketServer {

    /**
     * 靜態變量,用來記錄當前在線連接數。應該把它設計成線程安全的。
     */
    private static int onlineCount = 0;
    /**
     * concurrent包的線程安全Set,用來存放每個客戶端對應的MyWebSocket對象。
     */
    private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();
    /**
     * 與某個客戶端的連接會話,需要通過它來給客戶端發送數據
     */
    private Session session;
    /**
     * 接收sid
     */
    private String sid="";
    /**
     * 連接建立成功調用的方法
     **/
    @OnOpen
    public void onOpen(Session session,@PathParam("sid") String sid) {
        this.session = session;
        //加入set中
        webSocketSet.add(this);
        //在線數加1
        addOnlineCount();
        log.info("有新窗口開始監聽:"+sid+",當前在線人數爲" + getOnlineCount());
        this.sid=sid;
        try {
            sendMessage("連接成功");
        } catch (IOException e) {
            log.error("websocket IO異常");
        }
    }
    /**
     * 連接關閉調用的方法
     */
    @OnClose
    public void onClose() {
        //從set中刪除
        webSocketSet.remove(this);
        //在線數減1
        subOnlineCount();
        log.info("有一連接關閉!當前在線人數爲" + getOnlineCount());
    }
    /**
     * 收到客戶端消息後調用的方法
     * @param message 客戶端發送過來的消息
     **/
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("收到來自窗口"+sid+"的信息:"+message);
        //羣發消息
        for (WebSocketServer item : webSocketSet) {
            try {
                item.sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("發生錯誤");
        error.printStackTrace();
    }
    /**
     * 實現服務器主動推送
     */
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }
    /**
     * 羣發自定義消息
     * */
    public static void sendInfo(String message,@PathParam("sid") String sid) throws IOException {
        log.info("推送消息到窗口"+sid+",推送內容:"+message);
        for (WebSocketServer item : webSocketSet) {
            try {
                //這裏可以設定只推送給這個sid的,爲null則全部推送
                if(sid==null) {
                    item.sendMessage(message);
                }else if(item.sid.equals(sid)){
                    item.sendMessage(message);
                }
            } catch (IOException e) {
                continue;
            }
        }
    }
    public static synchronized int getOnlineCount() {
        return onlineCount;
    }
    public static synchronized void addOnlineCount() {
        WebSocketServer.onlineCount++;
    }
    public static synchronized void subOnlineCount() {
        WebSocketServer.onlineCount--;
    }
}

controller層接口代碼,可以向前端推送消息。

package org.bangying.websocket.controller;

import lombok.extern.slf4j.Slf4j;
import org.bangying.websocket.config.WebSocketServer;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import java.io.IOException;

/**
 * kcm
 */
@Slf4j
@RestController
@RequestMapping("/send")
public class CheckCenterController {
    /**
     * 頁面請求
     * @param cid
     * @return
     */
    @GetMapping("/socket/{cid}")
    public ModelAndView socket(@PathVariable String cid) {
        ModelAndView mav=new ModelAndView("/socket");
        mav.addObject("cid", cid);
        return mav;
    }
    /**
     * 推送數據接口
     * @param cid
     * @param message
     * @return
     */
    @ResponseBody
    @RequestMapping("/socket/push/{cid}")
    public String pushToWeb(@PathVariable String cid,String message) {
        try {
            WebSocketServer.sendInfo(message,cid);
        } catch (IOException e) {
            e.printStackTrace();
            return "error:"+cid+"#"+e.getMessage();
        }
        return "success:"+cid;
    }

}

html代碼

<html>
<head>
    <meta charset="UTF-8">
    <title>websocket測試</title>
    <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
    <style type="text/css">
        h3,h4{
            text-align:center;
        }
    </style>
</head>
<body>

<h3>WebSocket測試,客戶端接收到的消息如下:</h3>

<textarea id = "messageId" readonly="readonly" cols="150" rows="30" >

</textarea>


<script type="text/javascript">
    var socket;
    if (typeof (WebSocket) == "undefined") {
        console.log("遺憾:您的瀏覽器不支持WebSocket");
    } else {
        console.log("恭喜:您的瀏覽器支持WebSocket");
        //實現化WebSocket對象
        //指定要連接的服務器地址與端口建立連接
        //注意ws、wss使用不同的端口。我使用自簽名的證書測試,
        //無法使用wss,瀏覽器打開WebSocket時報錯
        //ws對應http、wss對應https。
        socket = new WebSocket("ws://127.0.0.1:8081/ws/asset");
        //連接打開事件
        socket.onopen = function() {
            console.log("Socket 已打開");
            socket.send("消息發送測試(From Client)");
        };
        //收到消息事件
        socket.onmessage = function(msg) {
            $("#messageId").append(msg.data+ "\n");
            console.log(msg.data  );
        };
        //連接關閉事件
        socket.onclose = function() {
            console.log("Socket已關閉");
        };
        //發生了錯誤事件
        socket.onerror = function() {
            alert("Socket發生了錯誤");
        }
        //窗口關閉時,關閉連接
        window.unload=function() {
            socket.close();
        };
    }
</script>

</body>
</html>

如上代碼本地測試肯定沒問題。

業務方面的代碼可以自己隨意添加代碼,同時要考慮一下跨域的問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章