f#簡(jiǎn)易Comet聊天服務(wù)實(shí)例分析

f#簡(jiǎn)易Comet聊天服務(wù)實(shí)例分析,針對(duì)這個(gè)問題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問題的小伙伴找到更簡(jiǎn)單易行的方法。

創(chuàng)新互聯(lián)建站是一家網(wǎng)站設(shè)計(jì)、成都網(wǎng)站設(shè)計(jì),提供網(wǎng)頁(yè)設(shè)計(jì),網(wǎng)站設(shè)計(jì),網(wǎng)站制作,建網(wǎng)站,定制網(wǎng)站,網(wǎng)站開發(fā)公司,2013年至今是互聯(lián)行業(yè)建設(shè)者,服務(wù)者。以提升客戶品牌價(jià)值為核心業(yè)務(wù),全程參與項(xiàng)目的網(wǎng)站策劃設(shè)計(jì)制作,前端開發(fā),后臺(tái)程序制作以及后期項(xiàng)目運(yùn)營(yíng)并提出專業(yè)建議和思路。

Visual Studio 2010中關(guān)于F#的部分已經(jīng)眾人皆知,那么具體該怎么開發(fā)呢?這里作者將本來可以用C#開發(fā)的實(shí)例,改用F#來進(jìn)行,也是為大家開闊眼界。

普通的Web應(yīng)用程序,都是靠大量HTTP短連接維持的。如實(shí)現(xiàn)一個(gè)聊天服務(wù)時(shí),客戶端會(huì)不斷輪詢服務(wù)器端索要新消息。這種做法的優(yōu)勢(shì)在于簡(jiǎn)單有效,因此廣為目前的聊天服務(wù)所采用。不過Comet技術(shù)與之不同,簡(jiǎn)單地說,Comet便是指服務(wù)器推(Server-Push)技術(shù)。它的實(shí)現(xiàn)方式是(這里只討論基于瀏覽器的Web平臺(tái))在瀏覽器與服務(wù)器之間建立一個(gè)長(zhǎng)連接,待獲得消息之后立即返回。否則持續(xù)等待,直至超時(shí)??蛻舳说玫较⒒虺瑫r(shí)之后,又會(huì)立即建立另一個(gè)長(zhǎng)連接。Comet技術(shù)的***優(yōu)勢(shì),自然就是很高的即使性。

如果要在ASP.NET平臺(tái)上實(shí)現(xiàn)Comet技術(shù),那么自然需要在服務(wù)器端使用異步請(qǐng)求處理。如果是普通處理方式的話,每個(gè)請(qǐng)求都會(huì)占用一個(gè)工作線程,要知道Comet是“長(zhǎng)連接”,因此不需要多少客戶端便會(huì)占用大量的線程,這對(duì)資源消耗是巨大的。如果是異步請(qǐng)求的話,雖然客戶端和服務(wù)器端之間一直保持著連接,但是客戶端在等待消息的時(shí)候是不占用線程的,直到“超時(shí)”或“消息到達(dá)”時(shí)才繼續(xù)執(zhí)行。

以前也有人實(shí)現(xiàn)過基于ASP.NET的Comet服務(wù)原型,不過是使用C#的。而現(xiàn)在我們用F#來實(shí)現(xiàn)這個(gè)功能。您會(huì)發(fā)現(xiàn)F#對(duì)于此類異步場(chǎng)景有其獨(dú)特的優(yōu)勢(shì)。

F#常用的工作單元是“模塊”,其中定義了大量函數(shù)或字段。例如我們要打造一個(gè)聊天服務(wù)的話,我便定義了一個(gè)Chat模塊:

#light  module internal Comet.Chating.Chat  open System  open System.Collections.Concurrent   type ChatMsg = {      From: string;      Text: string;  }   let private agentCache = new ConcurrentDictionary>()   let private agentFactory = new Func>(fun _ ->       MailboxProcessor.Start(fun o -> async { o |> ignore }))   let private GetAgent name = agentCache.GetOrAdd(name, agentFactory)

