网上有关“服务器推送”的介绍很是多,其中一种实现方式就是采用comet技术,在浏览器与服务端之间创建一个http协议的“长链接”,所谓“长链接”,就是指浏览器到服务端的http请求不会立刻获得服务端的应答,而是当知足必定条件的时候,服务器端才“主动”将数据返回给浏览器,这时候一次http请求才完成,普通http链接与http长链接见下图:jquery
图1web
如上图,左边为通常http链接,服务端收到浏览器的http请求后会当即作出应答,右边为http长链接,服务端收到浏览器的http请求后,若是有数据须要返回,则当即返回,不然,服务端会“维持住”这个http请求,也就是说,服务端冻结了该次http请求,直到服务端有数据须要返回给浏览器或者超时,服务端才“解冻”该次http请求,这时候,一次“浏览器->服务端”的http请求才完整结束。ajax
“长链接”的做用很明显,它能让服务端“主动”(注意这里的主动加了双引号)将数据发送给浏览器,是的,你没听错,传统的Web程序只能是浏览器主动请求服务端,服务端再做出应答,而如今,服务端竟然能够“主动推送”数据给浏览器了。既然服务端如今能够“主动推送”数据给浏览器,那么咱们能够完成许多以前不能作到的事情,好比“即时通信”、“实时监控”等相似客户端须要实时更新数据的应用程序了。这里插一句,若是不采用服务端“主动推送”的方式,咱们确实能够按照传统浏览器端使用ajax主动循环请求服务端,来实现所谓的“实时”更新数据的效果,好比每秒ajax请求服务端,来刷新界面数据,可是这种方式的缺点可想而知,无论服务端有没有需更新的数据,浏览器都必须不断地循环去请求(有关ajax轮询的缺点请参考网上其余文章)。chrome
具体实现comet的关键有如下几点:编程
1)服务端不会当即响应浏览器的http请求(除非当时有数据须要返回给浏览器);json
2)浏览器处理完服务端返回的数据后,要当即从新创建一个“http长链接”,供下次使用;数组
3)浏览器在处理服务端返回的数据时(下一次长链接创建以前),若是服务端有新的数据须要发送给浏览器,那么服务端必须将这些新数据保存起来,等下次“长链接”创建后,再一块儿发送给浏览器;浏览器
4)要想服务端可以实时地、不断地“主动推送”数据到浏览器,浏览器与服务端之间必须无时无刻保持一条“http通道”(也就是http长链接),服务端可以借助该通道将数据返回给浏览器;缓存
5)浏览器一接收到服务端返回的数据,一次“http长链接”就结束了,须要从新创建下一个。服务器
若是咱们以socket编程的角度看“http长链接”,它会是这样的:
图2
如上图,每一个browser必须时刻存在一个http下行通道(服务端->浏览器,图中(1)处),这样任什么时候候服务端都能“主动推送”数据给browser,同时,每一个浏览器都可以发起其余正常http请求(图中(2)处),这样一来,1号浏览器经过普通http请求发送数据(get/post方式)给服务端,服务端就能够立马将数据“推送”给2号浏览器(使用http下行通道),没错,这不就是socket编程吗?
asp.net中可使用“异步编程模型”(APM)简单地实现comet,具体涉及到IHttpAsyncHandler接口以及它的BeginProcessRequest和EndProcessRequest两个方法,当咱们接收到来自浏览器发起的“http长链接”请求时,咱们只调用IHttpAsyncHandler.BeginProcessRequest方法去异步处理,因为咱们不当即调用IHttpAsyncHandler.EndProcessRequest方法,因此这个http请求不会立马结束(也就是说,该请求被服务端冻结住了),等服务端有数据时,咱们再经过相似Response.Write()方法将数据发送给浏览器,同时调用IHttpAsyncHandler.EndProcessRequest方法结束异步处理http请求,这时候,浏览器端会接收到服务端“主动推送”的数据,浏览器端一次完整的http请求到此时才结束,紧接着,浏览器端经过脚本再次发起一个“http长链接”的请求,依次循环。
文章最后上传一个即时通信的demo,每一个登陆的用户均可以发送消息,在线用户均能及时收到服务端“推送”来的数据(包括上线、下线以及消息内容等),因为服务端发送的数据种类比较多,我简单的指定了一个协议(protocol),相似以下:
1 协议相似socket通信编程中的协议(相似): 2 [4bytes]+[16bytes]+[some]+[1byte] 3 消息类型+消息长度+消息内容+验证位 4 5 (Web Server->Browser方向)json格式数据包 每一个包相似C++中的结构体: 6 7 有用户上线: 8 { 9 "type":"login", 10 "login_name":"xiaozhi_5638", 11 "login_time":"2014-04-15 12:34:19" 12 } 13 14 有用户发送消息: 15 { 16 "type":"sendmsg", 17 "msg":"hello world![emo:23]", 18 "send_name":"xiaozhi_5638", 19 "send_time":"2014-04-15 12:34:19" 20 } 21 22 有用户下线: 23 { 24 "type":"logout", 25 "logout_name":"xiaozhi_5638", 26 "logout_time":"2014-04-15 12:34:19" 27 } 28 29 心跳包: 30 { 31 "type":"heartbeat", 32 "time":"2014-04-15 12:34:19" 33 } 34 35 本身登陆结果: 36 { 37 "type":"login_result", 38 "result":"true", //or false 39 "online_users":["xiaozhi_5638","somebody","zhangsan"], //登陆成功 返回在线用户 40 "time":"2014-04-15 12:34:19" 41 } 42 43 本身发送消息结果 44 { 45 "type":"sendmsg_result", 46 "result":"true", //or false 47 "msg":"hello world![emo:23]", 48 "time":"2014-04-15 12:34:19" 49 } 50 51 数据包集合(上面的都是单个数据包,data_list中包含多个数据包集合) 用于一次性将服务端缓存的数据包传递到browser 52 { 53 "type":"data_list", 54 "list":[{"type":"login","login_name":"xiaozhi_5638","login_time":"2014-04-15 12:34:19"},{"type":"sendmsg","msg":"hello world!","send_name":"xiaozhi_5638","send_time":"2014-04-15 12:34:19"}] //数组类型 包含多个数据包 55 } 56 Browser->Web Server方向的数据 以普通get/post方式传递
服务端到浏览器端的数据均以json格式传递,浏览器到服务端采用jquery写好的ajax库方式。浏览器端的脚本只须要解析服务端传递回来的json数据,而后更新界面,紧接着发起下一个“http长链接”请求。js脚本发起http长链接代码以下:
1 function Open_Http_Channel() //开启一个http通道(http长链接) 2 { 3 $.ajax( 4 { 5 url:"chat_aspx.ashx", //action处理页面 6 type:"post", //数据传递方式 7 data:{"requestType":"a_long_connection","id":$("#input_user_name").val()}, //上传参数 8 dataType:"json", //返回数据类型 9 success:function(data) //解析返回的json包 10 { 11 //接收web server端返回的数据 开始解析数据 参见protocol.txt 12 if(data.type == "login") //有人上线 13 { 14 ShowLogin(data); //显示登陆信息 15 } 16 if(data.type == "sendmsg") //有人发送消息 17 { 18 ShowMsg(data.msg,data.send_time,data.send_name); //显示消息 19 } 20 if(data.type == "logout") //有人下线 21 { 22 Logout(data); 23 } 24 if(data.type == "data_list") //数据包集合 25 { 26 for(i in data.list) //遍历数据包集合 27 { 28 if(data.list[i].type == "login") 29 { 30 ShowLogin(data.list[i]); //显示登陆信息 31 } 32 if(data.list[i].type == "sendmsg") 33 { 34 ShowMsg(data.list[i].msg,data.list[i].send_time,data.list[i].send_name); //显示消息 35 } 36 if(data.list[i].type == "logout") //下线 37 { 38 Logout(data.list[i]); 39 } 40 //... 41 } 42 } 43 //... 44 //... 45 //...定义的其余协议 在此处解析 46 Open_Http_Channel(); //立刻开启第二次http通道 47 }, 48 error:function(xhr,info,obj) 49 { 50 Open_Http_Channel(); //立刻开启第二次http通道 51 } 52 }); 53 }
其余具体详细的说明参见源代码。效果图一张图3(gif表情没有解析替换,直接显示的文本)
图3
源码地址:http://files.cnblogs.com/xiaozhi_5638/comet_in_aspnet.rar vs2008
用到了jquery以及跟它相关的几个界面库。注意不要在同一个浏览器上登陆太多用户,由于浏览器会为每一个用户维持一个http链接,而浏览器对http请求数量有限制(笔者用的chrome上限为6个),超过上限的话,以后全部http请求都会被阻塞。