SpringBoot之集成WebSocket


websocket是什么不做介紹。開發環境:jdk1.8,win7_64旗艦版,idea
 
1、初始化一個springboot項目
 
2、加入websocket依賴
<!-- springboot的websocket依賴 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

pom.xml如下:

<dependencies>
        <!-- 模板引擎 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!-- web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- websocket -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <!---->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>

        <!-- lombok工具 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- 內置tomcat -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>
        <!-- 測試 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>

 

3、編寫websocket的服務端

    3.1、WebSocketEndPoint是websocket服務端的核心
      @PathParam是javax.websocket.server下的注解,是將路徑中綁定的占位符的值取出來
  在url中使用key和name,是想通過key和name對websocket的連接進行訪問控制,這個key可以是用戶登錄后服務器給用戶的令牌,通過令牌和和name進行權限驗證(自己寫攔截器或者繼承權限框架實現),還可以通過key和name生成唯一值來進行在線websocket
連接的維護<(key+name), websocketSession>, 當然,我在這里沒有這樣做。
package com.geniuses.sewage_zero_straight.net.websocket;

import com.geniuses.sewage_zero_straight.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;

import static com.geniuses.sewage_zero_straight.net.websocket.WebSocketPool.*;
import static com.geniuses.sewage_zero_straight.net.websocket.WebSocketHandler.createKey;

@Slf4j
@Component
@ServerEndpoint("/net/websocket/{key}/{name}")//表明這是一個websocket服務的端點
public class WebSocketEndPoint {


    private static UserService userService;

    @Autowired
    public void setUserService(UserService userService){
        WebSocketEndPoint.userService = userService;
    }

    @OnOpen
    public void onOpen(@PathParam("key") String key, @PathParam("name") String name,  Session session){
        log.info("有新的連接:{}", session);
        add(createKey(key, name), session);
        WebSocketHandler.sendMessage(session, key + name);
        log.info("在線人數:{}",count());
        sessionMap().keySet().forEach(item -> log.info("在線用戶:", item));
        for (Map.Entry<String, Session> item : sessionMap().entrySet()){
            log.info("12: {}", item.getKey());
        }
    }

    @OnMessage
    public void onMessage(String message){
        log.info("有新消息: {}", message);
    }

    @OnClose
    public void onClose(@PathParam("key") String key, @PathParam("name") String name,Session session){
        log.info("連接關閉: {}", session);
        remove(createKey(key, name));
        log.info("在線人數:{}",count());
        sessionMap().keySet().forEach(item -> log.info("在線用戶:", (item.split("@"))[1]));
        for (Map.Entry<String, Session> item : sessionMap().entrySet()){
            log.info("12: {}", item.getKey());
        }
    }

    @OnError
    public void onError(Session session, Throwable throwable){
        try {
            session.close();
        } catch (IOException e) {
            log.error("onError Exception: {}", e);
        }
        log.info("連接出現異常: {}", throwable);
    }

}

 

  3.2、WebSocketPool是websocket的在線連接池

package com.geniuses.sewage_zero_straight.net.websocket;

import lombok.extern.slf4j.Slf4j;

import javax.websocket.Session;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class WebSocketPool {

    //在線用戶websocket連接池
    private static final Map<String, Session> ONLINE_USER_SESSIONS = new ConcurrentHashMap<>();

    /**
     * 新增一則連接
     * @param key
     * @param session
     */
    public static void add(String key, Session session){
        if (!key.isEmpty() && session != null){
            ONLINE_USER_SESSIONS.put(key, session);
        }
    }

    /**
     * 根據Key刪除連接
     * @param key
     */
    public static void remove(String key){
        if (!key.isEmpty()){
            ONLINE_USER_SESSIONS.remove(key);
        }
    }

    /**
     * 獲取在線人數
     * @return
     */
    public static int count(){
        return ONLINE_USER_SESSIONS.size();
    }

    /**
     * 獲取在線session池
     * @return
     */
    public static Map<String, Session> sessionMap(){
        return ONLINE_USER_SESSIONS;
    }
}

 

 3.3、WebSocketHandler是websocket的動作處理工具
package com.geniuses.sewage_zero_straight.net.websocket;

import lombok.extern.slf4j.Slf4j;

