iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 22
0
Software Development

從讀遊戲原始碼學做連線遊戲系列 第 22

Day 22 - 實作練習 - 處理 WebSocket 連線

前面幾篇我們已經針對建構 WebSocket 做了一定程度的準備,接下來就要將 WebSocket 連線從預設的行為接手回來改為用我們自己的方式來進行管理。這樣我們才能將接收到的指令轉換為 Ruby 可以理解的格式,並且呼叫對應的方法來進行處理。

Protocol::WebSocket

在修改 Unlight 為能夠支援 WebSocket 的版本時就想過一個問題,普通的 TCPSocket 伺服器跟 WebSocket 伺服器有沒有機會並存。因此在設計這個範例的時候預先假設我們會使用多個協定(Protocol)來提供服務,因此就採用了當伺服器接收連線後應該要轉交到不同協定上處理的預定。

雖然實際上要怎麼設計比較嗆大還沒有確定下來,不過先讓我們簡單的以 WebSocket 做一個雛形出來看看。

# app/network/protocol/websocket.rb

# frozen_string_literal: true

module Protocol
  class WebSocket
    class InvalidRequest < RuntimeError; end

    def initialize(env)
      @env = env
      setup
    end

    def websocket?
      Faye::WebSocket.websocket?(@env)
    end

    def response
      @ws.rack_response
    end

    private

    def receive(event)
      # TODO
    end

    def setup
      raise InvalidRequest unless websocket?

      @ws = Faye::WebSocket.new(@env)
      @ws.on :message, method(:receive)
    end
  end
end

這邊首先要注意的是 websocket 因為也是例外詞,所以需要用 Inflector 調整名稱。這個檔案我們先大致上的把處理的架構定義出來,因為進入這個物件後就預設一定是 WebSocket 連線,因此也把原本在 WebSocketServer 的判斷改到這邊,並且用 Exception 的方式呼叫外部的人員支援。

同樣的我們需要調整 WebSocketServer 物件來支援新定義的協定物件:

module WebSocketServer
  UNSUPPORT_RESPONSE = [501, { 'Content-Type' => 'text/plain' }, ['Unsupport']].freeze

  def self.call(env)
    ws = Protocol::WebSocket.new(env)
    ws.response
  rescue Protocol::WebSocket::InvalidRequest
    UNSUPPORT_RESPONSE
  end
end

Connection

當我們處理好協定的部分後,需要做出一個抽象的連線物件用來統一操作的介面。這樣在往後如果要拓展 TCPSocket 支援的時候就只需要調整介面到能讓這個物件呼叫就能夠擴充。

# app/network/connection.rb

# frozen_string_literal: true

class Connection
  def initialize(socket)
    @socket = socket
  end

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

  def receive(data)
    # TODO: Parse Command and Execute
  end
end

我們做了一個很簡單的介面,讓 Connection 物件可以支援接收資料並處理以及將資料發送出去的兩個行為。接下來我們要調整一下 Protocol::WebSocket 物件將資料轉發到 Connection 物件上。

# app/network/protocol/websocket.rb

# frozen_string_literal: true

module Protocol
  class WebSocket
    class InvalidRequest < RuntimeError; end

    def initialize(env)
      @env = env
      @conn = Connection.new(self)
      setup
    end

   # ...

    private

    def receive(event)
      @conn.receive(event.data)
    end

    # ...
  end
end

如此一來,當我們的 WebSocket 伺服器接收到資料後,會將資料轉給 Connection 物件處理,而 Connection 物件則成為處理的中樞,如果要發送或者接收都要透過他才能夠執行。下一篇我們就來討論將接收到的指令透過 Connection 處理後呼叫 Controller 執行的方式。

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


上一篇
Day 21 - 實作練習 - Autoload 機制
下一篇
Day 23 - 實作練習 - 指令執行
系列文
從讀遊戲原始碼學做連線遊戲33
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言