Spiga

数十行F#打造简易Comet聊天服务

2009-12-11 12:00 by Jeffrey Zhao, 8958 visits, 收藏, 编辑

普通的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<string, MailboxProcessor<ChatMsg>>()

let private agentFactory = new Func<string, MailboxProcessor<ChatMsg>>(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<ChatMsg>) 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<ChatMsg>结果,如果这个结果为None,则表示队列已为空。于是在receive这个主函数中,便先使用receive’函数获取已有消息,如果存在则立即返回,否则便接收3秒钟内获得的第一个消息,如果3秒结束还没有收到则返回None。

在receive和receive’函数中都使用了let!获取agent.TryReceive函数的结果。let!是F#中构造Workflow的关键字,它起到了“语法糖”的作用。例如,以下的Async Workflow:

async {
    let req = WebRequest.Create("http://www.cnblogs.com/")
    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://www.cnblogs.com/"), (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
Add your comment

76 条回复

  1. #1楼 无常      2009-12-11 12:12
    Java这种劣质语言……
     回复 引用 查看   
  2. #2楼 余魁      2009-12-11 12:16
    学了有些时间了。comet还是没有学会。笨。
     回复 引用 查看   
  3. #3楼[楼主] Jeffrey Zhao      2009-12-11 12:22
    引用无常:Java这种劣质语言……

    还是Scala好。
     回复 引用 查看   
  4. #4楼 jacky song      2009-12-11 12:22
    合理利用语言特性确实带来不少惊喜
    ps:java就这么让你不爽啊
     回复 引用 查看   
  5. #5楼[楼主] Jeffrey Zhao      2009-12-11 12:25
    @jacky song
    是的,相当不爽,尤其是出现Scala以后,就怕货比货……
     回复 引用 查看   
  6. #6楼 遛扬狗      2009-12-11 12:43
    受教了,F#看不懂,但是文章中链接的Comet异步机制给我很大启发,谢谢。
     回复 引用 查看   
  7. #7楼 toEverybody      2009-12-11 12:48
    F#,C#平台上的丑角,由所谓的程序员来演
    但用C++,Delphi,C/汇编可以拆台,让你演不成
    哈哈..
     回复 引用 查看   
  8. #8楼 Ivony...      2009-12-11 12:57
    引用toEverybody:
    F#,C#平台上的丑角,由所谓的程序员来演
    但用C++,Delphi,C/汇编可以拆台,让你演不成
    哈哈..



    深奥。。。。看不懂。
     回复 引用 查看   
  9. #9楼 chol      2009-12-11 12:58
    现在有好的C#开源项目吗?可以做WebIM的?
     回复 引用 查看   
  10. #10楼[楼主] Jeffrey Zhao      2009-12-11 13:10
    @Ivony...
    同不懂……
     回复 引用 查看   
  11. #11楼 韦恩卑鄙 alias:v-zhewg      2009-12-11 13:10
    在gac里面完全没找到

    System.Collections.Concurrent
    的mailboxprocessor


    告诉我哪个dll?
     回复 引用 查看   
  12. #12楼[楼主] Jeffrey Zhao      2009-12-11 13:10
    @chol
    似乎没有现成的
     回复 引用 查看   
  13. #13楼 謝霆鋒[未注册用户]2009-12-11 13:22
    感覺老趙的java還沒入門
     回复 引用   
  14. #14楼[楼主] Jeffrey Zhao      2009-12-11 13:23
    @韦恩卑鄙 alias:v-zhewg
    MailboxProcessor是F#核心库里的,在FSharp.Core.dll里。
     回复 引用 查看   
  15. #15楼[楼主] Jeffrey Zhao      2009-12-11 13:23
    引用謝霆鋒:感覺老趙的java還沒入門

    为啥这样说呀?其实我对Java语言的了解不比C#少……
     回复 引用 查看   
  16. #16楼 egmkang      2009-12-11 13:27
    看了一下,这玩意儿貌似是不支持.NET CF啊
     回复 引用 查看   
  17. #17楼 egmkang      2009-12-11 13:30
    算法,现在WM上面的开发,缺少的是性能...
    我还是不搞这玩意儿..
     回复 引用 查看   
  18. #18楼 韦恩卑鄙 alias:v-zhewg      2009-12-11 13:40
    引用Jeffrey Zhao:
    @韦恩卑鄙 alias:v-zhewg
    MailboxProcessor是F#核心库里的,应该FSharp.Core.dll里。

    f# 在.net 2.0 的这个库对我现在的工作很有帮助 我试验下 3q

     回复 引用 查看   
  19. #19楼 木乃伊      2009-12-11 13:41
    学习了。
     回复 引用 查看   
  20. #20楼 韦恩卑鄙 alias:v-zhewg      2009-12-11 13:44
    引用egmkang:看了一下,这玩意儿貌似是不支持.NET CF啊

    2.0 4.0 和sl2 sl3 sl4
     回复 引用 查看   
  21. #21楼[楼主] Jeffrey Zhao      2009-12-11 13:45
    @韦恩卑鄙 alias:v-zhewg
    顺便一说,F#的Async Workflow不一定要和MailboxProcessor一起用的,说不定你也可以不用MailboxProcessor,除非是Message Passing场景。
    还有,如果要Message Passing场景但不能用F#的话,也可以试试看CCR。
    最后就是……不能运行在.NET 2.0吧,至少3.5……
     回复 引用 查看   
  22. #22楼 Ivony...      2009-12-11 13:45
    终于把最难懂的一行代码搞明白了:
    let beginReceive, e, _ = Async.AsBeginEnd receive


    放到VS2010里面根据智能提示很容易就猜出来了。

    原来下划线就是丢弃这个值的意思。。。
     回复 引用 查看   
  23. #23楼[楼主] Jeffrey Zhao      2009-12-11 13:45
    @egmkang
    是的,不支持CF……
     回复 引用 查看   
  24. #24楼 oec2003      2009-12-11 13:46
    引用謝霆鋒:感覺老趙的java還沒入門

    老赵只是单指java语言来说的
    平台和语言是两回事 有些人可能有误解
     回复 引用 查看   
  25. #25楼[楼主] Jeffrey Zhao      2009-12-11 13:50
    @oec2003
    没错,尤其是Java平台上已经有Scala语言了……
     回复 引用 查看   
  26. #26楼 韦恩卑鄙 alias:v-zhewg      2009-12-11 14:56
    @Jeffrey Zhao
    已经找到2.0的实现了
    Fsharp.Core.dll 的2.0.0 版本
     回复 引用 查看   
  27. #27楼[楼主] Jeffrey Zhao      2009-12-11 15:05
    @韦恩卑鄙 alias:v-zhewg
    是的,在.NET 3.5上的就是2.0版本的程序集。
     回复 引用 查看   
  28. #28楼 Ivony...      2009-12-11 15:16
    为啥感觉F#的语法还不够严谨呢?
     回复 引用 查看   
  29. #29楼[楼主] Jeffrey Zhao      2009-12-11 15:36
    @Ivony...
    我有时也有这样的想法,比如那段代码里的:
    let private agentCache = new ConcurrentDictionary<string, MailboxProcessor<ChatMsg>>()
    
    这会编译成静态的field/property,但如果写成:
    let private agentCache = new ConcurrentDictionary<_, _>()
    
    就变成一个泛型函数了……
    此类细节,有时候比较烦人。
     回复 引用 查看   
  30. #30楼 Gnie      2009-12-11 15:59
    老赵的文章看不懂啊
     回复 引用 查看   
  31. #31楼 xland      2009-12-11 16:30
    引用Gnie:老赵的文章看不懂啊

    老赵脱离大众好多年了
     回复 引用 查看   
  32. #32楼 BillGan      2009-12-11 16:31
    异步就不占用线程资源吗?????????????
    有那么好用么??????
     回复 引用 查看   
  33. #33楼[楼主] Jeffrey Zhao      2009-12-11 16:33
    @BillGan
    具体如何可以查阅相关资料并自己试验一下,关键字是IOCP,ASP.NET异步请求。
     回复 引用 查看   
  34. #34楼 小白TWO[未注册用户]2009-12-11 17:09
    我的评 论呢。。。在在在在哪里去了。第一次留言就成这样。晕
    在线等回复。
     回复 引用   
  35. #35楼 小白TWO[未注册用户]2009-12-11 17:09

    核心方法的实现。
    是不是来一个AJAX请求一个页面。然后这个页面执行一个while(true)一直阻塞。在需要的时候Response.Write相应内容。

    但是AJAX请求这个页面。如果页面一直未加载完。根本就得不到内容啊。更别说得到输出的最新内容了

    不知道我理解是否有误。请老赵解释一下。GOOGLE了一番都不太清楚这个。
     回复 引用   
  36. #36楼 小白TWO[未注册用户]2009-12-11 17:10
    原来是没有发出去啊。。为了回复一下。还专门去下个FF浏览器。囧
     回复 引用   
  37. #37楼[楼主] Jeffrey Zhao      2009-12-11 17:11
    @小白TWO
    看我文章里的解释
     回复 引用 查看   
  38. #38楼 申飞      2009-12-11 21:54
    @toEverybody
    引用toEverybody:
    F#,C#平台上的丑角,由所谓的程序员来演
    但用C++,Delphi,C/汇编可以拆台,让你演不成
    哈哈..


    哎。。。。。
    你。。。。。
    我。。。。。



    在我的印象中,C/汇编 好像不是很难吧, 感觉 还没有C#难。
     回复 引用 查看   
  39. #39楼[楼主] Jeffrey Zhao      2009-12-11 22:11
    @申飞
    不过难易倒不是关键
     回复 引用 查看   
  40. #40楼 蔡探长      2009-12-11 22:13
    我感觉Comet技术必将在未来一两年内大放异彩。正因为有java社区,net社区的功劳很少,因为net受至于服务器没有开源,微软也没有想搞,造成目前的comet的应用基本上是以java为主。

    正如2005年的ajax技术。

    正如orm在目前正逐渐成为主流(特别是linq sql,当然最早还是java先火了orm,才有net的跟进学习),毕竟大家都喜欢爽的感觉,不喜欢再写讨厌的sql语句。

    为什么这么说呢,可以说Comet正是因为有java社区目前越来越多的服务器的支持,java中各种人才对Comet的研究造成Comet今日逐渐为世人所知。

    大家可以在ibm开发社区里面用“comet”搜索一下就知道多少的comet技术文章。

    没错c#等语言的语言特性确实比java有更多的好些。但是我感觉一些总要技术总是java社区先挑起来的,后来net才跟进。微软应该好好思考些。
     回复 引用 查看   
  41. #41楼 蔡探长      2009-12-11 22:22
    目前net搞comet基本上还是处在瞎搞阶段,因为没有服务器支持啊,不能说是遗憾。完成相信微软最终还是会让iis支持comet这种风格模型。

    有好的技术管它什么语言,能派上用场就好(这场合用不上,其它场合可能就是牛b),语言就是工具罢了,还是多了解一些其它语言,有好处的
     回复 引用 查看   
  42. #42楼 蔡探长      2009-12-11 22:27
    以后的web服务器我感断定是逐渐都成为异步服务器。
    就象处理器一样是多核并行发展。
    就象现在的语言的趋势也是逐渐成为类似js的那种脚本语言,很灵活很强大,完全脚本化当然也不行,但是有很多东西可以让现在所谓的先进语言好好学习一下。
     回复 引用 查看   
  43. #43楼 Ivony...      2009-12-11 23:21
    引用蔡探长:
    我感觉Comet技术必将在未来一两年内大放异彩。正因为有java社区,net社区的功劳很少,因为net受至于服务器没有开源,微软也没有想搞,造成目前的comet的应用基本上是以java为主。

    正如2005年的ajax技术。

    正如orm在目前正逐渐成为主流(特别是linq sql,当然最早还是java先火了orm,才有net的跟进学习),毕竟大家都喜欢爽的感觉,不喜欢再写讨厌的sql语句。

    为什么这么说呢,可以说Comet正是因为有java社区目前越来越多的服务器的支持,java中各种人才对Comet的研究造成Comet今日逐渐为世人所知。

    大家可以在ibm开发社区里面用“comet”搜索一下就知道多少的comet技术文章。

    没错c#等语言的语言特性确实比java有更多的好些。但是我感觉一些总要技术总是java社区先挑起来的,后来net才跟进。微软应该好好思考些。



    我倒不觉得,Comet技术么,最早应该是GTalk让世人所知的,反正我是通过GTalk才知道Comet的。

    无论在哪一个领域微软和.NET都贡献了大量的技术,只是大家都喜欢用有色眼镜看罢了。

    在Web领域有服务器控件模型,这显然是ASP.NET出来后,JSP才跟进的。在语言方面,也是微软最早提出跨语言共享平台。

    更进一步,考虑微软的专有平台,WPF、CAS、ClickOnce、WCF、WF这些东西至今在Java平台也没有相应的技术。所以.NET平台上充斥着各种各样的外来技术恰恰说明.NET的开放性,老实说,从Java平台上移植技术到.NET平台的周期远远小于.NET平台技术移植到Java的周期。这当然有微软的不公开源代码的壁垒存在,但有时候与某平台的傲慢的态度不无关系,显然他们现在刚刚醒悟过来。
     回复 引用 查看   
  44. #44楼[楼主] Jeffrey Zhao      2009-12-12 01:02
    引用蔡探长:
    目前net搞comet基本上还是处在瞎搞阶段,因为没有服务器支持啊,不能说是遗憾。完成相信微软最终还是会让iis支持comet这种风格模型。

    有好的技术管它什么语言,能派上用场就好(这场合用不上,其它场合可能就是牛b),语言就是工具罢了,还是多了解一些其它语言,有好处的

    IIS真不行吗?我今天下午/晚上试了试,建立个2-3万长连接没有问题,异步之后性能很高,又不占用资源,而且都还是我的破机器而已,呵呵。
    而且,IIS真不行的话,咱可以自己搞一个Server咯。Java那边所谓服务器,说白了也就是哪Socket堆出来而已,如Jetty,Mina,该有的技术Windows/.NET都有。
     回复 引用 查看   
  45. #45楼[楼主] Jeffrey Zhao      2009-12-12 01:08
    @蔡探长
    Java的就是.NET的,提出一大堆东西,.NET都可以采用,呵呵。
     回复 引用 查看   
  46. #46楼 shenzhen      2009-12-12 01:58
    哎。。楼主能告诉我F#的学习资料到哪里找吗?想了解下。。谢谢!
     回复 引用 查看   
  47. #47楼 ngaut      2009-12-12 02:01
    老赵用Scala也实现一个,对比看看:)
     回复 引用 查看   
  48. #48楼 蔡探长      2009-12-12 11:01
    @Jeffrey Zhao
    你没有骗我吧。改天我也试试,免得我在同事面前一说iis不支持,有人就不乐意,说我要搞出一个net的例子出来,别搞那些不懂java(我挺想再继续跟他们说其实没事啊,不要抵触对立社区的知识啊,自己能学到最要紧,管什么语言实现的),因为我从来没有在网上看过人家这么弄过,也没有听说iis支持过,也不知道它为什么支持,为什么不支持。
    我给你一些j2ee服务为什么支持的链接,你参考看看。
    http://www.infoq.com/cn/articles/request-asyn-risk-reduce
    http://www.ibm.com/developerworks/cn/web/wa-cometjava/
     回复 引用 查看   
  49. #49楼[楼主] Jeffrey Zhao      2009-12-12 12:33
    @蔡探长
    骗你做啥,你自己试试看就知道咯。
    总有人说IIS连接什么几千限制的,改个配置就能支持N万。
    总有人说Windows性能不好,其实IOCP是各平台上最早出现的异步通信技术。
    .NET上有异步IO封装的时候,Java的NIO还不知道在哪里呢。
    Java实现Comet就是用NIO,.NET也一样,所以我这里不是用异步请求吗?
    你给的文章都是旧闻了啊,我还是拿那句话说,Windows既不缺功能也不缺性能。
    说白了,这一切就是靠异步说话,Windows上靠iocp,其他平台上靠epoll(linux)或是kqueue(freebsd)。
     回复 引用 查看   
  50. #50楼[楼主] Jeffrey Zhao      2009-12-12 12:33
    引用shenzhen:哎。。楼主能告诉我F#的学习资料到哪里找吗?想了解下。。谢谢!

    互联网
     回复 引用 查看   
  51. #51楼 蔡探长      2009-12-12 13:59
    @Jeffrey Zhao
    不对阿,我查了一下,java2002年jdk1.4.2出来的时候就有nio了,
    具体请看
    http://www.ibm.com/developerworks/cn/java/j-javaio/
    http://java.sun.com/j2se/1.4/docs/guide/nio/
    我决定要好好搞一把,这技术还是挺有前途的。

    我不排斥net不排斥java,哪个适合、方便就用哪个,不懂就学,无所谓啦。
     回复 引用 查看   
  52. #52楼[楼主] Jeffrey Zhao      2009-12-12 14:20
    @蔡探长
    如果我没记错的话,“真正”的基于event的异步IO是后来才有的(Java 5?6?),一开始只是select + “非占用线程”的这种异步方式。
    要知道epoll是Linux Kernel 2.5(还是2.6)开始才集成进去的,当时已经02、03年了吧。2.4的Kernel版本还要通过后来的patch才能获得epoll,Java跟不了这么紧吧。
    而且我可以确定的是,包括现在Java 6 for Windows里的NIO,还没有使用IOCP,还是使用select,换句话说还是假的NIO。
    所以我一直不认同Java所谓的跨平台,性能完全不同么,呵呵。不过Java 7里说是已经用了IOCP了。
     回复 引用 查看   
  53. #53楼[楼主] Jeffrey Zhao      2009-12-12 14:22
    @蔡探长
    忘了说了,IOCP是NT 3.5里加入的,也就是说至少Windows 2000开始就有IOCP支持的。
    只可惜,微软不知道什么技术推广策略,推广不力,开发人员也不太关注这些。
    这又要说到AJAX了,话说XMLHttpRequest也是IE 5.5就有的,可惜被微软用于企业产品(OWA)了,没有推广……
    直到Gmail……总之微软很多事情的确我看不懂,很可惜。
     回复 引用 查看   
  54. #54楼 蔡探长      2009-12-12 16:42
    @Jeffrey Zhao
    嗯,静候我的佳音,我会用事实解密net和java的comet模式的优缺点。在这里没有事实是没有办法再说下去了
     回复 引用 查看   
  55. #55楼 Ivony...      2009-12-12 16:42
    引用Jeffrey Zhao:
    @蔡探长
    忘了说了,IOCP是NT 3.5里加入的,也就是说至少Windows 2000开始就有IOCP支持的。
    只可惜,微软不知道什么技术推广策略,推广不力,开发人员也不太关注这些。
    这又要说到AJAX了,话说XMLHttpRequest也是IE 5.5就有的,可惜被微软用于企业产品(OWA)了,没有推广……
    直到Gmail……总之微软很多事情的确我看不懂,很可惜。



    微软是赚钱的公司,其目的不是成为技术领袖,技术只是为了商业而存在的。很多技术微软弄出来了,市场那边看了看,没啥好推的,就放那里了。

    微软不可能自己去做Gmail,这会与他的Office竞争,毕竟Office很赚钱。再说微软控制着操作系统和浏览器这两块地盘,也让他根本没有任何动力去做Gmai这样的在线邮箱。如果确实需要,微软更倾向于提供Outlook Express这样的客户端。

    微软没有在互联网上找到赚钱的地方,这也是微软将来最重要的事情。微软不太可能像Google一样去卖广告,因为现在Google已经几乎事实垄断了互联网广告。所以可以看到微软在云计算上的投入不逊色于Google(因为这个可以赚钱),而在线应用却不愿意花精力。
     回复 引用 查看   
  56. #56楼[楼主] Jeffrey Zhao      2009-12-12 21:14
    引用蔡探长:
    @Jeffrey Zhao
    嗯,静候我的佳音,我会用事实解密net和java的comet模式的优缺点。在这里没有事实是没有办法再说下去了

    嗯嗯,有啥进展及时说说,呵呵
     回复 引用 查看   
  57. #57楼 Ivony...      2009-12-12 22:58
    有想法把这个F#的东东翻译成C#的。。。。。

    刚刚想了一部分语法的变换,看起来是可行的。
     回复 引用 查看   
  58. #58楼[楼主] Jeffrey Zhao      2009-12-12 23:14
    @Ivony...
    那个let!估计只能用yield了吧……
     回复 引用 查看   
  59. #59楼 Ivony...      2009-12-13 01:01
    翻译成C#应该不是难事,移植到C#就很麻烦,需要建立大量的辅助方法和Provider,我大体上进行了一些简单的翻译工作。

    首先是做一些简单的变换:

    #light
    
    module internal Comet.Chating.Chat
    
    open System
    open System.Collections.Concurrent
    
    type ChatMsg = {
        From: string;
        Text: string;
    }
    
    let private agentCache = new ConcurrentDictionary<string, MailboxProcessor<ChatMsg>>()
    
    let private agentFactory = new Func<string, MailboxProcessor<ChatMsg>>(fun _ -> 
        MailboxProcessor.Start(fun o -> async { o |> ignore }))
    
    let private GetAgent name = agentCache.GetOrAdd(name, agentFactory)
    
    let send fromName toName msg = 
        let agent = GetAgent toName
        { From = fromName; Text = msg; } |> agent.Post
    
    let receive name = 
        let rec receive' (agent: MailboxProcessor<ChatMsg>) 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]
        }
    


    变换为:

    namespace  Comet.Chating
    {
      using System;
      using System.Collections.Concurrent;
    
    
      public static class Chat
      {
    
        private class ChatMsg
        {
          public string From { get; set;}
          public string Text { get; set;}
        }
    
        private static ConcurrentDictionary<string, FSharpMailboxProcessor<ChatMsg>> agentCache = new ConcurrentDictionary<string,FSharpMailboxProcessor<ChatMsg>>();
        
        private static Func<string, FSharpMailboxProcessor<ChatMsg>> agentFactory = any => 
          FSharpMailboxProcessor<ChatMsg>.Start( o => /*async { ignore( o ) }*/ , null );
    
        private static FSharpMailboxProcessor<ChatMsg> GetAgent( string name ) { return agentCache.GetOrAdd( name , agentFactory ); }
    
        public static void send( string fromName, string toName, string msg )
        {
          var agent = GetAgent( toName );
          agent.Post( new ChatMsg(){ From=fromName, Text = msg } );
        }
    
        public static void receive( string name )
        {
          /*
          let rec receive' (agent: MailboxProcessor<ChatMsg>) messages = 
              async {
                  let! msg = agent.TryReceive 0
                  match msg with
                  | None -> return messages
                  | Some s -> return! receive' agent (s :: messages)
              }
          */
    
          var 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]
          }
          */
        }
      }
    }
    
    


    接下来就是妥善的处理async块语法了,已经有了一些初步的想法,还需要实验论证。

    其实也可以看出来,C#除了在类型上麻烦些,大体上还是能保持和F#语法差不多的。
     回复 引用 查看   
  60. #60楼 蛙蛙王子      2009-12-13 09:05
    这个东西我也想过要做,后来没时间了,老赵做的很棒,这个东西在《ajax设计模式》里的通信一部分有讲。
    当时一直怀疑,不知道性能如何,真的要用,感觉得自己用socket写一个http服务
     回复 引用 查看   
  61. #61楼[楼主] Jeffrey Zhao      2009-12-13 15:21
    @蛙蛙王子
    可以这么做的,呵呵,Java那边其实都是这么做的。
    还有就是,我不知道这个Host如果使用WCF会如何……
     回复 引用 查看   
  62. #62楼 没剑      2009-12-14 14:12
    老赵,等着你在iis上测试性能的文章,期待中!
     回复 引用 查看   
  63. #63楼 遛扬狗      2009-12-18 23:40
    我翻译成c#后测试了,最初的每个对象会占用大概52K,然后会逐渐增多,当内存到80M左右的时候,内存增长开始变得缓慢而且没有规律,此时w3wp.exe会无先兆的重启服务,如果w3wp.exe不重启的话,那么就会定在某个内存值上,150多M的样子,紧接着IIS就会报告停止服务。我测试了很多次,都是这样的。最后说一下,测试结果差不多2000多个就是极限了。可能和我测试手段有关系,开的线程太多导致,我会再做测试的。
     回复 引用 查看   
  64. #64楼[楼主] Jeffrey Zhao      2009-12-19 00:19
    @遛扬狗
    你是怎么翻译的啊?不该开太多线程的,如F#这种做法线程是很少的,因为是异步请求。
    我的测试中最多同时建立20k个长连接(没有断开),而且这不是极限,只是我没有测试更多的,呵呵。
     回复 引用 查看   
  65. #65楼 遛扬狗      2009-12-19 09:28
    正想向你咨询如何做到20k的,我的代码是这样的,请你看看是不是那里不对了?我用的是c#2.0+iis6 ,iis6配置是40000个核心排队,内存配置为1g。机器是双cpu xeon 3.16G,内存2g


    //异步
    namespace CometAsync.Comet
    {
        public class CometAsyncHandler : IHttpAsyncHandler
        {
            #region IHttpAsyncHandler Members
    
            public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
            {
               
    
                    //保存状态
    
                CometAsyncResult result = new CometAsyncResult(context.Response, cb, extraData);
    
                    result.AddCometAsyncResult(context.Request.QueryString["name"]);
    
                    //  ok, return it
                    return result;
    
            }
    
            public void EndProcessRequest(IAsyncResult result)
            {
    
            }
    
            #endregion
    
            #region IHttpHandler Members
    
            public bool IsReusable
            {
                get { return true; }
            }
    
    
            #endregion
        }
    }
    
    


    namespace CometAsync.Comet
    {
        public class CometAsyncResult : IAsyncResult
        {
            private HttpResponse Response;
            private AsyncCallback callback;
            private object asyncState;
            private bool isCompleted = false;
            private object responseObject;
            public DateTime Time;
            /// <summary>
            /// 在线状态
            /// </summary>
            public string State;
            /// <summary>
            /// 单个用户所有的消息
            /// </summary>
            public List<string> Messages = new List<string>();
            private readonly char BREAK='\x0000';
    
            public CometAsyncResult(HttpResponse Response, AsyncCallback callback, object asyncState)
            {
                this.callback = callback;
                this.Response = Response;
                this.asyncState = asyncState;
                this.Time = DateTime.Now;
    
                Response.Write(BREAK);
                Response.Flush();
            }
    
            #region IAsyncResult Members
    
            public object AsyncState
            {
                get { return this.asyncState; }
            }
    
            public WaitHandle AsyncWaitHandle
            {
                get { throw new InvalidOperationException("ASP.NET Should never use this property"); }
            }
    
            public bool CompletedSynchronously
            {
                get { return false; }
            }
    
            public bool IsCompleted
            {
                get { return this.isCompleted; }
            }
    
            #endregion
    
    
            public void AddCometAsyncResult(string Username)
            {
                string name = Username ?? "user" + HttpContextRemoting.UsersOnline.Count.ToString();
                if (!HttpContextRemoting.UsersOnline.ContainsKey(name))//初次连接
                {
                    HttpContextRemoting.UsersOnline.Add(name, this);
    
                }
    
            }
            /// <summary>
            /// 写入客户端流,成功返回true,失败返回false
            /// </summary>
            /// <param name="Message"></param>
            /// <returns></returns>
            public bool Write(string Message)
            {
                if (this.Response.IsClientConnected)
                {
                    this.Response.Write(Message);
                    this.Response.Write(BREAK);                
                    this.Response.Flush();
                    return true;
                }
                return false;
              
            }
            internal void SetCompleted()
            {
                this.isCompleted = true;
    
                if (callback != null)
                    try
                    {
                        callback(this);
                    }
                    catch (Exception)
                    {
                    }
            }
    
        }
    }
    
     回复 引用 查看   
  66. #66楼 遛扬狗      2009-12-19 09:30
    //这里静态变量保存用户状态
    namespace CometAsync.Remoting
    {
        public partial class HttpContextRemoting : MarshalByRefObject
        {
    
            public static SynchronizedDictionary<string, CometAsyncResult> UsersOnline = new SynchronizedDictionary<string, CometAsyncResult>(10000);
            
    
         public HttpContextRemoting()
         {
    
         }
    
            /// <summary>
            /// 返回用户对象
            /// </summary>
            /// <param name="To"></param>
            /// <returns></returns>
         private CometAsyncResult GetCometAsyncResult(string To)
         {
             if (UsersOnline.ContainsKey(To))
             {
                 return UsersOnline[To];
    
             }
             return null;
    
         }
        }
        
    }
    
    
     回复 引用 查看   
  67. #67楼[楼主] Jeffrey Zhao      2009-12-19 11:48
    @遛扬狗
    今天有些事情,明天看看,实在不行周一试验一下。:)
     回复 引用 查看   
  68. #68楼 遛扬狗      2009-12-19 22:07
    晚上我又测试了几个钟头,发现导致w3wp重启是因为iis6有个“启用快速失败保护”的选项,默认打开,设置为5分钟错5次就会重启线程,关闭这个选项后就不会重启了。现在测试最高是4000多个连接,内存188M左右,再多就报告server too busy了。下面是一些测试数据:

    连接数(个) 内存占用(M)
    1994 123
    2882 132
    3555 179
    4421 183

     回复 引用 查看   
  69. #69楼[楼主] Jeffrey Zhao      2009-12-19 22:15
    @遛扬狗
    有没有修改过最大连接数?IIS 7默认最大是5000个,IIS 6忘了咋修改了。
     回复 引用 查看   
  70. #70楼 遛扬狗      2009-12-20 12:53
    我试了试你说的方法,没有效果,有文章说2003 sp1 x64能支持到50K+,没有提到sp2 x32,我还是装个2008来试一试了。如果你能在2003+iis6下测试一下,我将不胜感激。
     回复 引用 查看   
  71. #71楼 遛扬狗      2009-12-20 18:39
    The default maximum number of ephemeral TCP ports is 5000 in the products that are included in the "Applies to" section. A new parameter has been added in these products. To increase the maximum number of ephemeral ports, follow these steps:
    Start Registry Editor.
    Locate the following subkey in the registry, and then click Parameters:
    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters
    On the Edit menu, click New, and then add the following registry entry:
    Value Name: MaxUserPort
    Value Type: DWORD
    Value data: 65534
    Valid Range: 5000-65534 (decimal)
    Default: 0x1388 (5000 decimal)
    Description: This parameter controls the maximum port number that is used when a program requests any available user port from the system. Typically, ephemeral (short-lived) ports are allocated between the values of 1024 and 5000 inclusive. After the release of security bulletin MS08-037, the behavior of Windows Server 2003 was changed to more closely match that of Windows Server 2008 and Windows Vista. For more information about Microsoft security bulletin MS08-037, click the following article numbers to view the articles in the Microsoft Knowledge Base:


    应该是这个问题吧,但我修改了没啥反应。
     回复 引用 查看   
  72. #72楼 遛扬狗      2009-12-21 17:17
    今天装了2008,然后按照http://www.cnblogs.com/dudu/archive/2009/11/10/1600062.html这篇文章设置了系统,在13k的时候IIS崩溃了。我开了5台机器,用WAS来测试的。另外我又用amd 64+2003的系统来测试了一次,也是在5000连接处停止服务了。
     回复 引用 查看   
  73. #73楼[楼主] Jeffrey Zhao      2009-12-21 17:21
    @遛扬狗
    一会儿看看你的代码,呵呵。
     回复 引用 查看   
  74. #74楼[楼主] Jeffrey Zhao      2009-12-21 22:32
    @遛扬狗
    你的代码没咋看懂唉……我再看看……
     回复 引用 查看   
  75. #75楼[楼主] Jeffrey Zhao      2009-12-21 22:35
    @遛扬狗
    你的代码很奇怪,怎么跑得起来啊?SetComplete没见调用过,不是么……
     回复 引用 查看   
  76. #76楼 遛扬狗      2009-12-22 18:02
    恩,其他地方调用的,要不我把程序传给你吧,你看怎么给你。
     回复 引用 查看   
发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 1621789 eYJ3jWOa8vo=