import javax.websocket.RemoteEndpoint;
import javax.websocket.Session;
import java.io.IOException;
import static com.geniuses.sewage_zero_straight.net.websocket.WebSocketPool.sessionMap;

@Slf4j
public class WebSocketHandler {


    /**
     * 根據key和用戶名生成一個key值,簡單實現下
     * @param key
     * @param name
     * @return
     */
    public static String createKey(String key, String name){
        return key + "@" + name;
    }

    /**
     * 給指定用戶發送信息
     * @param session
     * @param msg
     */
    public static void sendMessage(Session session, String msg){
        if (session == null)
            return;
        final RemoteEndpoint.Basic basic = session.getBasicRemote();
        if (basic == null)
            return;
        try {
            basic.sendText(msg);
        } catch (IOException e) {
            log.error("sendText Exception: {}", e);
        }
    }


    /**
     * 給所有的在線用戶發送消息
     * @param message
     */
    public static void sendMessageAll(String message){
        log.info("廣播:群發消息");
        sessionMap().forEach((key, session) -> sendMessage(session, message));
    }
}

 

4、前端訪問實現

    4.1、index.html,頁面引用了jquery和bootstrap樣式,請自行應用

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.springframework.org/schema/mvc">
<head>
    <meta charset="UTF-8">
    <title>chat room websocket</title>
    <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">
    <script th:src="@{/js/jquery-3.3.1.min.js}" ></script>
</head>
    <body class="container" style="width: 60%">
        <div class="form-group" ></br>
            <h5>聊天室</h5>
            <textarea id="message_content"  class="form-control"  readonly="readonly" cols="50" rows="10"></textarea>
        </div>
        <div class="form-group" >
            <label for="in_user_name">用戶姓名 &nbsp;</label>
            <input id="in_user_name" value="" class="form-control" /></br>
            <button id="user_join" class="btn btn-success" >加入聊天室</button>
            <button id="user_exit" class="btn btn-warning" >離開聊天室</button>
        </div>
        <div class="form-group" >
            <label for="in_room_msg" >群發消息 &nbsp;</label>
            <input id="in_room_msg" value="" class="form-control" /></br>
            <button id="user_send_all" class="btn btn-info" >發送消息</button>
        </div>
    </body>
    <<script type="text/javascript">
        $(document).ready(function(){
            var urlPrefix ='ws://192.168.2.156:8080/net/websocket/12/';
            var ws = null;
            $('#user_join').click(function(){
                var username = $('#in_user_name').val();
                var url = urlPrefix + username;
                ws = new WebSocket(url);
                ws.onopen = function () {
                    console.log("建立 websocket 連接...");
                };
                ws.onmessage = function(event){
                    //服務端發送的消息
                    $('#message_content').append(event.data+'\n');
                };
                ws.onclose = function(){
                    $('#message_content').append('用戶['+username+'] 已經離開聊天室!' + '\n');
                    console.log("關閉 websocket 連接...");
                }
            });
            //客戶端發送消息到服務器
            $('#user_send_all').click(function(){
                var msg = $('#in_room_msg').val();
                if(ws){
                    ws.send(msg);
                }
            });
            // 退出聊天室
            $('#user_exit').click(function(){
                if(ws){
                    ws.close();
                }
            });
        })
    </script>
</html>

    

  4.2、頁面訪問控制器,由此來訪問index.html

package com.geniuses.sewage_zero_straight.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@RequestMapping("/view")
@Controller
public class ViewController {

    /**
     * 返回首頁
     * @return
     */
    @GetMapping("/index")
    public String index(){
        return "index";
    }
}

5、websocket配置

package com.geniuses.sewage_zero_straight.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
@EnableWebSocket
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
}
 
6、注意:
    在使用了@ServerEndpoint注解的類是無法直接使用@Autowired的,因為@ServerEndpoint表明當前類是websocket的服務端點,在spring容器啟動時會初始化一次該類,當有新的websocket連接的時候,也會進行該類實例的創建(每一次連接時都會創建一個實例),所以在第二次往后創建該類實例的時候,就無法進行有效的@Autowired了,此時發現,即便第一次注入是有效的,但是也沒有什么用。這個時候,將需要注入的變量置為類的變量,提供一個set方法(該方法為實例方法),在set方法上面進行依賴注入,這樣就可以進行有效的注入了。
 
7、這里只是websocket的簡單實現,更多情況...


免責聲明!

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



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