在前篇文章試寫了WCF雙工服務,由於WsHttpBinding不支援雙工,故我們改用WsDaulHttpBinding及NetTcpBinding,分別用HTTP及TCP協定傳送資料。問題來了,大家都知道HTTP是單向的,Client端連上Server,每次送Request收Response後就銀貨兩訖,WCF服務端要如何主動呼叫Client端,難道要像SignalR一樣,使出Long Polling之類的奇技淫巧達成任務?答案是,不!Server得回頭開一條HTTP連線到Client端傳送Callback。跌破很多人的眼鏡吧?
打開Microsoft Network Monitor,執行前文的Timer.svc測試程式,錄得封包記錄如下:
172.28.1.1是Client端,172.28.1.227是Server端,執行期間除了有172.28.1.1呼叫172.28.1.227/WcfWas/Timer.svc的記錄,還有另一個由172.28.1.227呼叫172.28.1.1/feb5ab41-cd06-4678-a485-185503863379的連線。你沒看錯,Server端主動連上Client的80 Port,送出一個HTTP Request並接收回應,Client端也變成一個迷你Web Server了~
觀察由Server連至Client的反向HTTP連線,我們看到每秒四個封包的來回,並由SOAP內容中看到OnTime字眼及由Server傳回的time字串。
而由Client連至Server方面,我們也觀察到一秒一次四個封包的/WcfWas/Timer.svc呼叫。所以每次Server呼叫Client的同時,Client都要再呼叫一次Server。
換言之,每秒一次的Tick,總共要產生 235+65+639+152(Server=>Client) + 211+65+698+224(Client=>Server) 8個封包共 2,289 bytes的傳輸量。更重要的,這裡隱藏了一個嚴重問題:一般而言,若非Intranet環境,Client與Server很難直接對連,傳輸路徑少不了得通過一道以上的防火牆。Server要對外服務,防火牆允許外界連入不是什麼問題,但Server要逆向穿過保護Client機器的防火牆,難度可高了,而且連Windows自身的防火牆也可能有意見,故此一做法在現實Internet世界的可行性極低。故WsDualHttpBinding只適用Intranet可直接連通的環境,還得留意Client的防火牆軟體作梗。
看完WsDualHttpBinding有點掉漆的表現,再來看看NetTcpBinding:
從頭到尾,一條連線搞定。當然,Client/Server兩台機器要能直接連線,不能有Application Firewall/Proxy阻隔。再看看傳輸封包:
每秒一次的Callback,兩個封包,95+40=135 bytes,收工!
由以上的觀察,WsDualHttpBinding雖然名字中有HTTP,但穿透性沒有比較好,而每次Callback所傳輸的封包數及資料量(2289 vs 135)都遠不如NetTcpBinding有效率。要實做雙工服務,建議使用TCP為宜。由於使用WsDualHttpBinding有利於偵錯,故可以在開發期間用WsDualHttpBinding,上線時再改設定換成NetTcpBinding,魚與熊掌兼得,享受WCF架構的彈性。
最後的疑問,WsDualHttpBinding很廢,NetTcpBinding很難穿防火牆,難道WCF雙工服務註定無緣用在Internet環境?
除了預設Binding,無比彈性的WCF當然允許開發者自訂Binding,像Silverlight就開發了PollingDuplexHttpBinding,使用Polling技巧克服HTTP只允許單向的限制(原理可看這裡),但無法應用在非Silverlight程式。好消息是,.NET 4.5起,WCF增加生力軍,NetHttpBinding支援透過WebSocket解決雙工需求,留待下期分解。