【Java Web開發學習】Spring MVC整合WebSocket通信


Spring MVC整合WebSocket通信

目錄

============================================================================

1、使用 Spring 的低層級 WebSocket API

2、使用JSR356定義的WebSocket規范

3、

4、

============================================================================

轉載:http://www.cnblogs.com/yangchongxing/p/8474256.html

WebSocket是HTML5開始提供的一種瀏覽器與服務器間進行全雙工通訊的網絡技術。依靠這種技術可以實現客戶端和服務器端的長連接,雙向實時通信。
特點:事件驅動、異步,使用ws或者wss協議的客戶端socket,能夠實現真正意義上的推送功能
缺點:少部分瀏覽器不支持,瀏覽器支持的程度與方式有區別。

參考資料:https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket

構造函數

WebSocket(url[, protocols])  返回一個 WebSocket 對象

 常量

名稱 作用
WebSocket.CONNECTING 0 正嘗試與服務器建立連接
WebSocket.OPEN 1 與服務器已經建立連接
WebSocket.CLOSING 2 正在關閉與服務器的連接
WebSocket.CLOSED 3 已經關閉了與服務器的連接

 

 

 

 

 

 

 

WebSocket實例的readyState屬性對照判斷狀態

屬性

binaryType 使用二進制的數據類型連接
bufferedAmount 只讀 未發送至服務器的字節數
extensions 只讀 服務器選擇的擴展
onclose 用於指定連接關閉后的回調函數
onerror 用於指定連接失敗后的回調函數
onmessage 用於指定當從服務器接受到信息時的回調函數
onopen 用於指定連接成功后的回調函數
protocol 只讀 服務器選擇的下屬協議
readyState 只讀 當前的鏈接狀態
url 只讀 WebSocket 的絕對路徑

 

 

 

 

 

 

 

 

 

 

 

 

 

方法

WebSocket.close([code[, reason]]) 關閉當前鏈接

WebSocket.send(data) 向服務器發送數據


Java服務端:
JSR356定義了WebSocket的規范,JSR356 的 WebSocket 規范使用 javax.websocket.*的 API,可以將一個普通 Java 對象(POJO)使用 @ServerEndpoint 注釋作為 WebSocket 服務器的端點。

 

1、使用 Spring 的低層級 WebSocket API

實現WebSocketHandler接口、該接口包含5個方法,用於處理不同的事件

public interface WebSocketHandler {
    void afterConnectionEstablished(WebSocketSession session) throws Exception;
    void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception;
    void handleTransportError(WebSocketSession session, Throwable exception) throws Exception;
    void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception;
    boolean supportsPartialMessages();
}

比實現接口更為簡單的方式是擴展AbstractWebSocketHandler抽象類,下面是抽象類的代碼

public abstract class AbstractWebSocketHandler implements WebSocketHandler {
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
    }
    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
        if (message instanceof TextMessage) {
            handleTextMessage(session, (TextMessage) message);
        }
        else if (message instanceof BinaryMessage) {
            handleBinaryMessage(session, (BinaryMessage) message);
        }
        else if (message instanceof PongMessage) {
            handlePongMessage(session, (PongMessage) message);
        }
        else {
            throw new IllegalStateException("Unexpected WebSocket message type: " + message);
        }
    }
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
    }
    protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
    }
    protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
    }
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
    }
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
    }
    @Override
    public boolean supportsPartialMessages() {
        return false;
    }
}

我們以文本消息為例直接繼承TextWebSocketHandler類
TextWebSocketHandler繼承自AbstractWebSocketHandler類用來處理文本消息。
BinaryWebSocketHandler繼承自AbstractWebSocketHandler類用來處理二進制消息。

public class CommoditySocket extends TextWebSocketHandler {
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        System.out.println("open...");
    }
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        System.out.println("Closed");
    }
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        super.handleTextMessage(session, message);
        System.out.println("收到消息:" + message.getPayload());
    }
}

