歡迎光臨
每天分享高質量文章

SpringBoot | 第十九章:web 應用開發之 WebSocket

(點選上方公眾號,可快速關註)


來源:oKong ,

blog.lqdev.cn/2018/08/14/springboot/chapter-nineteen/


前言


web開發也講解了三章了,這章節開始講解關於與前端通訊相關知識。實現一個線上聊天室類似的功能或者後端推送訊息到前端,在沒有WebSocket時,讀大學那夥還有接觸過DWR(Direct Web Remoting),也使用過輪詢的方式,當Servlet3.0出來後,也有使用其非同步連線機制進行前後端通訊的。今天我們就來說說WebSocket。它是HTML5開始提供的。


關於WebSocket


WebSocket是HTML5開始提供的一種在單個TCP連線上進行全雙工通訊的協議。


在WebSocket API中,瀏覽器和伺服器只需要做一個握手的動作,然後,瀏覽器和伺服器之間就形成了一條快速通道。兩者之間就直接可以資料互相傳送。


瀏覽器透過JavaScript向伺服器發出建立WebSocket連線的請求,連線建立以後,客戶端和伺服器端就可以透過TCP連線直接交換資料。


當獲取Web Socket連線後,你可以透過 send() 方法來向伺服器傳送資料,並透過 onmessage事件來接收伺服器傳回的資料。


對於前端,建立一個WebSocket物件,如下:


var Socket = new WebSocket(url, [protocol] );


說明:第一個引數 url, 指定連線的 URL。第二個引數 protocol 是可選的,指定了可接受的子協議。


WebSocker屬性


以下是WebSocket物件的屬性。假定我們使用了以上程式碼建立了Socket物件:



WebSocket事件


以下是 WebSocket 物件的相關事件。假定我們使用了以上程式碼建立了 Socket 物件:



WebSocket方法


以下是 WebSocket 物件的相關方法。假定我們使用了以上程式碼建立了 Socket 物件:



WebSocket實踐


前面介紹了在瀏覽器端中webSocket的相關知識點,現在我們就來搭建一個後臺對接應用,以實現一個簡單的線上聊天室。


一點知識


後端關於WebSocket的實現是基於JSR356標準的。該標準的出現,統一了

WebSocket的程式碼寫法。只要支援web容器支援JSR356標準,那麼實現方式是一致的。而目前實現方式有兩種,一種是註解方式,另一種就是繼承繼承javax.websocket.Endpoint類了。


常用註解說明


@WebSocketEndpoint

註解是一個類層次的註解,它的功能主要是將目前的類定義成一個websocket伺服器端。註解的值將被用於監聽使用者連線的終端訪問URL地址。


@onOpen

開啟一個新連線,即有新連線時,會呼叫被此註解的方法。


@onClose

關閉連線時呼叫。


@onMessage

當伺服器接收到客戶端傳送的訊息時所呼叫的方法。


@PathParam

接收uri引數的,與@PathVariable功能差不多,可透過url獲取對應值

搭建一個簡易聊天室


0.加入POM依賴。


    org.springframework.boot

    spring-boot-starter-web

    org.springframework.boot

    spring-boot-starter-websocket


1.編寫控制層,對應WebSocket的各事件。同時抽取了個公用類,進行通用方法呼叫。


WebSocketController.java


/**

 * websocket 簡易聊天

 * @author oKong

 *

 */

//由於是websocket 所以原本是@RestController的http形式 

//直接替換成@ServerEndpoint即可,作用是一樣的 就是指定一個地址

//表示定義一個websocket的Server端

@Component

@ServerEndpoint(value = “/my-chat/{usernick}”)

@Slf4j

public class WebSocketController {

     

    /**

     * 連線事件 加入註解

     * @param session

     */

    @OnOpen

    public void onOpen(@PathParam(value = “usernick”) String userNick,Session session) {

        String message = “有新遊客[” + userNick + “]加入聊天室!”;

        log.info(message);

        WebSocketUtil.addSession(userNick, session);    

        //此時可向所有的線上通知 某某某登入了聊天室         

        WebSocketUtil.sendMessageForAll(message);

    }

     

    @OnClose

    public void onClose(@PathParam(value = “usernick”) String userNick,Session session) {

        String message = “遊客[” + userNick + “]退出聊天室!”;

        log.info(message);

        WebSocketUtil.remoteSession(userNick);  

        //此時可向所有的線上通知 某某某登入了聊天室         

        WebSocketUtil.sendMessageForAll(message);

    }

     

    @OnMessage

    public void OnMessage(@PathParam(value = “usernick”) String userNick, String message) {

        //類似群發

        String info = “遊客[” + userNick + “]:” + message;

        log.info(info);

        WebSocketUtil.sendMessageForAll(message);

    } 

     