在這里我構(gòu)建了一個(gè)名為ChatMsg的Record類型,一個(gè)ChatMsg對(duì)象便是一條消息。然后,我使用一個(gè)名為agentCache的ConcurrentDictionary對(duì)象來保存每個(gè)用戶所對(duì)應(yīng)的聊天隊(duì)列——MailboxProcessor。它是F#核心庫(kù)中內(nèi)置的,用于實(shí)現(xiàn)消息傳遞式并發(fā)的組件,非常輕量級(jí),因此我為每個(gè)用戶分配一個(gè)也只使用很少的資源。GetAgent函數(shù)的作用是根據(jù)用戶的名稱獲取對(duì)應(yīng)的MailboxProcessor對(duì)象,自不必多說。

Chat模塊中還定義了send和receive兩個(gè)公開方法,如下:

let send fromName toName msg =       let agent = GetAgent toName      { From = fromName; Text = msg; } |> agent.Post   let receive name =       let rec receive' (agent: MailboxProcessor) messages =           async {              let! msg = agent.TryReceive 0              match msg with              | None -> return messages              | Some s -> return! receive' agent (s :: messages)          }       let agent = GetAgent name       async {          let! messages = receive' agent List.empty          if (not messages.IsEmpty) then return messages          else             let! msg = agent.TryReceive 3000              match msg with              | None -> return []              | Some s -> return [s]      }

send方法接受3個(gè)參數(shù),沒有返回值,它的實(shí)現(xiàn)只是簡(jiǎn)單地構(gòu)造一個(gè)ChatMsg對(duì)象,并塞入對(duì)應(yīng)的MailboxProcessor。不過receive方法是這里最關(guān)鍵的部分(沒有之一)。receive函數(shù)的作用是接受并返回MailboxProcessor中已有的對(duì)象,或者等待3秒鐘后超時(shí)——這么說其實(shí)不太妥當(dāng),因?yàn)閞eceive方法其實(shí)只是構(gòu)造了一個(gè)“做這件事情”的Async Workflow,而還沒有真正執(zhí)行它。至于它是如何執(zhí)行的,我們稍候再談。

receive函數(shù)的邏輯是這樣的:首先我們構(gòu)造一個(gè)輔助函數(shù)receive’來“嘗試獲取”隊(duì)列中已有的所有消息。receive’是一個(gè)遞歸函數(shù),每次獲取一個(gè),并遞歸獲取剩余的消息。agent.TryReceive函數(shù)接受0,表示查詢隊(duì)列,并立即返回一個(gè)Option結(jié)果,如果這個(gè)結(jié)果為None,則表示隊(duì)列已為空。于是在receive這個(gè)主函數(shù)中,便先使用receive’函數(shù)獲取已有消息,如果存在則立即返回,否則便接收3秒鐘內(nèi)獲得的***個(gè)消息,如果3秒結(jié)束還沒有收到則返回None。

在receive和receive’函數(shù)中都使用了let!獲取agent.TryReceive函數(shù)的結(jié)果。let!是F#中構(gòu)造Workflow的關(guān)鍵字,它起到了“語(yǔ)法糖”的作用。例如,以下的Async Workflow:

async {      let req = WebRequest.Create("http://moma.org/")      let! resp = req.GetResponseAsync()      let stream = resp.GetResponseStream()      let reader = new StreamReader(stream)      let! html = reader.ReadToEndAsync()      html  }

事實(shí)上在“解糖”后就變成了:

