日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

SpringBoot2.0集成WebSocket,實(shí)現(xiàn)后臺(tái)向前端推送信息

 jackeyqing 2019-04-06

什么是WebSocket?

這里寫圖片描述
WebSocket協(xié)議是基于TCP的一種新的網(wǎng)絡(luò)協(xié)議。它實(shí)現(xiàn)了瀏覽器與服務(wù)器全雙工(full-duplex)通信——允許服務(wù)器主動(dòng)發(fā)送信息給客戶端。

為什么需要 WebSocket?

初次接觸 WebSocket 的人,都會(huì)問同樣的問題:我們已經(jīng)有了 HTTP 協(xié)議,為什么還需要另一個(gè)協(xié)議?它能帶來什么好處?

  • 答案很簡(jiǎn)單,因?yàn)?HTTP 協(xié)議有一個(gè)缺陷:通信只能由客戶端發(fā)起,HTTP 協(xié)議做不到服務(wù)器主動(dòng)向客戶端推送信息。
    這里寫圖片描述
    舉例來說,我們想要查詢當(dāng)前的排隊(duì)情況,只能是頁(yè)面輪詢向服務(wù)器發(fā)出請(qǐng)求,服務(wù)器返回查詢結(jié)果。輪詢的效率低,非常浪費(fèi)資源(因?yàn)楸仨毑煌_B接,或者 HTTP 連接始終打開)。因此WebSocket 就是這樣發(fā)明的。

話不多說,馬上進(jìn)入干貨時(shí)刻。

maven依賴

SpringBoot2.0對(duì)WebSocket的支持簡(jiǎn)直太棒了,直接就有包可以引入

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

WebSocketConfig

啟用WebSocket的支持也是很簡(jiǎn)單,幾句代碼搞定

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

/**
 * 開啟WebSocket支持
 * @author zhengkai
 */
@Configuration  
public class WebSocketConfig {  
	
    @Bean  
    public ServerEndpointExporter serverEndpointExporter() {  
        return new ServerEndpointExporter();  
    }  
  
} 

WebSocketServer

因?yàn)閃ebSocket是類似客戶端服務(wù)端的形式(采用ws協(xié)議),那么這里的WebSocketServer其實(shí)就相當(dāng)于一個(gè)ws協(xié)議的Controller
直接@ServerEndpoint("/websocket")@Component啟用即可,然后在里面實(shí)現(xiàn)@OnOpen,@onClose,@onMessage等方法

import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import org.springframework.stereotype.Component;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import lombok.extern.slf4j.Slf4j;


@ServerEndpoint("/websocket/{sid}")
@Component
public class WebSocketServer {
	
	static Log log=LogFactory.get(WebSocketServer.class);
    //靜態(tài)變量,用來記錄當(dāng)前在線連接數(shù)。應(yīng)該把它設(shè)計(jì)成線程安全的。
    private static int onlineCount = 0;
    //concurrent包的線程安全Set,用來存放每個(gè)客戶端對(duì)應(yīng)的MyWebSocket對(duì)象。
    private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();

    //與某個(gè)客戶端的連接會(huì)話,需要通過它來給客戶端發(fā)送數(shù)據(jù)
    private Session session;

    //接收sid
    private String sid="";
    /**
     * 連接建立成功調(diào)用的方法*/
    @OnOpen
    public void onOpen(Session session,@PathParam("sid") String sid) {
        this.session = session;
        webSocketSet.add(this);     //加入set中
        addOnlineCount();           //在線數(shù)加1
        log.info("有新窗口開始監(jiān)聽:"+sid+",當(dāng)前在線人數(shù)為" + getOnlineCount());
        this.sid=sid;
        try {
        	 sendMessage("連接成功");
        } catch (IOException e) {
            log.error("websocket IO異常");
        }
    }

    /**
     * 連接關(guān)閉調(diào)用的方法
     */
    @OnClose
    public void onClose() {
        webSocketSet.remove(this);  //從set中刪除
        subOnlineCount();           //在線數(shù)減1
        log.info("有一連接關(guān)閉!當(dāng)前在線人數(shù)為" + getOnlineCount());
    }

