Spring 學習——基於Spring WebSocket 和STOMP實現簡單的聊天功能
本篇主要講解如何使用Spring websocket 和STOMP搭建一個簡單的聊天功能專案,裡面使用到的技術,如websocket和STOMP等會簡單介紹,不會太深,如果對相關介紹不是很瞭解的,請自行查閱相關知識。
本篇的專案主要是一個學習Spring websocket和STOMP的專案,基於Spring4.0之上。因為Spring4.0之上才支援Websocket。例子比較的簡單,但是總體實現了
- 瀏覽器 和伺服器可以正常建立websocket服務
- 瀏覽器可以像伺服器訂閱併發送訊息
- 瀏覽器可以接收到伺服器推送過來的訊息,即瀏覽器訂閱的訊息
一.簡單介紹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部落格,我們一同成長!
“不管做什麼,只要堅持下去就會看到不一樣!在路上,不卑不亢!”