1. 程式人生 > >Spring 學習——基於Spring WebSocket 和STOMP實現簡單的聊天功能

Spring 學習——基於Spring WebSocket 和STOMP實現簡單的聊天功能

本篇主要講解如何使用Spring websocket 和STOMP搭建一個簡單的聊天功能專案,裡面使用到的技術,如websocket和STOMP等會簡單介紹,不會太深,如果對相關介紹不是很瞭解的,請自行查閱相關知識。
本篇的專案主要是一個學習Spring websocket和STOMP的專案,基於Spring4.0之上。因為Spring4.0之上才支援Websocket。例子比較的簡單,但是總體實現了

  1. 瀏覽器 和伺服器可以正常建立websocket服務
  2. 瀏覽器可以像伺服器訂閱併發送訊息
  3. 瀏覽器可以接收到伺服器推送過來的訊息,即瀏覽器訂閱的訊息

一.簡單介紹Websocket和STOMP

1.websocket介紹

websocket協議是html5的一種新的協議,提供了通過套接字實現全雙工通訊的功能,【全雙工:意味著伺服器可以傳送訊息給瀏覽器,瀏覽器也可以傳送訊息給伺服器】並能夠實現web瀏覽器和伺服器之間的非同步通訊。

它和http通訊機制對比如下圖:

這裡寫圖片描述

  • http每次傳送請求都需要和伺服器建立一次連線, 是一種無狀態的協議。
  • websocket只要第一個建立成功,瀏覽器就可以伺服器進行通訊,是一種有狀態的協議。
  • websocket協議的請求報文和響應報文和http也是有區別的,這裡不做介紹!

2.STOMP介紹

什麼是 STOMP呢?
http是在TCP套接字之上添加了請求-響應模型!
STOMP是在WebSocket之上提供了一個基於幀的線路格式層,用於定義訊息的語義。
STOMP幀由命令、一個或多個頭資訊以及負載所組成!舉例傳送資料的一個STOMP幀:

SEND
destination:/app/marco
content-length:20

