最近在維護公司舊專案,偶然發現使用Fleck實現的WebSocket主動推送功能,(由於前端頁面關閉時WebSocket Server中執行了多次OnClose事件回撥並且列印了大量的關閉日誌,),後來我特地看了原始碼,這裡做一些分享
github: https://github.com/statianzo/Fleck
在原始碼中,作者在 Samples 專案中貼心的準備了Server端和 前端Html檔案供除錯
開啟後,我們稍微改動一下Server.cs類,模擬實際場景向客戶端主動推送訊息。
執行ConsoleApp專案,隨後在瀏覽器中開啟client.html,我們就可以看到客戶端接收到很多主動推送的訊息
關閉客戶端後,此時我們會發現控制檯上列印了好多“Close!”,並且丟擲了異常,異常是從System.Net.Sockets.NetworkStream 丟擲,說我們訪問了已釋放的物件。
大概猜測的是程式併發太高了,Socket已經關閉釋放的同時,我們任然在向流寫入byte位元組傳送訊息。
接下來我們讀一下原始碼
首先是 WebSocketServer.cs,構造方法中建立了 System.Net.Sockets.Socket類,並傳遞給 Fleck.SocketWrapper,後續和Socket相關的操作都是由SocketWrapper例項進行執行。SupportDualStack為True時表示啟用IPV6
隨後我們看一下 WebSocketServer.Start() 方法,方法的入參是一個Action,IWebSocketConnection 中我們定義了OnOpen、OnClose、OnMessage等方法。
Start()方法中給SocketWrapper(或System.Net.Sockets.Socket類)類繫結了偵聽地址和埠。
ListenForClients() 方法為開始接收連線(Accept),如果有連線,則呼叫OnClientConnect()方法,OnClientConnect()方法呼叫中如果出現異常,則執行重啟Socket工作。
我們再來看看OnClientConnect()方法做了什麼,ListenForClients() 方法是繼續偵聽客戶端連線,隨後建立了WebSocketConnection類物件,然後開始connection.StartReceiving() 也就是讀取訊息內容。
我們可以簡單的看一下Fleck.SocketWrapper類的實現,特別是Accept()方法和Receive()方法,其實就是Task執行BeginAccept()、EndAccept()、BeginRead() 和 EndRead() 方法,如果呼叫時出現異常,則執行Action<Exception>()方法。
接下來我們看一下WebSocketConnection類,首先是構造方法,
- socket:連線Socket例項。
- initialize:是我們在WebSocketServer 中配置的OnOpen、OnClose、OnMessage等方法。
- handlerFactory:是透過工廠模式建立出對應的Handle物件,其中實現了 Draft76Handler、Hybi13Handler、FlashSocketPolicyRequestHandler 等幾種類,將收到的byte[]序列化成對應的訊息。
- parseRequest:是RequestParser.Parse方法的委託,將byte[]透過UTF8序列化成中文,再透過正規表示式提取關鍵資訊生成WebSocketHttpRequest物件。
其次是Read(List<byte>, byte[]) 方法。就是呼叫SocketWrapper(或System.Net.Sockets.Socket類)的 Receive()方法讀取byte[]並交給Handle物件進行處理,如果byte[]長度為0,則表示關閉斷開
最後是SendBytes(byte[], Action)方法,就是呼叫SocketWrapper(或System.Net.Sockets.Socket類)的 Send()方法,如果傳送不成功,則會呼叫CloseSocket()方法關閉Socket(這就是為什麼連線只有一個但多次觸發OnClose事件原因)
Fleck 的核心功能已經講解完了,其他類還有 WebSocketConnectionInfo、QueuedStream、SubProtocolNegotiator等類基本也是比較簡單,這裡就不展開講解了。
Fleck 原始碼 用了裝飾者模式、工廠模式等設計模式,還很優雅的處理方法執行失敗的異常方式,對委託的使用也是值得我們學習的。