    /**
     * 收到客戶端消息后調(diào)用的方法
     *
     * @param message 客戶端發(fā)送過來的消息*/
    @OnMessage
    public void onMessage(String message, Session session) {
    	log.info("收到來自窗口"+sid+"的信息:"+message);
        //群發(fā)消息
        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("發(fā)生錯(cuò)誤");
        error.printStackTrace();
    }
	/**
	 * 實(shí)現(xiàn)服務(wù)器主動(dòng)推送
	 */
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }


    /**
     * 群發(fā)自定義消息
     * */
    public static void sendInfo(String message,@PathParam("sid") String sid) throws IOException {
    	log.info("推送消息到窗口"+sid+",推送內(nèi)容:"+message);
        for (WebSocketServer item : webSocketSet) {
            try {
            	//這里可以設(shè)定只推送給這個(gè)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寫個(gè)方法調(diào)用WebSocketServer.sendInfo();即可

@Controller
@RequestMapping("/checkcenter")
public class CheckCenterController {

	//頁(yè)面請(qǐng)求
	@GetMapping("/socket/{cid}")
	public ModelAndView socket(@PathVariable String cid) {
		ModelAndView mav=new ModelAndView("/socket");
		mav.addObject("cid", cid);
		return mav;
	}
	//推送數(shù)據(jù)接口
	@ResponseBody
	@RequestMapping("/socket/push/{cid}")
	public ApiReturnObject pushToWeb(@PathVariable String cid,String message) {  
		try {
			WebSocketServer.sendInfo(message,cid);
		} catch (IOException e) {
			e.printStackTrace();
			return ApiReturnUtil.error(cid+"#"+e.getMessage());
		}  
		return ApiReturnUtil.success(cid);
	} 
} 

頁(yè)面發(fā)起socket請(qǐng)求

然后在頁(yè)面用js代碼調(diào)用socket,當(dāng)然,太古老的瀏覽器是不行的,一般新的瀏覽器或者谷歌瀏覽器是沒問題的。還有一點(diǎn),記得協(xié)議是ws的哦,如果像我這樣封裝了一些basePath的路徑類,可以replace(“http”,“ws”)來替換協(xié)議

    <script> 
    var socket;  
    if(typeof(WebSocket) == "undefined") {  
        console.log("您的瀏覽器不支持WebSocket");  
    }else{  
        console.log("您的瀏覽器支持WebSocket");  
        	//實(shí)現(xiàn)化WebSocket對(duì)象,指定要連接的服務(wù)器地址與端口  建立連接  
            //等同于socket = new WebSocket("ws://localhost:8083/checkcentersys/websocket/20");  
            socket = new WebSocket("${basePath}websocket/${cid}".replace("http","ws"));  
            //打開事件  
            socket.onopen = function() {  
                console.log("Socket 已打開");  
                //socket.send("這是來自客戶端的消息" + location.href + new Date());  
            };  
            //獲得消息事件  
            socket.onmessage = function(msg) {  
                console.log(msg.data);  
                //發(fā)現(xiàn)消息進(jìn)入    開始處理前端觸發(fā)邏輯
            };  
            //關(guān)閉事件  
            socket.onclose = function() {  
                console.log("Socket已關(guān)閉");  
            };  
            //發(fā)生了錯(cuò)誤事件  
            socket.onerror = function() {  
                alert("Socket發(fā)生了錯(cuò)誤");  
                //此時(shí)可以嘗試刷新頁(yè)面
            }  
            //離開頁(yè)面時(shí),關(guān)閉socket
            //jquery1.8中已經(jīng)被廢棄,3.0中已經(jīng)移除
            // $(window).unload(function(){  
            //     socket.close();  
            //});  
    }
    </script> 

運(yùn)行效果

v1.1的效果,剛剛修復(fù)了日志,并且支持指定監(jiān)聽某個(gè)端口,代碼已經(jīng)全部更新,現(xiàn)在是這樣的效果

這里寫圖片描述

這里寫圖片描述
先打開頁(yè)面,指定cid,啟用socket接收,然后再另一個(gè)頁(yè)面調(diào)用剛才Controller封裝的推送信息的方法到這個(gè)cid的socket,即可向前端推送消息。

后續(xù)

針對(duì)簡(jiǎn)單IM的業(yè)務(wù)場(chǎng)景,進(jìn)行了一些優(yōu)化,可以看后續(xù)的文章SpringBoot2+WebSocket之聊天應(yīng)用實(shí)戰(zhàn)(優(yōu)化版本)

主要變動(dòng)是CopyOnWriteArraySet改為ConcurrentHashMap,保證多線程安全同時(shí)方便利用map.get(userId)進(jìn)行推送到指定端口。

相比之前的Set,Set遍歷是費(fèi)事且麻煩的事情,而Map的get是簡(jiǎn)單便捷的,當(dāng)WebSocket數(shù)量大的時(shí)候,這個(gè)小小的消耗就會(huì)聚少成多,影響體驗(yàn),所以需要優(yōu)化。

Websocker注入Bean問題

關(guān)于這個(gè)問題,可以看最新發(fā)表的這篇文章,在參考和研究了網(wǎng)上一些攻略后,項(xiàng)目已經(jīng)通過該方法注入成功,大家可以參考。
關(guān)于controller調(diào)用controller/service調(diào)用service/util調(diào)用service/websocket中autowired的解決方法

使用Netty構(gòu)建高性能websocket服務(wù)器

Springboot2構(gòu)建基于Netty的高性能Websocket服務(wù)器(netty-websocket-spring-boot-starter)
只需要換個(gè)starter即可實(shí)現(xiàn)高性能websocket,趕緊使用吧

另外也感謝大家的閱讀和評(píng)論,一起進(jìn)步,謝謝!~~

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多