async.Delay(fun () ->      async.Let(WebRequest.Create("http://moma.org/"), (fun req ->          async.Bind(req.GetResponseAsync(), (fun resp ->              async.Let(resp.GetResponseStream(), (fun stream ->                  async.Let(new StreamReader(stream), (fun reader ->                      async.Bind(reader.ReadToEndAsync(), (fun html ->                          async.Return(html))))))))))

let!關(guān)鍵字則會(huì)轉(zhuǎn)化為Bind函數(shù)調(diào)用,Bind調(diào)用有兩個(gè)參數(shù),***個(gè)參數(shù)為Async<’a>類型,它便負(fù)責(zé)一個(gè)“回調(diào)”,待回調(diào)后才執(zhí)行一個(gè)匿名函數(shù)——也就是Bind函數(shù)的第二個(gè)參數(shù)。可見,let!關(guān)鍵字的一個(gè)重要作用,便是將流程的“控制權(quán)”轉(zhuǎn)交給“系統(tǒng)”,待合適的時(shí)候再繼續(xù)執(zhí)行下去。這便是關(guān)鍵,因?yàn)檫@樣的話,在接受一個(gè)消息的時(shí)候,這等待的3秒鐘是不占用任何線程的,也就是真正的純異步。但是如果觀察代碼——難道不是純粹的順序型寫法嗎?

這就是F#的神奇之處。

在ASP.NET處理時(shí)需要Handler,于是在Send階段便是簡(jiǎn)單的IHttpHandler:

#light   namespace Comet.Chating   open Comet  open System  open System.Web   type SendHandler() =       interface IHttpHandler with          member h.IsReusable = false         member h.ProcessRequest(context) =               let fromName = context.Request.Form.Item("from");              let toName = context.Request.Form.Item("to")              let msg = context.Request.Form.Item("msg")              Chat.send fromName toName msg              context.Response.Write "sent"

而Receive階段則是個(gè)異步的IHttpAsyncHandler:

#light   namespace Comet.Chating   open Comet  open System  open System.Collections.Generic  open System.Web  open System.Web.Script.Serialization   type ReceiveHandler() =       let mutable m_context = null     let mutable m_endReceive = null      interface IHttpAsyncHandler with          member h.IsReusable = false         member h.ProcessRequest(context) = failwith "not supported"          member h.BeginProcessRequest(c, cb, state) =              m_context <- c               let name = c.Request.QueryString.Item("name")              let receive = Chat.receive name              let beginReceive, e, _ = Async.AsBeginEnd receive              m_endReceive <- new Func<_, _>(e)               beginWork (cb, state)           member h.EndProcessRequest(ar) =              let convert (m: Chat.ChatMsg) =                  let o = new Dictionary<_, _>();                  o.Add("from", m.From)                  o.Add("text", m.Text)                  o               let result = m_endReceive.Invoke ar              let serializer = new JavaScriptSerializer()              result              |> List.map convert              |> serializer.Serialize              |> m_context.Response.Write

這里的關(guān)鍵是Async.AsBeginEnd函數(shù),它將Chat.receive函數(shù)生成的Async Workflow轉(zhuǎn)化成一組標(biāo)準(zhǔn)APM形式的begin/end對(duì),然后我們只要把BeginProcessRequest和EndProcessReqeust的職責(zé)直接交給即可。剩下的,便是一些序列化成JSON的工作了。

于是我們可以新建一個(gè)Web項(xiàng)目,引用F#工程,在Web.config里配置兩個(gè)Handler,再準(zhǔn)備一個(gè)Chat.aspx頁(yè)面即可。您可以在文末的鏈接中查看該頁(yè)面的代碼,也可以在這里試用其效果。作為演示頁(yè)面,您其實(shí)只能“自己給自己”發(fā)送消息,其主要目的是查看其響應(yīng)時(shí)間而已。例如,以下便是使用效果一例:

2 - receiving...  3026 - received nothing (3024ms)  3026 - receiving...  6055 - received nothing (3028ms)  6055 - receiving...  7256 - sending 123654...  7268 - received: 123654 (1213ms)  7268 - receiving...  10281 - received nothing (3013ms)  10281 - receiving...  13298 - received nothing (3017ms)  13298 - receiving...  13679 - sending 123456...  13698 - received: 123456 (400ms)  13698 - receiving...  16716 - received nothing (3018ms)  16716 - receiving...  18256 - sending hello world...  18265 - received: hello world (1549ms)  18266 - receiving...  21281 - received nothing (3015ms)  21281 - receiving...

可見,如果沒有收到消息,那么receive操作會(huì)在3秒鐘后返回。當(dāng)send一條消息后,先前的receive操作便會(huì)立即獲得消息了,即無需等待3秒便可提前返回。這便是Comet的效果。

至于性能,我寫了一個(gè)客戶端小程序,用于模擬大量用戶同時(shí)聊天,每個(gè)用戶每隔1秒便給另外5個(gè)用戶發(fā)送一條消息,然后查看這條消息收到時(shí)產(chǎn)生多少的延遲。經(jīng)過本機(jī)測(cè)試(2.4GHz雙核,2G內(nèi)存),當(dāng)超過2K個(gè)在線用戶時(shí)(即2000個(gè)長(zhǎng)連接)延遲便超過了1秒——到20K還差不多。這個(gè)性能其實(shí)并不理想。不過,我這個(gè)測(cè)試也很一般。因?yàn)闇y(cè)試環(huán)境相當(dāng)馬虎,大量程序(如N個(gè)VS)基本上已經(jīng)完全用滿了所有的物理內(nèi)存,測(cè)試客戶端和服務(wù)器也是同一臺(tái)機(jī)器,甚至代碼也是Debug編譯的……而根據(jù)監(jiān)視,測(cè)試用的客戶端小程序CPU占用超過50%,而服務(wù)器進(jìn)程對(duì)應(yīng)的w3wp.exe的CPU占用卻小于10%。因此,我們可以這樣推斷,其實(shí)服務(wù)器端的性能并沒有用足,也有可能是MailboxProcessor的調(diào)度方式不甚理想。至于具體是什么原因,我還在調(diào)查之中。