消息處理類完成了,來看配置文件

首先追加websocket命名空間

<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"
    xmlns:task="http://www.springframework.org/schema/task" xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.3.xsd
        http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.3.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
    

 再追加如下配置,注意path的路徑這個是請求的地址,ws://域名:端口/項目名/socket/text

<websocket:handlers>
    <websocket:mapping handler="textHandler" path="/socket/text"/>
</websocket:handlers>

<bean id="textHandler" class="cn.ycx.web.socket.TextHandler"></bean>

瀏覽器端使用標准的WebSocket

var url = 'ws://' + window.location.host + '/ycxxml/socket/text';  //配置文件中配的path有關
var socket = new WebSocket(url);
socket.onopen = function() {
    console.log("open...");
    socket.send("start talk...")
}
socket.onmessage = function(e) {
    console.log("服務器發來:" + e.data);
    document.write("" + e.data + "<br/>");
}
socket.onclose = function() {
    console.log("close...");
}

 

2、使用JSR356定義的WebSocket規范

@ServerEndpoint(value="/websocket/commodity/{userId}", configurator = SpringConfigurator.class)

特別注意:configurator = SpringConfigurator.class,若要進行對象注入此段代碼必須加

表示將普通的Java對象注解為WebSocket服務端點,運行在ws://[Server端IP或域名]:[Server端口]/項目/websocket/commodity/{userId}的訪問端點,客戶端瀏覽器已經可以對WebSocket客戶端API發起HTTP長連接了。
@OnOpen
在新連接建立時被調用。@PathParam可以傳遞url參數,滿足業務需要。Session表明兩個WebSocket端點對話連接的另一端,可以理解為類似HTTPSession的概念。
@OnClose
在連接被終止時調用。參數closeReason可封裝更多細節,如為什么一個WebSocket連接關閉。
@OnMessage
注解的Java方法用於接收傳入的WebSocket信息,這個信息可以是文本格式,也可以是二進制格式

服務器端代碼:

package cn.ycx.web.socket;

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import javax.websocket.CloseReason;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

import org.springframework.web.socket.server.standard.SpringConfigurator;

import com.alibaba.fastjson.JSON;
/**
 * 交易
 * @author 楊崇興
 *
 */