{\"message\":\"hello\"}
  • 這裡STOMP的命令是SEND,後面接傳送的目標地址,訊息內容長度,然後是一個空行,最後是傳送內容,這個裡面是一個JSON訊息。
  • 這裡需要注意的是destination,目標地址,訊息會發送到這個目的地,這個目的地有服務端元件來進行處理。
  • Spring使用STOMP需要進行配置,並且Spring為STOMP訊息提供了基於SpringMVC的程式設計模型!

下面進入實戰演練:

1、新增依賴

<!-- 新增Spring依賴 -->
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <!--spring單元測試依賴 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> <scope>test</scope> </dependency> <!-- spring webmvc相關jar --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <!--Spring websocket start--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-messaging</artifactId> <version>${spring.version}</version> </dependency> <!--Spring websocket end--> <!-- For SockJS --> <!-- http://jira.codehaus.org/browse/JACKSON-884 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson.version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>${jackson.version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>${jackson.version}</version> </dependency>

2、新增配置檔案

1.web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
  <display-name>spring-webscoket</display-name>
  <!-- 讀取spring配置檔案 -->


    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:application*.xml</param-value>
    </context-param>


    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- Spring字符集過濾器 -->
    <filter>
        <filter-name>SpringEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
        <async-supported>true</async-supported>
    </filter>
    <filter-mapping>
        <filter-name>SpringEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>



    <!-- springMVC核心配置 -->
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <!--spingMVC的配置路徑 -->
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
        <async-supported>true</async-supported>
    </servlet>
    <!-- 攔截設定 -->
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>


  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

需要注意:< async-supported>true< /async-supported>,在filter和Servlet中都需要新增!

2.spring-mvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:p="http://www.springframework.org/schema/p"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:mvc="http://www.springframework.org/schema/mvc"
  xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.2.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">

    <!-- 掃描controller(controller層注入) -->
   <context:component-scan base-package="com.dufy"/>

     <mvc:annotation-driven />

    <!-- 當在web.xml 中   DispatcherServlet使用 <url-pattern>/</url-pattern> 對映時,能對映靜態資源 -->
    <mvc:default-servlet-handler />  
    <!-- 靜態資源對映 -->
    <mvc:resources mapping="/static/**" location="/WEB-INF/static/"/>


   <!-- 對模型檢視新增前後綴 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/" />
        <property name="suffix" value=".jsp" />
    </bean>


</beans>
3.applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
">

    <!-- Spring WebSocketSession管理容器 -->
    <bean id="webSocketSessionManager" class="com.dufy.webscocket.session.data.DefaultWebSocketSessionManager" />

    <!-- websocket握手攔截器 -->
    <bean id="handshakeInterceptor" class="com.dufy.webscocket.interceptor.ChatHandInteceptor" />

    <!-- WebSocket相關監聽器 -->
    <bean id="sessionConnectedListener" class="com.dufy.webscocket.listener.SessionConnectedListener" />
    <bean id="sessionDisConnectedListener" class="com.dufy.webscocket.listener.SessionClosedListener" />
    <bean id="sessionSubscribeListener" class="com.dufy.webscocket.listener.SessionSubscribeListener" />

</beans>

3、新增程式碼

1.後臺程式碼目錄

這裡寫圖片描述

2.前臺程式碼

(1)建立websocket的jsp頁面:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    String path = request.getContextPath();
    String basePath = request.getServerName() + ":"
            + request.getServerPort();
    String finalPath = "http://" + basePath + path;
    out.print("finalPath = " + finalPath);
%>
<html>
<head>

    <script src="${pageContext.request.contextPath}/recourse/jquery-1.7.2.min.js"></script>
    <script src="${pageContext.request.contextPath}/recourse/stomp.js"></script>
    <script src="${pageContext.request.contextPath}/recourse/sockjs.min.js"></script>
    <script type="text/javascript">
        $(document).ready(function(){
            connect();
            //checkoutUserlist();
        });

        var projectName = "springwebscoket";
        var baseUrl= getBaseUrl();
        var stompClient = null;
        console.log("baseUrl = " + baseUrl);
        //this line.
        function connect() {
            console.log('Connected: ----------------------------------');
            var userid = document.getElementById('name').value;

            var socket = new SockJS(baseUrl + projectName +"/chat");
            console.log("=-="+ socket);
            stompClient = Stomp.over(socket);

            stompClient.connect({}, function(frame) {
                setConnected(true);
                console.log('Connected: ' + frame);

                stompClient.ws.onclose = function (CloseEvent){
                    console.log("ERROR","weboscket close code is " + CloseEvent.code);
                    setConnected(false);
                };

                //系統訂閱訊息
                stompClient.subscribe('/user/queue/system/newMsg', function (greeting) {
                    console.log("------ue/system/newMsg-----------");
                    console.log(greeting);
                    showGreeting(JSON.parse(greeting.body).content);
                });

                //新訊息訂閱
                stompClient.subscribe('/user/queue/chat/newMsg', function (greeting) {
                    console.log("/chat/newMsg" + greeting);
                    $("#showMessage").append("<p>"+JSON.parse(greeting.body).message+"</p>")
                    //showGreeting(JSON.parse(greeting.body).content);
                });

                //訪客訊息響應訂閱
                stompClient.subscribe('/user/queue/chat/msgResponse', function (greeting) {
                    alert("收到資訊");
                });

                //關閉訂閱訊息
                stompClient.subscribe('/user/queue/system/close', function (greeting) {
                    var retCode = JSON.parse(greeting.body).retCode;
                    if(retCode == "000000"){
                        _evaluate.open();
                        disconnect();
                    }
                });


            },function(error){
                var msg = "會話建立失敗,請稍候重試!";
                alert(msg);
                setConnected(false);

            });
        }



        function sendName() {
            var name = document.getElementById('name').value;
            stompClient.send("/app/sendMessage", {}, JSON.stringify({ 'name': name }));
        }

        function disconnect() {
            if (stompClient != null) {
                stompClient.disconnect();
            }
            setConnected(false);
            console.log("Disconnected");
        }


        function setConnected(connected) {
            document.getElementById('connect').disabled = connected;
            document.getElementById('disconnect').disabled = !connected;
            document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden';
            document.getElementById('response').innerHTML = '';
        }

        function showGreeting(message) {
            var response = document.getElementById('response');
            var p = document.createElement('p');
            p.style.wordWrap = 'break-word';
            p.appendChild(document.createTextNode(message));
            response.appendChild(p);
        }


        /**
         * 獲取baseUrl
         * @returns {string}
         */
        function getBaseUrl(){
            var url= window.location.href;
            var num = url.indexOf(projectName);
            var baseUrlStr = url.substring(0,num);
            return baseUrlStr;
        }
    </script>
</head>
<body>
<h1>Welcome</h1> ${name }<h1>訪問此頁面</h1>
<div>
    <div>
        <button id="connect" onclick="connect();">Connect</button>
        <button id="connectAny" onclick="connectAny();">ConnectAny</button>
        <button id="disconnect" disabled="disabled" onclick="disconnect();">Disconnect</button>
    </div>
    <div id="conversationDiv">
        <label>you can send message to WebSocketMessageController[ @MessageMapping("/sendMessage") ]</label><input type="text" id="name" />
        <button id="sendName" onclick="sendName();">Send</button>
        <p id="response"></p>
    </div>

    <div id="showMessage">

    </div>

</div>
</div>
</body>
</html>

(2)伺服器推送訊息的頁面:

<%@ page contentType="text/html; charset=utf-8"%>
<!doctype html>
<html>
<head>
    <title>agent page</title>
    <script src="${pageContext.request.contextPath}/recourse/jquery.js"></script>
</head>
<body>
    <div>
        <input type="text" placeholder="請輸入wsSocketid" id="webSocketId" value="${wsId}"/><br/>
        <input type="text" placeholder="請輸入傳送的內容" id="message"/><br/>
        <button id="sendMsg">傳送</button>
        <button id="resetMessage">清空傳送內容</button>
        <button id="resetid">重置id</button>
    </div>

</body>
<script>

    $("#sendMsg").click(function () {
        var webSocketId = $("#webSocketId").val();
        var message = $("#message").val();
        var url = "${pageContext.request.contextPath}/websocket/notifyMsg";
        $.post(url,{"webSocketId":webSocketId,"message":message},function (data) {
            console.log(data);
            alert(data);

        })
    })
    $("#resetid").click(function () {
        $("#webSocketId").val("");
    })
    $("#resetMessage").click(function () {
        $("#message").val("");
    })

</script>
</html>

4、專案演示

(1).啟動專案,我專案 不是的Application context 為 /springwebscoket ,埠9091,啟動成功訪問:http://localhost:9081/springwebscoket/,顯示如下頁面:
這裡寫圖片描述

(2).點選訪客登入,進行登入,登入成功!建立websocket成功!如圖:
這裡寫圖片描述

(3).建立成功,傳送訊息到伺服器,如圖:
這裡寫圖片描述

(4).建立成功,接收伺服器發來的訊息,如圖:
這裡寫圖片描述

三.總結

上面的專案裡面的程式碼沒有具體進行講解,因為專案中可以執行起來,並且裡面的邏輯也比較簡單,如有不懂的地方可以直接對專案進行debug除錯,一步步看,一步步學習,建議學習之前,要對websocket和STOMP有了解!
如果您對專案中有不懂的地方,歡迎加入我的QQ群:600756207,和我進行溝通!共同成長!

四.參考

官方文件

歡迎訪問我的csdn部落格,我們一同成長!

不管做什麼,只要堅持下去就會看到不一樣!在路上,不卑不亢!