1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        spring-boot之webSocket · 上

        共 15652字,需瀏覽 32分鐘

         ·

        2021-07-31 01:58

        前言

        昨天我們已經(jīng)分享完了security的相關(guān)知識(shí)點(diǎn),所以從今天開始我們要開始學(xué)習(xí)spring-boot另一個(gè)組件——webSocket。

        websocket也算是spring-boot的一個(gè)核心組件,目前我能想到的應(yīng)用場(chǎng)景就是群聊,所以我們今天的內(nèi)容核心就是搭建一個(gè)簡(jiǎn)易版的網(wǎng)絡(luò)聊天室。

        webSocket

        websocket是什么

        在開始正文之前,我們先看下什么是webSocket,下面是我在一本springboot書籍上找到的解釋:

        WebSocket協(xié)議是基于 TCP的一種新的網(wǎng)絡(luò)協(xié)議 。它實(shí)現(xiàn)了瀏覽器與服務(wù)器全雙工( full-duplex )通信一一允許服務(wù)器主動(dòng)發(fā)送信息給客戶端,這樣就可以實(shí)現(xiàn)從客戶端發(fā)送消息到服務(wù)器 ,而服務(wù)器又可以轉(zhuǎn)發(fā)消息到客戶端,這樣就能夠?qū)崿F(xiàn)客戶端之間的交互。對(duì)于WebSocket的 開發(fā) ,Spring也提供了 良好 的支持 。目前很多瀏覽器己經(jīng)實(shí)現(xiàn)了Web Socket協(xié)議 ,但是依舊存在著很多瀏覽器沒有實(shí)現(xiàn)該協(xié)議,為了 兼容那 些沒有實(shí)現(xiàn)該協(xié)議的瀏覽器 , 往往還需要通過 STOMP 協(xié)議來完成這些兼容。

        簡(jiǎn)單來說,webSocket就是一種新的網(wǎng)絡(luò)協(xié)議,在這種協(xié)議的加持下,運(yùn)行服務(wù)端給客戶端直接發(fā)送消息,而且服務(wù)器也可以把消息轉(zhuǎn)發(fā)給客戶端。

        在以前的網(wǎng)絡(luò)協(xié)議中,服務(wù)端只能被動(dòng)接受客戶端的請(qǐng)求,然后才能給客戶端發(fā)送數(shù)據(jù),但是有了webSocket協(xié)議,我們就可以實(shí)現(xiàn)類似于打電話這樣的雙工通信,確實(shí)方便了很多。

        簡(jiǎn)易聊天室

        下面我們通過webSocket來搭建一個(gè)簡(jiǎn)易的網(wǎng)絡(luò)聊天室。

        項(xiàng)目依賴

        首先創(chuàng)建一個(gè)spring-boot項(xiàng)目,然后引入websocket的依賴:

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

        同時(shí)我還加入了security、thymeleaf等附屬依賴:

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

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

        這兩個(gè)依賴就不過多說明了,security昨天才分享完,還是熱乎的。

        websocket配置類

        websocket的配置比較簡(jiǎn)單,主要就是創(chuàng)建一個(gè)服務(wù)端實(shí)例,就相當(dāng)于往容器中注入了一個(gè)ServerEndpointExporter實(shí)例對(duì)象。

        @Configuration
        public class WebSocketConfig {
            @Bean
            public ServerEndpointExporter serverEndpointExporter() {
                return new ServerEndpointExporter();
            }
        }
        websokcet服務(wù)實(shí)現(xiàn)

        這里就是websocket服務(wù)的關(guān)鍵,也就是服務(wù)提供者。

        @ServerEndpoint("/ws")
        @Service
        public class WebSocketService {
            private final Logger logger = LoggerFactory.getLogger(WebSocketService.class);

            private Map<String, String> nameMap = Maps.newHashMap();

            {
                nameMap.put("nezha""哪吒");
                nameMap.put("pangu""盤古");
                nameMap.put("zhongkui""鐘馗");
                nameMap.put("fuxi""伏羲");
                nameMap.put("shennongshi""神農(nóng)氏");
                nameMap.put("kuafu""夸父");
                nameMap.put("nvwa""女媧");
                nameMap.put("jiangziya""姜子牙");
                nameMap.put("jingwei""精衛(wèi)");
            }

            // 在線數(shù)量
            private static AtomicInteger onlineCount = new AtomicInteger(0);
         // 保存已建立連接的客戶端(在線)
            private static CopyOnWriteArraySet<WebSocketService> webSocketServiceSet = Sets.newCopyOnWriteArraySet();

            private Session session;

            public Session getSession() {
                return session;
            }

            public void setSession(Session session) {
                this.session = session;
            }

            @OnOpen
            public void onOpen(Session session) {
                String name = nameMap.get(session.getUserPrincipal().getName());
                this.session = session;
                webSocketServiceSet.add(this);
                addOnlineCount();
                logger.info("有新連接加入!當(dāng)前在線人數(shù)為: {}", onlineCount.get());
                webSocketServiceSet.parallelStream().forEach(item -> {
                    try {
                        sendMessage(item.getSession(), String.format("%s加入群聊!", name));
                    } catch (Exception e) {
                        logger.error("發(fā)送消息異常:", e);
                    }
                });
            }

            @OnMessage
            public void onMessage(String message, Session session) {
                logger.info("來自客戶端的消息:{}", message);
                webSocketServiceSet.parallelStream().forEach(item -> {
                    String name = nameMap.get(session.getUserPrincipal().getName());
                    logger.info("{}發(fā)送了一條消息:{}", name, message);
                    try {
                        item.sendMessage(item.getSession(), String.format("%s:%s", name, message));
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                });
            }

            @OnClose
            public void onClose() {
                webSocketServiceSet.remove(this);
                subOnlineCount();
            }

            @OnError
            public void onError(Session session, Throwable t) {
                logger.error("發(fā)生錯(cuò)誤:", t);
            }

            /**
             * 在線人數(shù)加一
             */

            private void addOnlineCount() {
                onlineCount.incrementAndGet();
            }

            /**
             * 在線人數(shù)減一
             */

            private void subOnlineCount() {
                onlineCount.decrementAndGet();
            }

            private void sendMessage(Session session, String message) throws IOException {
                session.getBasicRemote().sendText(message);
            }
        }

        @ServerEndpoint注解制定了我們服務(wù)的節(jié)點(diǎn)路徑,這樣也確定了我們wesocket服務(wù)的訪問地址:

        ws://localhost:8080/ws

        地址中的ws表示協(xié)議類別,也就是websocket的縮寫,緊跟著的是我們springboot服務(wù)的地址(主機(jī)、端口等),然后就是我們的websocket的節(jié)點(diǎn)地址。

        @service注解也就是我們最常用的服務(wù)注解,就是把他標(biāo)記成springboot可以管理的組件,沒有這個(gè)注解,websocket是訪問不到的:

        緊接著,我們寫了四個(gè)監(jiān)聽方法,方法上都有對(duì)應(yīng)的注解標(biāo)注:

        • OnOpen:客戶端首次連接服務(wù)端時(shí)會(huì)調(diào)用該方法
        • OnMessage:客戶端發(fā)送消息時(shí)會(huì)調(diào)用該方法
        • OnClose:客戶端斷開連接時(shí),會(huì)調(diào)用該方法
        • OnError:發(fā)生錯(cuò)誤時(shí)會(huì)調(diào)用該方法
        用戶登錄配置

        為了更好的演示,我加入security組件,這樣用戶登錄之后,session中就保留了用戶的用戶信息,方便前端對(duì)數(shù)據(jù)進(jìn)行展示:

        @Configuration
        public class SecurityConfig extends WebSecurityConfigurerAdapter {
            @Autowired
            private PasswordEncoder passwordEncoder;

            @Override
            protected void configure(AuthenticationManagerBuilder auth) throws Exception {
                auth.inMemoryAuthentication().withUser("zhongkui").password(passwordEncoder.encode("123456")).roles("user")
                        .and().withUser("fuxi").password(passwordEncoder.encode("123456")).roles("user")
                        .and().withUser("pangu").password(passwordEncoder.encode("123456")).roles("user")
                        .and().withUser("nezha").password(passwordEncoder.encode("123456")).roles("user")
                        .and().withUser("nvwa").password(passwordEncoder.encode("123456")).roles("user")
                        .and().withUser("jiangziya").password(passwordEncoder.encode("123456")).roles("user")
                .and().passwordEncoder(passwordEncoder);
            }

            @Override
            protected void configure(HttpSecurity http) throws Exception {
                http.authorizeRequests()
                        .anyRequest().authenticated()
                        .and()
                        .formLogin().and()
                        .httpBasic()
                        .and().logout().logoutUrl("/logout");
            }

            @Bean
            public PasswordEncoder passwordEncoder() {
                return new BCryptPasswordEncoder();
            }

        }

        websocket服務(wù)中,我還構(gòu)建了用戶名和用戶姓名的映射,這樣在用戶建立連接的時(shí)候或者發(fā)送消息的時(shí)候,我就可以根據(jù)session的用戶名拿到用戶的姓名了。

        前端頁(yè)面實(shí)現(xiàn)

        這里最核心的就是websocket連接的那段js了:

        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>test page</title>
        </head>
        <body>
        websocket測(cè)試<br>
        <input id = "message" type="text">
        <button onclick="sendMessage()">發(fā)送消息</button>
        <button onclick="closeWebSocket()">關(guān)閉websocket連接</button>
        <button onclick="logout()">退出登錄</button>
        <div id="context"></div>

        <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
        <script type="application/javascript">
            function logout({
                closeWebSocket();
                $.ajax({
                    url"/logout",
                    type"POST",
                    successfunction (rsp{
                        console.log("退出登錄成功")
                        console.log(rsp)
                    }
                })
            }

            var websocket = null;
            // 判斷當(dāng)前瀏覽榕是否支持 WebSocket
            if ('WebSocket' in window) {
                // 創(chuàng)建 WebSocket 對(duì)象,連接服務(wù)器端點(diǎn)
                websocket = new WebSocket("ws://localhost:8080/ws");
            } else {
                alert('Not support websocket')
            }
            // 連接發(fā)生錯(cuò)誤的 回調(diào)方法
            websocket.onerror = function ({
                appendMessage("error");
            }
            // 連接成功建立的回調(diào)方法
            websocket.onopen = function (event{
                    appendMessage("open ");
                }
            // 接收到消息的回調(diào)方法
            websocket.onmessage = function (event{
                appendMessage(event.data);
            }
            // 連接關(guān)閉的回調(diào)方法
            websocket.onclose = function ({
                    appendMessage(" close ");
                }
            // 監(jiān)聽窗口關(guān)閉事件,當(dāng)窗口關(guān)閉時(shí),主動(dòng)關(guān)閉 websocket 連接
            // 防止連接還沒斷開就關(guān)閉窗口,server 端會(huì)拋異常
            window.onbeforeunload = function ({
                websocket.close();
            }

            // 將消息顯示在網(wǎng)頁(yè)上
            function appendMessage(message{
                var context = $("#context").html() + "<br/>" + message;
                $("#context").html(context);
            }

            // 關(guān)閉連接
            function closeWebSocket({
                websocket.close();
                logout();
            }

            // 發(fā)送消息
            function sendMessage({
                var message = $("#message").val();
                websocket.send(message);
            }
        </script>

        </body>
        </html>

        首先我們判斷瀏覽器是否支持WebSocket,如果支持會(huì)建立websocket連接,然后設(shè)定WebSocket的一些回調(diào)函數(shù),和服務(wù)器端對(duì)應(yīng),而且頁(yè)面還是比較簡(jiǎn)單的。

        測(cè)試

        下面我們簡(jiǎn)單測(cè)試下,我們分別登錄三個(gè)賬號(hào):nezhanvwa、伏羲,然后用三個(gè)賬號(hào)分別發(fā)送消息:

        效果還是可以的,首先是哪吒三太子加入群聊,然后時(shí)女媧加入群聊,然后他們分別發(fā)送消息,接著伏羲加入群聊,發(fā)送消息。第一個(gè)進(jìn)群的人,會(huì)收到后面進(jìn)群的所有人的消息,是不是和我們的微信差不多呢?

        總結(jié)

        websocket還是蠻有意思的,而且很容易上手。如果你有做一款自己的聊天工具,那么websocket應(yīng)該是最佳選擇,相比于socket,它更輕量,也更靈活,相比于傳統(tǒng)的http通信,它支持雙工通信。

        總之,用websocket做一款聊天工具,真的是太簡(jiǎn)單了。后面有時(shí)間的話,用它做一個(gè)簡(jiǎn)易版的微信。好了,今天就先到這里吧!

        最后,附上今天項(xiàng)目的源碼地址,有興趣的小伙伴可以自己動(dòng)手練練,還挺有意思的:

        https://github.com/Syske/learning-dome-code/tree/dev/sping-boot-websocket-demo
        - END -


        瀏覽 32
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            日本一级毛一片免费视频 | 国产18女人水真多免费看 | 亚洲最大福利视频 | 久久国品片 | 婷婷A片 成人A片一级 | 日本夫妻性生活片 | 航空服务一级A片视频 | 中国女人操逼 | 久99久热| 翁虹最裸三级未删减 |