    @OnError

    public void onError(Session session, Throwable throwable) {

        log.error(“異常:”, throwable);

        try {

            session.close();

        } catch (IOException e) {

            e.printStackTrace();

        }

        throwable.printStackTrace();

    }

 

}


WebSocketUtil.java


public class WebSocketUtil {

 

    /**

     * 簡單使用map進行儲存線上的session

     * 

     */

    private static final Map ONLINE_SESSION = new ConcurrentHashMap<>();

     

    public static void addSession(String userNick,Session session) {

        //putIfAbsent 新增鍵—值對的時候,先判斷該鍵值對是否已經存在

        //不存在:新增,並傳回null

        //存在:不改寫,直接傳回已存在的值

//      ONLINE_SESSION.putIfAbsent(userNick, session);

        //簡單示例 不考慮複雜情況。。怎麼簡單怎麼來了。。

        ONLINE_SESSION.put(userNick, session);

    }

     

    public static void remoteSession(String userNick) {

        ONLINE_SESSION.remove(userNick);

    }

     

    /**

     * 向某個使用者傳送訊息

     * @param session 某一使用者的session物件

     * @param message

     */

    public static void sendMessage(Session session, String message) {

        if(session == null) {

            return;

        }

        // getAsyncRemote()和getBasicRemote()非同步與同步

        Async async = session.getAsyncRemote();

        //傳送訊息

        async.sendText(message);

    }

     

    /**

     * 向所有線上人傳送訊息

     * @param message

     */

    public static void sendMessageForAll(String message) {

        //jdk8 新方法

        ONLINE_SESSION.forEach((sessionId, session) -> sendMessage(session, message));

    }

}


註意點:


  • @ServerEndpoint的value值填寫時,開頭需要加上/,不然會提示路徑無效。

  • 需要加上型別@Component註解,使得能被掃描到。

  • 這裡的session等,都在包javax.websocket包下的,註意區分。


2.編寫主啟動類,主要是加入註解@EnableWebSocket和申明一個Websocket endpoint類。


@SpringBootApplication

@EnableWebSocket

@Slf4j

public class Chapter19Application {

 

    public static void main(String[] args) {

        SpringApplication.run(Chapter19Application.class, args);

        log.info(“Chapter19啟動!”);

    }

     

    /**

     * 會自動註冊使用了@ServerEndpoint註解宣告的Websocket endpoint

     * 要註意,如果使用獨立的servlet容器,

     * 而不是直接使用springboot的內建容器,

     * 就不要註入ServerEndpointExporter,因為它將由容器自己提供和管理。

     */

    @Bean

    public ServerEndpointExporter serverEndpointExporter() {

        return new ServerEndpointExporter();

    }

}


3.啟動應用,利用線上的測試工具進行測試。這裡直接使用了http://coolaf.com/tool/chattest進行測試。當然也可以自己寫一個html了。


首先,輸入我們的服務地址:ws://127.0.0.1:8080/my-chat/okong,連線後就可以看見伺服器傳回的訊息了。



我們再開一個標簽頁,然後繼續以另一個身份進入:



這時,可以看見第一個頁面開的,也收到訊息了。現在我們傳送一條訊息:



然後,其中一個斷開連線:



然後可以愉快聊天了,簡單的一個聊天室就完成了。


參考資料


  1. https://docs.spring.io/spring/docs/4.3.18.RELEASE/spring-framework-reference/htmlsingle/#websocket

  2. https://docs.spring.io/spring-boot/docs/1.5.15.RELEASE/reference/htmlsingle/#boot-features-websockets

  3. http://www.oracle.com/technetwork/articles/java/jsr356-1937161.html

  4. http://www.runoob.com/html/html5-websocket.html


總結


本章節主要是講解了WebSocket的使用。因為有統一標準的存在,編寫webSocket也是很簡單的。對於如何一對一聊天,大家可以自行編寫下,因為知道了對方名稱,就能找出對方的session然後就能傳送訊息了。


最後


目前網際網路上很多大佬都有SpringBoot系列教程,如有雷同,請多多包涵了。本文是作者在電腦前一字一句敲的,每一步都是自己實踐的。若文中有所錯誤之處,還望提出,謝謝。


系列


【關於投稿】


如果大家有原創好文投稿,請直接給公號傳送留言。


① 留言格式:
【投稿】+《 文章標題》+ 文章連結

② 示例:
【投稿】《不要自稱是程式員,我十多年的 IT 職場總結》:http://blog.jobbole.com/94148/

③ 最後請附上您的個人簡介哈~



看完本文有收穫?請轉發分享給更多人

關註「ImportNew」,提升Java技能

贊(0)

分享創造快樂