前言
之前為了學習 go 寫了兩個小專案,fastcgi-go 和 mysql-proxy 都是用了 go 來處理二進位制包,從而實現一些協議(fastcgi 協議 和 mysql 協議)
之後感覺各種應用協議並不是高深複雜的東西,只要跟著協議走就能跑通它。這次有點好奇 websocket 是什麼協議,和 socket 是什麼關係,有空用 php 寫一個小框架就明白了
這裡先貼出 github 地址 websocket-php
首先搜尋 websocket 協議,在 Laravel China 找到了 老司機帶你用 PHP 實現 Websocket 協議 這篇文章,裡面介紹了很多基礎知識,對於第一次實現 websocket 有很大幫助,因此就不在這贅述了
而我想做一個稍微 “能用” 的東西。在實現這些功能的過程中,遇到了一些與 websocket 相關或不相關的問題,要好好記錄一下
目標
跟 Workerman 一樣啟動一個服務,同時管理多個連線,為各個事件設定回撥,就像這樣
$server = new Server();
$server->on('message', function(Connection $client, Message $message) use ($server) {
$server->sendMessage($client, Packet::MSG_TYPE_TXT, 'get ' . $message->content());
});
$server->start();
socket 連線的非阻塞模式
在呼叫 socket_accept
和 socket_read
時,程式完全停止住了,這時如果第二個連線請求進來,則會超時。而 socket_set_nonblock
可以將這個連線設定為非阻塞模式。
這時與一般的阻塞模式有些許不同,在等待輸入時,這兩個函式不是阻塞住,而是直接返回 false ,需要做為 false 時的處理
$client = socket_accept($this->connection);
if ($client) {
...
}
Chrome 瀏覽器的包長限制
寫完後在 index.html
測試,發現包長超過 255 位元組就會報錯
我猜測 Chrome 限制了 websocket 協議包的長度,用 go 做客戶端測試了一下,發現能正常傳輸,這也證實了猜想。所以要考慮分包的問題了
訊息抽象 和 websocket 協議分包
使用 websocket 通訊應該專注與傳輸的訊息而忽略底層的分包操作,因此需要先抽象出一個 Message
類,一個訊息可以由一個或多個包組成
無論是傳送還是接受訊息時,只需要遵守 websocket 協議分包即可,協議中有包有兩個部分用於分包。第一個是第一位(FIN),1表示結束,0表示未結束。第二個是第 5 到 8 位(opcode),如果是 0 的話表示這個包是上個包的延續
輸入抽象以方便測試
在編寫的過程中總是要不斷測試,開啟服務,再開啟客戶端傳送訊息的測試方式太過麻煩。如果可以不開啟服務,直接讀取包的內容就好了
於是包的讀取通過 Reader
類完成,把輸入的內容存到檔案以後,Server
可以通過 startReadFile
來完成一次檔案讀取,來模擬接收一個或多個訊息
執行截圖
測試過程:
- 傳送長度達到需要分包的訊息
- 同時開啟多個連線傳送訊息
- 關閉一個連線
- 讀取本地檔案
網頁截圖
php 輸出截圖
讀取本地檔案
$server = new Server();
$server->on('message', function($conn, Message $msg) {
echo $msg->length(), PHP_EOL, substr($msg->content(), $msg->length()-10, 10);
});
$server->startReadFile('test_bin_file');
其他
最近在看 Laravel 的原始碼,也寫了一些 筆記,有什麼建議歡迎交流
本作品採用《CC 協議》,轉載必須註明作者和本文連結