iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 23
0

現在我們的伺服器已經可以處理 WebSocket 連線並且透過我們自定義的物件來管理,不過當接收到指令的時候還是無法直接執行。因為已經採用 WebSocket 來實作,因此我們先選用最直觀的 JSON 格式來當作指令的格式。

實際上 JSON 在傳輸資料比較多的時候會佔用相對多的頻寬,像是 MessagePack 或者 gRPC 都採用了比較「緊湊」的方式處理資料,前面提到的 Unlight 的指令規格也是屬於緊湊的類型。

Controller

在 Unlight 中不同的伺服器會使用專門的 Controller 來處理指令,因為我們的專案還算簡單所以就直接這樣使用。不過如果在未來專案變得複雜之後,也許就會需要考慮其他類型的設計。像是從 MapController 拆分成多個 Contraller 並且放在 Map 模組下(Ex. Map::MovementController)不過這是否是一個恰當的做法就需要在未來慢慢實驗去驗證。

首先我們在 app/controllers 目錄下面增加 app/controllers/map_controller.rb 這個檔案用來處理地圖相關的指令。

# app/controllers/map_controller.rb

# frozen_string_literal: true

class MapController
  def initialize(conn)
    @conn = conn
  end

  def move(x, y)
    @conn.write(command: 'move', parameters: [x, y])
  end
end

上面這段程式碼算是一個進階版的 Echo Server 用來呈現玩家移動的狀態,因為我們目前還沒沒處理多人連線的部分,因此就是很單純地將指令回傳給發送的使用者。在連線遊戲中為了公平性,不論哪種操作都需要回傳到作為伺服器的機器上統一處理後再回傳給玩家,以這邊的案例在後面我們增加資料庫支援時就還會多出儲存座標的步驟。

接下來我們要對我們的 WebSocket 協定物件做一些調整,讓使用者可以透過這個抽象的封裝傳輸訊息回去給這個「連線」的使用者。

# app/network/protocol/websocket.rb

require 'json'

module Protocol
  class WebSocket
    # ...

    def write(data)
      @ws.send(data)
    end

    private

    # ...
  end
end

運行指令

因為已經定義了新指令,但是當我們收到指令時卻還不知道要怎麼執行,繼續修改 app/network/connection.rb 把前面我們還沒實作的 #receive 方法實作出來。

# app/network/connection.rb

# frozen_string_literal: true

require 'json'

class Connection
  def initialize(socket)
    @socket = socket
    # TODO: Auto Detect Controller
    @controller = MapController.new(self)
  end

  def write(data)
    @socket.write(data.to_json)
  end

  def receive(data)
    data = JSON.parse(data)
    return unless @controller.respond_to?(data['command'])

    @controller.send(data['command'], *data['parameters'])
  end
end

首先我們將 Controller 在連線初始化時進行定義,之後我們還需要依照啟動的伺服器類型判斷不同的 Controller 來使用,不過現階段就先直接將 MapController 寫死在上面。然後我們對傳進來的資料做了解析,並且判斷這個指令是否存在於我們的 Controller 上,如果存在的話就利用 Ruby 語言的 #send 行為呼叫這個方法。

在 Ruby 想要將不確定長度的參數傳入方法的話,可以利用 * 將某個陣列展開使用。另外因為 JSON 在 Ruby 預設不會載入,因此我們還需要再使用前將它讀取進來。

完成上面的實作之後,我們可以打開伺服器,然後在瀏覽器上執行下面這段 JavaScript 來觀察效果。

let ws = new WebSocket('ws://localhost:9292')
ws.onmessage = console.log
ws.onopen = (ev) => ws.send(JSON.stringify({ command: 'move', parameters: [0, 0] }))

假設實作正確的話,我們會收到一個 MessageEvent 事件作為回傳,而事件所帶的資料也會是 { "command": "move", "parameters": [0, 0] } 這樣的 JSON 字串,跟我們發送出去的完全一致。所以前面才會提到這是一個進階版的 Echo Server 雖然做的事情表面上看起來差不多,不過我們已經完成了能夠針對伺服器任意拓展指令的機制。

我的個人部落格是弦而時習之平常會把自己發現的一些新技巧紀錄在上面,也歡迎大家來逛逛。


上一篇
Day 22 - 實作練習 - 處理 WebSocket 連線
下一篇
Day 24 - 實作練習 - 架構客戶端
系列文
從讀遊戲原始碼學做連線遊戲33

尚未有邦友留言

立即登入留言