我們已經知道客戶端是如何連上伺服器並將指令發送出去。那麼我們接下來就要來看如何生成指令發送到伺服器上,在討論伺服器部分的時候我們大致上知道指令需要由長度、指令編號、指令內容三個部分組成,並且指令內容還需要依照參數的順序跟類型使用特定的格式。
運氣不錯的是在 ActionScript 中這些都可以透過內建的 ByteArray 物件來處理,如果是單純的 JavaScript 我們就得依靠 Uint8Array 之類的物件來封裝,中間可能還會遇到不少問題。
我們一樣用 AuthServer 的註冊指令來當作範例,在 src/net/command/AuthCommand.as
檔案中可以找到這段:
public function register(name:String, email:String, salt:String, verifire:String, server_type:int):ByteArray
{
var cmd:ByteArray = new ByteArray();
cmd.writeShort(0);
cmd.writeUTF(name);
cmd.writeUTF(email);
cmd.writeUTF(salt);
cmd.writeUTF(verifire);
cmd.writeInt(server_type);
return cmd;
}
在 ActionScript 中關於這部分的實作是非常容易的,只需要產生一個 ByteArray 物件,並且告訴 ActionScript 應該要寫入怎樣類型的資訊,那麼就可以順利的產生對應的 ByteArray。
我們可以直接用
writeUTF
方法放進去的關係,可以是推測 Ruby 伺服器是為了配合 ActionScript 的 ByteArray 格式來設計的。當然,這也可能是某個習慣或者標準做法,不過因為我們大多接觸的都是 Web 相關東西或者套件,反而比較少機會接觸到這類的處理。
在這邊大家應該會發現,似乎沒有整個指令的「長度」資訊被放到裡面,這是因為長度資訊是包含「指令編號」的大小,因此是在 sendCommand
處理中,在發送出去之前補上的。
不過在 src/net/Host.as
中並沒有實際定義,還記得 Unlight 自定義過的 ULSocket
嗎?
我們會在 src/net/ULSocket.as
裡面發現,當 Host 使用 sendCommand
發送時會透過 ULSocket 來送出,送出前的處理就是生成一個新的 ByteArray 然後在開頭插入一個 2 Bytes 的資料表示長度,以及結尾放入一個 \n
表示指令結束。
/**
* コマンドを送信
* @param data 送信するデータ
*/
public function send(data:ByteArray):void
{
var ba:ByteArray = new ByteArray();
ba.writeShort (data.length);
// 圧縮がここに入る。data.compress
// log.writeLog(log.LV_INFO, this, "data len",ba);
writeShort (data.length);
writeBytes (cipher.encrypt(data));
writeUTFBytes ("\n");
flush(); // 送信
// log.writeLog(log.LV_DEBUG, this,"Send",data);
}
那麼,假設我們想要使用 JavaScript 來做到這件事情是可行的嗎?
因為在 JavaScript 中我們沒有 ByteArray 這樣方便的物件可以使用,因此我們只能依靠像是 Uint8Array 的方式來製作出對應的指令,以 register
這個指令來說,我們可能會需要類似下面的處理(並非實際的程式碼)
const encoder = new TextEncoder();
const username = encoder.encode(params['name'])
const email = encoder.encode(params['email'])
// ...
let data = new Uint8Array(2 + username.length + 2 + email.length + 2) // ...
data.set(new Uint16Array(dataLength));
data.set(new Uint16Array(username.length), 2)
data.set(username.length, 4)
data.set(new Uint16Array(email.length), 4 + username.length)
data.set(email, 4 + username.length + 2)
// ...
看起來是蠻不方便的,假設當初 Unlight 不是選擇用 ActionScript 的話基本上應該就不會這樣設計封包的結構,這樣也可能應證了伺服器的解析方式其實是為了配合 ActionScript 的設計或需求。
我的個人部落格是弦而時習之平常會把自己發現的一些新技巧紀錄在上面,也歡迎大家來逛逛。