中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

f#簡易Comet聊天服務實例分析

發布時間:2022-01-06 21:28:23 來源:億速云 閱讀:139 作者:柒染 欄目:編程語言

f#簡易Comet聊天服務實例分析,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。

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

普通的Web應用程序,都是靠大量HTTP短連接維持的。如實現一個聊天服務時,客戶端會不斷輪詢服務器端索要新消息。這種做法的優勢在于簡單有效,因此廣為目前的聊天服務所采用。不過Comet技術與之不同,簡單地說,Comet便是指服務器推(Server-Push)技術。它的實現方式是(這里只討論基于瀏覽器的Web平臺)在瀏覽器與服務器之間建立一個長連接,待獲得消息之后立即返回。否則持續等待,直至超時。客戶端得到消息或超時之后,又會立即建立另一個長連接。Comet技術的***優勢,自然就是很高的即使性。

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

以前也有人實現過基于ASP.NET的Comet服務原型,不過是使用C#的。而現在我們用F#來實現這個功能。您會發現F#對于此類異步場景有其獨特的優勢。

F#常用的工作單元是“模塊”,其中定義了大量函數或字段。例如我們要打造一個聊天服務的話,我便定義了一個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)

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

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

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個參數,沒有返回值,它的實現只是簡單地構造一個ChatMsg對象,并塞入對應的MailboxProcessor。不過receive方法是這里最關鍵的部分(沒有之一)。receive函數的作用是接受并返回MailboxProcessor中已有的對象,或者等待3秒鐘后超時——這么說其實不太妥當,因為receive方法其實只是構造了一個“做這件事情”的Async Workflow,而還沒有真正執行它。至于它是如何執行的,我們稍候再談。

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

在receive和receive’函數中都使用了let!獲取agent.TryReceive函數的結果。let!是F#中構造Workflow的關鍵字,它起到了“語法糖”的作用。例如,以下的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  }

事實上在“解糖”后就變成了:

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!關鍵字則會轉化為Bind函數調用,Bind調用有兩個參數,***個參數為Async<’a>類型,它便負責一個“回調”,待回調后才執行一個匿名函數——也就是Bind函數的第二個參數。可見,let!關鍵字的一個重要作用,便是將流程的“控制權”轉交給“系統”,待合適的時候再繼續執行下去。這便是關鍵,因為這樣的話,在接受一個消息的時候,這等待的3秒鐘是不占用任何線程的,也就是真正的純異步。但是如果觀察代碼——難道不是純粹的順序型寫法嗎?

這就是F#的神奇之處。

在ASP.NET處理時需要Handler,于是在Send階段便是簡單的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階段則是個異步的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

這里的關鍵是Async.AsBeginEnd函數,它將Chat.receive函數生成的Async Workflow轉化成一組標準APM形式的begin/end對,然后我們只要把BeginProcessRequest和EndProcessReqeust的職責直接交給即可。剩下的,便是一些序列化成JSON的工作了。

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

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操作會在3秒鐘后返回。當send一條消息后,先前的receive操作便會立即獲得消息了,即無需等待3秒便可提前返回。這便是Comet的效果。

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

***我想說的是,這個Comet實現只是一個原型,我最想說明的問題其實是F#在異步編程中的優勢。目前我寫的一些程序,例如一些網絡爬蟲,都已經使用F#進行開發了,因為它的Async Workflow實在是過于好用,為我省了太多力氣。同時我還想證明,“語言特性”并非不重要,它對于編程的簡化也是至關重要的。在我看來,“類庫”也好,“框架”也罷都是可以補充的,但是語言特性是個無法突破的“限制”。例如,異步編程對于F#來說簡化了不少,這是因為我們可以使用順序的方式編寫異步程序。在C#中略有不足,但還有yield可以起到相當作用,因此我們可以使用CCR和AsyncEnumerator簡化異步操作。但如果您使用的是Java這種劣質語言……因此,放棄Java,使用Scala吧。

值得一提的是,Async Workflow并不是F#的語言特性,F#的語言特性是Workflow,而Async Workflow其實只是實現了一個Workflow Builder,也就是那個async { ... },以此來簡化異步編程而已。PDC 09上關于F#對異步編程的支持也有相應的介紹。

關于f#簡易Comet聊天服務實例分析問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

靖西县| 铁岭县| 嘉荫县| 鄂托克前旗| 车致| 石楼县| 肥西县| 门头沟区| 随州市| 安溪县| 丰台区| 红原县| 盈江县| 诏安县| 山东省| 五峰| 阳原县| 故城县| 德昌县| 江油市| 宁南县| 鲁甸县| 富源县| 左贡县| 防城港市| 胶州市| 准格尔旗| 兰西县| 静海县| 罗平县| 临武县| 正定县| 南充市| 新沂市| 疏勒县| 五莲县| 乌什县| 绥芬河市| 安岳县| 高平市| 沂水县|