使用SpringBoot及Construct2的WebSocket製作聯機遊戲(一)

 

 

一、介紹

服務端:SpringBoot框架下的WebSocket實現

客戶端:Construct2使用官方插件WebSocket實現

業務:連接、發送信息、接收信息

二、服務端實現

1、導入相關依賴

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-websocket -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
            <version>2.1.2.RELEASE</version>
        </dependency>

2、編寫配置類

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

/**
 * websocket配置類
 */
@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
}

如果沒有編寫配置類,則會出現

“Error in connection establishment: net::ERR_CONNECTION_REFUSED”

這個報錯,通常是配置文件錯誤或者缺乏配置,因爲websocket的連接需要通過攔截器來進行“分配到特定的url“,如果沒有配置則會出現404或者403錯誤。

3、編寫主程序

(1)主程序註解:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;

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

/**
 * websocket服務端控制
 */
@ServerEndpoint("/websocket/{playerId}")
@Component
public class WebSocketServer {

    private Logger logger = LoggerFactory.getLogger(this.getClass());
    /**
     * 在線人數
     */
    public static int onLineNumber = 0;
    /**
     * 以玩家id爲Key websocket爲對象保存下來
     */
    private static Map<Integer,WebSocketServer> clients = new ConcurrentHashMap<>();
    /**
     * 會話
     */
    private Session session;
    /**
     * 玩家對象
     */
    private Player player;
}

講解:

1、websocket的訪問url形式不是使用@RequestMapping而是使用@ServerEndpoint

2、使用@Component將其加入到SpringBoot自帶的Tomcat容器中

3、日誌初始化(注意導入的包)

4、每個WebSocket都擁有一個特有的會話Session,使用Session來進行信息接收和發送,當建立WebSocket連接時就會生成一個Session

5、每個新的連接都會初始化一個WebSocket對象

(2)在WebSocket中使用註解@Autowired

這裏要感謝 解決spring boot websocket無法注入bean的問題 給出的解決方案

    private static PlayerService playerService;
    @Autowired
    public void setPlayerService(PlayerService playerService){
        WebSocketServer.playerService = playerService;
    }

(3)響應連接

    /**
     * 建立連接
     * @param playerId
     * @param session
     */
    @OnOpen
    public void onOpen(@PathParam("playerId")int playerId,Session session){
        onLineNumber ++;
        this.player = playerService.find(playerId).get(0);
        this.session = session;
        logger.info("當前連接客戶id:"+session.getId()+" 用戶名:"+player.getPlayerName());
        logger.info("新玩家加入!當前在線人數:"+onLineNumber);

        try {
            sendMessageAll(player.getPlayerName()+"進入了大廳...");

            clients.put(this.player.getPlayerId(),this);

            sendMessageOne(onLineNumber+"人",this.player.getPlayerId());

        } catch (IOException e) {
            e.printStackTrace();
            logger.info("網絡錯誤");
        }
    }

要先給player賦值再調用,不然就是空指針異常

如果是第一個人進入,是不會顯示”XXX進入了大廳“,因爲現在大廳還沒人。

(4)連接關閉

    /**
     * 連接關閉
     */
    @OnClose
    public void onClose(){
        onLineNumber --;
        clients.remove(player.getPlayerId());

        try {
            sendMessageAll(player.getPlayerName()+"已經離開了遊戲...");

        } catch (IOException e) {
            e.printStackTrace();
            logger.info("網絡錯誤");
        }
        logger.info("有連接關閉!當前在線人數"+onLineNumber);
    }

(5)收到客戶端信息

    /**
     * 收到客戶端得消息
     * @param message
     * @param session
     */
    @OnMessage
    public void onMessage(String message,Session session){

        try {
            sendMessageAll(message);
        } catch (IOException e) {
            e.printStackTrace();
            logger.info("網絡錯誤");
        }

    }

這裏因爲還沒有給Message做數據封裝,所以只會接收到文字信息。應該是將發送人,接收人,發送信息封裝成一個Map傳到後臺進行解析

(6)服務端錯誤

    /**
     * 服務器錯誤
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error){
        logger.info("服務器錯誤"+error.getMessage());
    }

(7)羣聊與私聊實現方法

    /**
     * 私聊
     * @param message
     * @param playerId
     * @throws IOException
     */
    public void sendMessageOne(String message,int playerId) throws IOException{
        WebSocketServer wss = clients.get(playerId);
        wss.session.getAsyncRemote().sendText(message);
    }

    /**
     * 羣聊
     * @param message
     * @throws IOException
     */
    public void sendMessageAll(String message) throws IOException{
        for(WebSocketServer wss : clients.values()){
            wss.session.getAsyncRemote().sendText(message);
        }
    }

講解:

1、因爲在建立連接時爲每個連接創建了一個web Socket對象並存儲起來,而Session又是它們特有的會話對象,私聊和羣聊都是找到它們的webSocket然後調用它們的會話進行發送信息。

2、getAsyncRemote()和getBasicRemote()區別

這裏引用一篇博客  websocket getAsyncRemote()和getBasicRemote()區別

三、客戶端實現

1、界面

因爲我在數據庫裏面放了幾個用戶,連接時候需要對應的用戶id,我的做法是先向Controller發送Post請求登錄成功後拿到id進行WebSocket連接。(之後會做具體實現)

2、使用插件

使用官方的WebSocket插件

這裏附上官方文檔: Official Construct 2 Manual  及 webSocket插件文檔  WebSocket plugin

3、邏輯編寫

4、解決報錯問題

參考我的博客  Construct2及Springboot關於跨域訪問的解決辦法

四、成果演示

五、總結

1、在沒有Spring框架支持下的WebSocket編寫無疑是十分麻煩的,基於JavaEE的tomcat容器不僅需要自己編寫配置文件也要解決版本衝突問題(因爲Tomecat7和8之間的WebSocket用法有區別)

2、SpringBoot框架以組件形式解決諸如WebSocket類似的問題,使開發者能夠更加重視業務而不是環境配置

(SpringBoot牛筆!!!(破音))

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章