最近在做asp.NET长连接,用于消息的及时推送,方案如下:
1. 前端页面用jQuery.ajax请求一般处理程序MainPageAjax.ashx
2. 后端长连接页面, comet.ashx 继承自IHttpAsyncHandler(异步处理),IRequiresSessionState(需要获取登陆状态);
当有消息时,立即返回消息;如果1分钟都没有新消息,则返回空消息,以防前端异常退出,导致资源不能释放;
3. web.config配置session存储器为StateServer,<sessionState mode="StateServer" stateConnectionString="tcpip=127.0.0.1:42424" timeout="60"/>
以上3步做好后,F5运行,结果包含以上几个页面以外,其他页面全部都打不开,浏览器一直显示加载中。。。
很明显,线程全部被阻塞了,网上查资料未果,折腾了2天
从网上下了一个类似的应用叫 aspComet:https://github.com/nmosafi/aspcomet,是asp.net长链接的经典应用,发现这个应用根本没有阻塞问题
于是判断是配置文件问题,于是通过不断删除web.config的结点,终于发现在删除了session配置<sessionState mode="StateServer" stateConnectionString="tcpip=127.0.0.1:42424" timeout="60"/>节点这后,网站正常了。
之前一直对asp.Net的session机制不太了解,只知道用默认的配置,session很容易丢失。后来听说配置为外部存储之后,可以解决丢失问题,于是就配置成了StateSever,果然如此,后来也就没再深入了解session的用法。
而这次长连接的坑,让我重新了解了session的用法。《 会话状态Session》这篇文章写得很详细。
其实这个坑的原理很简单,因为asp.net或者mvc默认情况下,页面都是启用读写锁
而comet.ashx继承了IRequiresSessionState,也是启用了session读写锁,当会话开始的时候,comet.ashx便得到一个读写锁,但这个线程一直不结束,这个锁便无法释放,从而导致其他页面无法得到session锁,也就一直阻塞。
解决方法很有几种:
1,不启用外部session存储器,显示这种做法不合适,特别是对session有比较高的需求时
2,comet.ashx删除IRequiresSessionState接口,这样comet.ashx无法读写session
3,comet.ashx删除IRequiresSessionState接口,改为继承IReadOnlySessionState接口,这样可以保证comet.ashx可以读取session,但不能修改session
其实这个坑不仅仅存在长连接。只要启用了session外部存储,就会导致同一会话的并发阻塞问题。
可以做以下试验
1. 做两个页面:test1.aspx、test2.aspx
2. test1.aspx直接 Response.Write("test1");
3. test2.aspx, Response.Write("test2");Thread.Sleep(60000);//模拟长时间处理的程序
4. 在同一浏览器,先打开test2.aspx,再打开test1.aspx,会发现test1.aspx会阻塞60秒才显示出来
这是因为test2.aspx处理时间较长,阻塞了test1.aspx
注意:以上的坑都是基于同一会话的,通俗地讲就是针对当前浏览器打开的页面。而不同浏览器打开相当于不同的会话,不同会话的sessionid是不一样的,而session读写锁是针对同一sessionid的。