***我想說的是,這個(gè)Comet實(shí)現(xiàn)只是一個(gè)原型,我最想說明的問題其實(shí)是F#在異步編程中的優(yōu)勢(shì)。目前我寫的一些程序,例如一些網(wǎng)絡(luò)爬蟲,都已經(jīng)使用F#進(jìn)行開發(fā)了,因?yàn)樗腁sync Workflow實(shí)在是過于好用,為我省了太多力氣。同時(shí)我還想證明,“語(yǔ)言特性”并非不重要,它對(duì)于編程的簡(jiǎn)化也是至關(guān)重要的。在我看來,“類庫(kù)”也好,“框架”也罷都是可以補(bǔ)充的,但是語(yǔ)言特性是個(gè)無法突破的“限制”。例如,異步編程對(duì)于F#來說簡(jiǎn)化了不少,這是因?yàn)槲覀兛梢允褂庙樞虻姆绞骄帉懏惒匠绦?。在C#中略有不足,但還有yield可以起到相當(dāng)作用,因此我們可以使用CCR和AsyncEnumerator簡(jiǎn)化異步操作。但如果您使用的是Java這種劣質(zhì)語(yǔ)言……因此,放棄Java,使用Scala吧。

值得一提的是,Async Workflow并不是F#的語(yǔ)言特性,F(xiàn)#的語(yǔ)言特性是Workflow,而Async Workflow其實(shí)只是實(shí)現(xiàn)了一個(gè)Workflow Builder,也就是那個(gè)async { ... },以此來簡(jiǎn)化異步編程而已。PDC 09上關(guān)于F#對(duì)異步編程的支持也有相應(yīng)的介紹。

關(guān)于f#簡(jiǎn)易Comet聊天服務(wù)實(shí)例分析問題的解答就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道了解更多相關(guān)知識(shí)。

文章標(biāo)題:f#簡(jiǎn)易Comet聊天服務(wù)實(shí)例分析
文章URL:http://muchs.cn/article26/pgddjg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站建設(shè)、外貿(mào)建站、關(guān)鍵詞優(yōu)化網(wǎng)站導(dǎo)航、微信公眾號(hào)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

成都做網(wǎng)站