@ServerEndpoint(value="/websocket/commodity/{fromUserId}/{toUserId}", configurator = SpringConfigurator.class)
public class WebSocketServer {
    // 已經建立鏈接的對象緩存起來
    private static ConcurrentMap<Integer, WebSocketServer> serverMap = new ConcurrentHashMap<Integer, WebSocketServer>();
    // 當前session
    private Session currentSession;
    @OnOpen
    public void onOpen(Session session, @PathParam("fromUserId") int fromUserId, @PathParam("toUserId") int toUserId) throws IOException {
        this.currentSession = session;
        serverMap.put(fromUserId, this);//建立鏈接時,緩存對象
    }
    @OnClose
    public void onClose(Session session, CloseReason reason) {
        System.out.println(reason.toString());
        if (serverMap.containsValue(this)) {
            Iterator<Integer> keys = serverMap.keySet().iterator();
            int userId = 0;
            while(keys.hasNext()) {
                userId = keys.next();
                if (serverMap.get(userId) == this) {
                    serverMap.remove(userId, this);//關閉鏈接時,刪除緩存對象
                }
            }
        }
        this.currentSession = null;
        try {
            session.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    @OnMessage()
    @SuppressWarnings("unchecked")
    public void onMessage(String json) {
        HashMap<String, String> map =  JSON.parseObject(json, HashMap.class);
        int fromUserId = Integer.parseInt(map.get("fromUserId"));
        int toUserId = Integer.parseInt(map.get("toUserId"));
        String content = map.get("content").toString();
        WebSocketServer server = serverMap.get(toUserId);//若存在則用戶在線,否在用戶不在線
        if (server != null && server.currentSession.isOpen()) {
            if (fromUserId != toUserId) {
                try {
                    server.currentSession.getBasicRemote().sendText(content);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    @OnError
    public void onError(Throwable t) {
        t.printStackTrace();
    }

}

注意:修改了原來的問題,serverMap對象全局緩存了已經鏈接上的對象,通過這對象也能判斷用戶是否在線。

注意:使用spring boot是要定義ServerEndpointExporter

If you want to use @ServerEndpoint in a Spring Boot application that used an embedded container, you must declare a single ServerEndpointExporter @Bean, as shown in the following example:

如果想要在使用嵌入式容器的Spring Boot應用中使用@ServerEndpoint,則必須聲明單個ServerEndpointExporter @Bean,如下例所示:

@Bean
public ServerEndpointExporter serverEndpointExporter() {
    return new ServerEndpointExporter();
}

The bean shown in the preceding example registers any @ServerEndpoint annotated beans with the underlying WebSocket container. When deployed to a standalone servlet container, this role is performed by a servlet container initializer, and the ServerEndpointExporter bean is not required.

前面示例中所示任何在WebSocket容器中使用@ServerEndpoint注解標注的beans。當部署到獨立的servlet容器時,此角色由servlet容器初始值設定項執行,並且不需要 ServerEndpointExporter bean

瀏覽器端:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">


<title>socketjs</title>
</head>
<body>
發送者:<input id="fromUserId" value="2">
接收者:<input id="toUserId" value="3">
<button type="button" onclick="openClick();">打開</button>
<button type="button" onclick="closeClick();">關閉</button><br/>
<input id="message" value="---"/>
<button type="button" onclick="sendClick();">發送</button>
<div id="content"></div>
<script>
var socket;
var t;
function openClick() {
    connection();
}
function closeClick() {
    if (socket) {
        socket.close();
    }
}
function sendClick() {
    var fromUserId = document.getElementById("fromUserId").value;
    var toUserId = document.getElementById("toUserId").value;
    var content = document.getElementById("message").value;
    var obj = {
            "fromUserId":fromUserId,
            "toUserId":toUserId,
            "content":content
    };
    document.getElementById("content").innerHTML = document.getElementById("content").innerHTML + '<br/>' + fromUserId + "說:" + content;
    socket.send(JSON.stringify(obj));
    console.log(fromUserId + "說:" + JSON.stringify(content));
}

var connection = function() {
    console.log("connection()");
    var fromUserId = document.getElementById("fromUserId");
    var toUserId = document.getElementById("toUserId");
    var url = 'ws://' + window.location.host + '/ycxcode/websocket/commodity/{' + fromUserId.value + '}/{' + toUserId.value + '}';
    socket = new WebSocket(url);
    socket.onopen = onopen;
    socket.onmessage = onmessage;
    socket.onclose = onclose;
    socket.onerror = onerror;
}
//重連
var reconnection = function() {
    //與服務器已經建立連接
    if (socket && socket.readyState == 1) {
        clearTimeout(t);
    } else {
        //已經關閉了與服務器的連接
        if (socket.readyState == 3) {
            connection();
        }
        //0正嘗試與服務器建立連接,2正在關閉與服務器的連接
        t = setTimeout(function() {
            reconnection();
            }, 1000);
    }
}
var onopen = function() {
    console.log("onopen()");
}
var onclose = function() {
    console.log("onclose()");
    reconnection();
}
var onmessage = function(e) {
    console.log("onmessage()");
    if (e.data === "") return;
    var toUserId = document.getElementById("toUserId").value;
    document.getElementById("content").innerHTML = document.getElementById("content").innerHTML + '<br/>' + toUserId + "說:" + e.data;
    console.log(toUserId + "說:" + e.data);
}
var onerror = function() {
    console.log("error...");
    reconnection();
}


</script>
</body>
</html>

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM