在网上搜了搜,没有发现C#实现http长连接的开源项目,估计是实现起来太简单了吧。自己写一个,不是项目中使用,纯粹测试一下。
1、原理
所谓长连接,是指客户端以http协议连接到服务器,区别于一般的短连接,服务器不会立即返回数据,而是保持住这个连接,等到有数据时才返回。说起来简单,但却不能使用Sleep或者信号量的方式保持住连接,因为这么做会长时间占用线程,客户端多时很快会占满ASP.Net的线程池。
ASP.Net提供了IHttpAsyncHandler接口,允许开发者以异步方式处理http请求。示意图如下:
Http请求到达服务器后,被BeginProcessRequest方法处理,它个方法要生成一个IAsyncResult对象,并将其保存到其它地方,之后返回。此时连接将保持住,但ASP.Net已经可以空出资源去处理下一个请求了。直到程序的业务逻辑触发IAsyncResult对象的Callback,IHttpAsyncHandler对象的EndProcessRequest方法才被调用,此时可以做输出,之后这个http请求才真正结束。
2、IHttpAsyncHandler实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
using
System;
using
System.Web;
namespace
Comet
{
public
class
CometAsyncHandler : IHttpAsyncHandler
{
/// <summary>
/// 请求到达时的处理方法
/// </summary>
public
IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback callback,
object
extraData)
{
//通过context可以取请求附加的数据,略
//...
//之后生成IAsyncResult对象,callback比较重要,调用这个回调,EndProcessRequest才被触发
var
result =
new
CometResult(context, callback, extraData);
//在返回之前把刚生成的IAsyncResult对象保存起来,略
//...
return
result;
}
/// <summary>
/// 请求结束时的处理方法
/// </summary>
public
void
EndProcessRequest(IAsyncResult asyncResult)
{
//得到对应的IAsyncResult对象
var
result = asyncResult
as
CometResult;
//后续处理,如输出内容等,略
//...
}
public
bool
IsReusable
{
get
{
return
false
; }
}
public
void
ProcessRequest(HttpContext context)
{
throw
new
NotImplementedException();
}
}
}
|
3、IAsyncResult实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
using
System;
using
System.Web;
using
System.Threading;
namespace
Comet
{
public
class
CometResult : IAsyncResult
{
#region 实现IAsyncResult接口
public
object
AsyncState {
get
;
private
set
; }
public
WaitHandle AsyncWaitHandle {
get
;
private
set
; }
public
bool
CompletedSynchronously {
get
;
private
set
; }
public
bool
IsCompleted {
get
;
private
set
; }
#endregion
public
AsyncCallback Callback {
get
;
private
set
; }
public
HttpContext Context {
get
;
private
set
; }
public
object
ExtraData {
get
;
private
set
; }
public
CometResult(HttpContext context, AsyncCallback callback,
object
extraData)
{
this
.Context = context;
this
.Callback = callback;
this
.ExtraData = extraData;
}
public
Call()
{
if
(
this
.Callback !=
null
)
this
.Callback(
this
);
}
}
}
|
4、调用
找到对应的IAsyncResult对象,调用其Call方法即可。
5、配置文件
在<system.web>节中注册httpHandler,增加:
1
2
3
4
5
6
|
<
system.web
>
<
httpHandlers
>
<!-- 针对GET与POST两种请求,路径是Data.aspx文件,使用CometAsyncHandler处理 -->
<
add
verb
=
"GET,POST"
path
=
"Data.aspx"
type
=
"Comet.CometAsyncHandler, Comet"
/>
</
httpHandlers
>
</
system.web
>
|
考虑到兼容IIS7,还要在<system.webServer>节中注册,增加:
1
2
3
4
5
6
7
8
9
|
<
system.webServer
>
<!--忽略system.web中的注册-->
<
validation
validateIntegratedModeConfiguration
=
"false"
/>
<
modules
runAllManagedModulesForAllRequests
=
"true"
/>
<
handlers
>
<!--与上面完全相同-->
<
add
name
=
"CometHandler"
verb
=
"GET,POST"
path
=
"Data.aspx"
type
=
"Comet.CometAsyncHandler, Comet"
/>
</
handlers
>
</
system.webServer
>
|
6、针对MVC的特殊处理
增加HttpHandler会受mvc影响,需要在RegisterRoutes方法里加忽略:
1
2
|
//忽略Data.aspx
routes.IgnoreRoute(
"Data.aspx/{*pathInfo}"
);
|
搞定收工,请求Data.aspx时,连接会被保持住,直到通过另一个页面调用IAsyncResult对象的Call方法,当前连接才会返回。
简单测试了一下,保持3000个连接很轻松,并且都能快速响应,达到了预期。由于.net有默认最大并发连接数的限制,客户端如果开多个连接,需要把 ServicePointManager.DefaultConnectionLimit 调大一些。另外开到1300多个连接的时候就报OutOfMemoryException了,所以要多用几台客户端测。
本文转自 BoyTNT 51CTO博客,原文链接:http://blog.51cto.com/boytnt/1250084,如需转载请自行联系原作者