[IM]即时通讯调研报告

mikel阅读(1178)

1概述
1.1 IM技术概念
  IM技术全称Instant Messaging,中文翻译“即时通讯”,它是一种使人们能在网上识别在线用户并与他们实时交换消息的技术,是电子邮件发明以来迅速崛起的在线通讯方 式。IM的出现和互联网有着密不可分的关系,IM完全基于TCP/IP网络协议族实现,而TCP/IP协议族则是整个互联网得以实现的技术基础。 最早出现即时通讯协议是IRC(Internet Relay Chat),但是可惜的是它仅能单纯的使用文字、符号的方式通过互联网进行交谈和沟通。随着互连网变得高度发达,即时通讯也变得远不止聊天这么简单,自 1996年第一个IM产品ICQ发明后,IM的技术和功能也开始基本成型,语音、视频、文件共享、短信发送等高级信息交换功能都可以在IM工具上实现,于 是功能强大的IM软件便足以搭建一个完整的通信交流平台。目前最具代表性的几款的IM通讯软件有MSN、Google Talk、Yahoo Messenger、腾讯QQ等。

1.2 IM技术原理和工作方式
  典型的IM工作方式如下:登陆IM通讯中心(IM通讯服务 器),获取一个自建立的历史的交流对象列表(好友列表),然后自身标志为在线状态,当好友列表中的某人在任何时候登录上线并试图通过你的计算机联系你 时,IM系统会发一个消息提醒你,然后你能与他建立一个聊天会话通道进行各种消息如键入文字、通过语音等的交流。

  从技术上来说,IM的基本技术原理如下:

  1.IM服务器

  2.登陆或注销

  3.用户A通过列表找到B,用户B获得的消息并与之交谈

  4.通过IM服务器指引建立与B单独的通讯通道

   第一步,用户A输入自己的用户名和密码登录IM服务器,服务器通过读取用户数据库来验证用户身份,如果验证通过,登记用户A的IP地址、IM客户端软件 的版本号及使用的TCP/UDP端口号,然后返回用户A登录成功的标志,此时用户A在IM系统中的状态为在线(Online Presence)。

   第二步,根据用户A存储在IM服务器上的好友列表(Buddy List),服务器将用户A在线的相关信息发送给也同时在线的IM好友的PC机,这些信息包括在线状态、IP地址、IM客户端使用的TCP端口 (Port)号等,IM好友的客户端收到此信息后将在予以提示。

  第三步是IM服务器把用户A存储在服务器上的好友列表及相关信息回送到他的客户端机,这些信息包括也在线状态、IP地址、IM客户端使用的TCP端口(Port)号等信息,用户A的IM客户端收到后将显示这些好友列表及其在线状态。

1.3 IM通讯方式
1.3.1在线直接通讯
如 果用户A想与他的在线好友用户B聊天,他将直接通过服务器发送过来的用户B的IP地址、TCP端口号等信息,直接向用户B的PC机发出聊天信息,用户B的 IM客户端软件收到后显示在屏幕上,然后用户B再直接回复到用户A的PC机,这样双方的即时文字消息就不再IM服务器中转,而是直接通过网络进行点对点的 通讯,即对等通讯方式(Peer To Peer)。

1.3.2在线代理通讯
用户A与用户B的点对点通讯由于防火墙、网络速度等原因难以建立或者速度很慢,IM服务器将会主动提供消息中转服务,即用户A和用户B的即时消息全部先发送到IM服务器,再由服务器转发给对方。

1.3.3离线代理通讯
用户A与用户B由于各种原因不能同时在线的时候,如此时A向B发送消息,IM服务器可以主动寄存A用户的消息,到B用户下一次登陆的时候,自动将消息转发给B。

1.3.4扩展方式通讯
用户A可以通过IM服务器将信息以扩展的方式传递给B,如短信发送方式发送到B的手机,传真发送方式传递给B的电话机,以email的方式传递给B的电子邮箱等。

早 期的IM系统,在IM客户端和IM服务器之间通讯采用UDP协议,UDP协议是不可靠的传输协议,而在IM客户端之间的直接通讯中,采用具备可靠传输能力 的TCP协议。随着用户需求和技术环境的发展,目前主流的IM系统倾向于在IM客户端之间、IM客户端和IM服务器之间都采用TCP协议。

即时通讯相对于其他通讯方式如电话、传真、email等的最大优势就是消息传达的即时性和精确性,只要消息传递双方均在网络上可以互通,使用即时通讯软件传递消息,传递延时仅为1秒种

1.4兴起的嵌入式IM工具
传 统的IM在统治了互联网即时通讯领域长达十年之久,以其日趋稳定的性能,与较强的用户黏着度,至今仍统治着这个巨大的市场。然而,软件行业的技术精英们, 并不满足于此。他们厚积薄发,一直致力于开发出性能更为优越的即时通讯工具。当然,在功能上的不断完善,自然是一个必然的发展方向,在Web2.0时代, 如何大力增强用户对网站的黏着度,而不仅仅是对于IM的拥附,已经成为他们的主攻方向了。于是,嵌入式IM工具,应运而生了。

相对以往的传 统的即使沟通工具,它们需要用户下载软件包,需要用户进行安装。对于拥有IM产品的网站而言,用户在登陆网站后,不能直接使用其IM工具,对于流量与用户 的黏着度,都是有一定影响的。因此在IM与网站相互依存的今天,没有哪家网络公司,愿意将IM工具孤立开来。

于是,目前,一种新型的嵌入式IM工具就应运而生了。这种IM工具,不需要下载安装,当用户登陆网页后,该IM直接嵌套在网页中,可以直接使用。

而 在功能上,则一点也不输于传统的IM,无论是传统的文字沟通的速度与效率,还是近年来越来越成为IM工具必备的音频/视频功能,这种嵌入式IM都能提供非 常稳定的传输。更值得一提的是,因为嵌入式IM是嵌套在网页上的,软件供应商,可以根据网站需求,设计出适合网站风格的IM产品。而不是像传统的IM工 具,千篇一律,毫无个性可言。

目前,这类嵌入式IM在社区、交友、社团及协作等类型的网站上,应用已经较为广泛。在Web2.0时代,将发挥越来越重要的作用。

2即时通讯协议
IM(Instant Messaging)正在被广泛地采用,特别是在公司与它们的客户互动联接方案上。为了解决即时通讯的标准问题,IETF成立了专门的工作小组,研究和开发与IM相关的协议。
目前IM有四种协议:即时信息和空间协议(IMPP)、空间和即时信息协议(PRIM)、针对即时通讯和空间平衡扩充的进程开始协议SIP(SIMPLE)以及XMPP。PRIM与XMPP、 SIMPLE类似,但已经不再使用了。
2.1 IMPP
IMPP 主要定义必要的协议和数据格式,用来构建一个具有空间接收、发布能力的即时信息系统。到目前为止,这个组织已经出版了三个草案RFC,但主要的有两个:一 个是针对站点空间和即时通讯模型的(RFC 2778);另一个是针对即时通讯/空间协议需求条件的(RFC2779)。RFC2778是一个资料性质的草案,定义了所有presence和IM服务 的原理。RFC2779定义了IMPP的最小需求条件。另外,这个草案还就presence服务定义了一些条款,如运行的命令、信息的格式,以及 presence服务器如何把presence的状态变化通知给客户。

2.2 SIP/SIMPLE
SIMPLE是目前为止制定的 较为完善的一个。SIMPLE和XMPP两个协议,都符合RFC2778和RFC2779 。SIMPLE计划利用SIP来发送presence信息。SIP是IETF中为终端制定的协议。SIP一般考虑用在建立语音通话中,一旦连接以后,依靠 如实时协议(RTP)来进行实际上的语音发送。但SIP不仅仅能被用在语音中,也可以用于视频。SIMPLE被定义为建立一个IM进程的方法。 SIMPLE在2002年夏季得到额外的信任,目前,微软和IBM都致力于在它们的即时通讯系统中实现这个协议。
SIMPLE小组致力于进程模式 的操作,这将提升运行效率,使基于SIP的机制能够进行会议和三方电话交谈控制,也考虑到能和未来提供的许多新特性实现兼容并提升表现能力。有了进程模 式,SIMPLE使用SIP来建立一次进程,再利用SDP(进程描述协议)来实际传输IM数据。
SIMPLE是SIP for Instant Messaging and Presence Leveraging Extensions的缩写, 其目标是将SIP协议应用于IM 和出席检测业务。因为SIP和IMPP所提出的RFC2779中的要求有许多共同之处,加上SIP已受到较广泛的支持而且相对较成熟,故采用SIP实现 IMPP是很自然的选择。
SIP(Session Initiation Protocol)协议是由IETF提出的一种用于IP网络多媒体通信的应用层控制协议,其主要功能是创建、修改、终结和管理多媒体会话或呼叫,SIP协 议的语法和语义在很大程度上借鉴了SMTP和HTTP的机制,使用C/S通信模式以及文本形式的消息编码。本质上,SIP提供以下功能:①名字翻译和用户 定位:无论被呼叫方在哪里都确保呼叫达到被叫方;执行所有描述信息到定位信息的映射;确保呼叫(会话)的本质细节被支持。②特征协商:它允许与呼叫有关的 组在支持的特征上达成一致。③呼叫参与者管理:呼叫中参与者能够引入其他用户加入呼叫或取消到其他用户的连接,支持呼叫转移和呼叫保持。④呼叫特征改变: 用户能够改变呼叫过程中的呼叫特征。例如,某次呼叫开始时被置为"voice2only",但用户可以在呼叫过程中按需开启视频功能,新加入呼叫的第三方 也可以开启不同的特征。
多数即时消息和出席检测架构可以不加修改地利用SIP中已有的特性。例如,一个IM客户机向SIP注册服务器发送 REGISTER消息,通知它可以接收IM,注册服务器采用与SIP系统中其他规则相同的方法来处理登录请求。不过, SIP缺乏消息路由机制,不完全符合IMPP,还需对其进行一定的扩展,目前SIMPLE工作组已经提交了3项RFC和17项草案。SIMPLE在本质上 与SIP相同,没有采用GET和POST等数据存取方法,而采用INV ITE和BYE等信令方法来启动和结束一次呼叫或会话。SIMPLE增加了一种称为MESSAGE的新的请求方法来发送一次性的短消息,即寻呼机模式的 IM;用SUBSCR IBE发送对出席消息的询问,用NOTIFY传输出席消息。在持续较长的IM会话中,参与者在一段时间内交换多条消息,这时就要用到INV ITE信令和一种称为"消息会话中继协议"(MSRP)的传输协议;文本通过MSRP传输,话音和视频数据则和其他SIP应用一样用RTP来传输。
2.3 Jabber/XMPP
XMPP 是一种基于XML的协议,它继承了在XML环境中灵活的发展性。这表明XMPP是可扩展的。可以通过发送扩展的信息来处理用户的需求,以及在XMPP的顶 端建立如内容发布系统和基于地址的服务等应用程序。而且,XMPP包含了针对服务器端的软件协议,使之能与另一个进行通话,这使得开发者更容易建立客户应 用程序或给一个配好系统添加功能。
XMPP目前在免费源代码开放Jabber IM系统中被广泛采用。2002年,这个产品有超过5万的下载量。XMPP拥有成千的Jabber开发者,以及大约数万台配置的服务器和超过百万的终端用户。
在XMPP能够成为标准前,还需要努力克服它本身的缺点。对于网络协同工作者而言,需要加强安全性和互连性。
XMPP(Extensible Messaging and Presence Protocol,可扩展的消息和出席协议)是一种基于XML的传递出席信息(Presence)和消息路由的协议,它为不同的网络之间互联提供了一种安 全而简单的编程语言,是Jabber系统(一种开放源代码的IM系统)的基础,IETF成立了XMPP工作组并已发布了若干项草案。XMPP路由的核心是 一种类似于电子邮件的逻辑编址方案,在Jabber系统中,这一地址被称为Jabber ID,其形式为node@domain/resource。 XMPP的工作方式也与简单邮件传输协议(SMTP)相似,地址中的域(有相应的服务器)可以用普通的DNS系统来解析,由服务器来转发用户的消息。与 SMTP不同的是,节点(node)部分既可以表示用户,也可以表示应用或服务;resource是连接识别标记,允许同一个客户在同一个时刻多次登录服 务器。当节点连接到一台服务器时,它们利用来自本地目录系统的证书进行鉴权、指定资源并通知服务器向订阅者(比如说列在好友名单上的用户)发出出席公告。 服务器之间能相互发现、连接和认证,不管某节点的本地服务器(域)是什么,只要不违反保密或业务规则,它都可以通过服务器的转接而连接到XMPP社区中的 任意其他节点。
每个XMPP节(stanza)都是标有JID地址的XML结构化数据块,节的类型可以通过恰当的命名空间XML结构化数据加以扩 展。正如W3C是Web格式的标准化主体,Jabber软件基金(JSF)是XMPP节类型扩展的标准化主体,不过,任何其他组织都可以按需确定事实上的 标准格式。XMPP将出席(Presence)和相关的消息嵌入XML 结构化数据之中,使之能有效地路由至最合适的资源,起到了通用的XML结构化数据传输层的作用,这种层次结构最大程度地简化了客户端的实现。JSF提供了 到其他IM系统的网关规范,只要网关支持,客户端就可以与其他IM系统交流。

说明:
综合考虑功能、扩展能力和成熟度等各方面特征,两种协议各有特色,其对比如表1 SIMPLE与XMPP协议的比较所示。

表1 SIMPLE与XMPP协议的比较
  SIMPLE XMPP
基础 SIP协议 XML协议
成熟度 较为成熟 新兴技术
功能支持 各种即时消息通信 支持各种即时消息通信
扩展能力 一般 很强
主流厂商的支持 微软、IBM、SUN Oracle、Google
前景 将率先广泛应用 后来居上

SIP 已经在网络电话等领域得到了较广泛的应用,出现了大量的产品和方案,对现有SIP系统进行一定的修改后就能支持SIMPLE , 这是SIMPLE最大的优势。从产业上看,微软已经选择SIP作为其实时通信策略并在WindowsXP,Pocket PC和MSN Messenger中进行了部署, IBM也在大力地推进SIP的应用。微软公司宣布其Live Communications Server 2005将使用户能够与雅虎,AOL的即时通信服务的用户互联互通,进一步增强了SIMPLE作为标准化IM协议的强势地位。
XMPP的优势在于 灵活和开放。它具有XML带来的与生俱来的扩展性,任何人都可以用扩展的XML 信息来处理客户的特殊要求并在XMPP基础上构建新的应用。例如, CRM或别的企业应用系统可以作为一个XMPP的客户端注册到服务器上,在管理员许可的前提下,用户或别的应用能发现CRM的状态并向其发送数据、对其进 行操作。利用XMPP作为通用的传输机制,不同组织内的不同应用都可以进行有效的通信。由于XML很易穿过防火墙,所以用XMPP构建的应用不易受到防火 墙的阻碍。从产业上看, XMPP起源于开放源代码的Jabber系统,现在已有了大量使用该协议的IM系统,目前SUN和Oracle已经在它们的IM系统中使用XMPP,而 Google也可能在其IM系统中使用这一协议。
SIMPLE和XMPP各有其优势,前者有较成熟的应用基础和主流IM服务商的支持,在未来一段 时间内可能会成为主要的标准化IM协议;后者发挥了XML的优势,随着XML的发展,它的应用会越来越普及。在相当的一段时间内仍会出现两种标准系统共存 的状况,所以这两种系统之间的互联就成了一个新的问题。不过, XMPP支持网关扩展,用XMPP2SIP网关可以实现两类系统间的互联。

3 Web IM 实现技术
3.1基于插件的技术
如ActiveX,插件相对稳定,但插件需要用户自己允许并下载安装,而大多数用户担心安装了黑客软件或插件对计算机系统不好而不愿意安装,并且上网助手等软件也拦截插件,导致很多用户无法使用。另外,ActiveX受平台限制,只能在IE下使用。
3.2基于Flash的技术
典型的如Yahoo web messenger,结合Flash和Ajax, 
3.3纯粹的基于HTTP的技术
前端使用Ajax的Web IM:meebo, ebuddy, ILoveIm, MSN Web Messenger, KoolIM等。
后台使用comet的Web IM:meebo, gtalk等。
只有ebuddy支持wap
4服务器端实现方式
  优点 缺点
循 环的面向连接的方式 调试、编程、维护、修改容易 1.服务器一次只能处理一个客户机的请求,可能造成客户端请求的拒绝或响应变慢;2.客户机无法知道服 务器是否收到了其它客户机发给它的信息;3.服务器对客户机的处理是按照连接请求的先后顺序进行,与一个客户机的通讯故障可能对后续客户造成影响。
多进程并发方式 并行化、简单有序、互不干扰、事务化 1.创建进程需要一定的时间开销;2.考虑如何实现多个进程之间的互不干扰;3.占用较多的系统资源
多线程并发方式 除了具有多进程并发方式的优点外,与多进程相比:1.线程节约系统的资源;2.创建时的时间开销小;3.减小了对服务器资源的消耗 1.需要采用同步机制实现多个线程之间的同步与互斥;2.程序的编写、调试、维护难度大
5流行即时通讯系统分析
Web QQ
是国内最为流行的聊天工具QQ的网页式聊天工具,国外没有相关的服务是提供给QQ的,马化腾不失时机地做了基于Web浏览器的IM服务,所谓是web2.0的大势所趋。

功能特点:因为用户群过于庞大,非常有发展潜力。
目前Web QQ同样在测试阶段,只支持IE6.0的浏览器。其在线主界面保持了客户端QQ主界面的风格,在操作上可以使用快捷键;不提供聊天记录保存、语音视频聊天、文件传输等功能,纯属客户端QQ的精简版。
Web MSN
通 过试用发现,在线方式不支持视频聊天,也不能保存聊天记录;在线方式由于精简了功能,使得聊天窗口弹出及具体的文字聊天速率较理想;对浏览器的支持较好, 适用于IE5.0或更高版本、Netscape 7.1 或更高版本、Mozilla1.6或更高版本、Maxthon1.5或更高版本等。
总的说来功能较单一。
即 时消息是Windows Messenger中的一种通信和协作的模式。IM对话中使用的初始化和通信协议依所选择的服务器或服务而定。对于.NET Messenger或Exchange IM,IM文本是通过TCP连接进行传送的。当使用SIP Proxy服务器时,可以对服务器进行配置,以使用TCP UDP或"安全套接层"(Secure Sockets Layer,SSL)–贯穿TCP的安全协议–传输IM文本。
发起对话的客户向服务器发送一个请求,邀请与联系人开始对话。在服务器将邀请发送到联系人后,就可以进行IM通信了。
消 息文本通过服务器发送给对方。消息文本的定义依采用的服务器和协议而不同,通常为HTTP消息,由服务器将其封装在TCP连接中,内部包含"超文本传输协 议"(Hypertext Transfer Protocol,HTTP)或"可扩展标记语言"(Extensible Markup Language,XML)。当使用SIP IM时,可以设置在同级之间直接传送消息文本。但是采用SIP Proxy服务器的话,就需要对服务器进行配置。
Gtalk
在此在线IM方式中,同样不能实现网络通话功能(需通过下载Google Talk 客户端实现);电子邮件和即时聊天在同一页面完成,方便快捷;在浏览器支持方面做得不够,适用于IE 6.0 以上和 Firefox 1.0 以上版本。最有特色的是其聊天记录的保存和直接回复功能。

基于 Iframe 及 htmlfile 的流(streaming)方式
iframe 是很早就存在的一种 HTML 标记, 通过在 HTML 页面里嵌入一个隐蔵帧,然后将这个隐蔵帧的 SRC 属性设为对一个长连接的请求,服务器端就能源源不断地往客户端输入数据。

图 3. 基于流方式的服务器推模型
 
上 节提到的 AJAX 方案是在 JavaScript 里处理 XMLHttpRequest 从服务器取回的数据,然后 JavaScript 可以很方便的去控制 HTML 页面的显示。同样的思路用在 iframe 方案的客户端,iframe 服务器端并不返回直接显示在页面的数据,而是返回对客户端 Javascript 函数的调用,如"<script type="text/javascript">js_func("data from server ")</script>"。服务器端将返回的数据作为客户端 JavaScript 函数的参数传递;客户端浏览器的 Javascript 引擎在收到服务器返回的 JavaScript 调用时就会去执行代码。
从 图 3 可以看到,每次数据传送不会关闭连接,连接只会在通信出现错误时,或是连接重建时关闭(一些防火墙常被设置为丢弃过长的连接, 服务器端可以设置一个超时时间, 超时后通知客户端重新建立连接,并关闭原来的连接)。
使 用 iframe 请求一个长连接有一个很明显的不足之处:IE、Morzilla Firefox 下端的进度栏都会显示加载没有完成,而且 IE 上方的图标会不停的转动,表示加载正在进行。Google 的天才们使用一个称为"htmlfile"的 ActiveX 解决了在 IE 中的加载显示问题,并将这种方法用到了 gmail+gtalk 产品中。Alex Russell 在 "What else is burried down in the depth's of Google's amazing JavaScript?"文章中介绍了这种方法。Zeitoun 网站提供的 comet-iframe.tar.gz,封装了一个基于 iframe 和 htmlfile 的 JavaScript comet 对象,支持 IE、Mozilla Firefox 浏览器,可以作为参考。(请参见 参考资源)

sigslot是一个线程安全、类型安全,用C++实现的sig/slot机制(sig/slot机制就是对象之间发送和接收消息的机制)的开源代码库

网易POPO
从登录速度上,网页版网易POPO是最快的,基本上瞬间就可登录到使用界面。同样地,它也不能实现多媒体聊天功能;而且也不能保存聊天记录;未提供好友删除及好友分组的管理功能;适用于各版本浏览器。

  Web QQ Web MSN Gtalk Web POPO
登录速率 理想 一般 一般 理想
头像更改 否 否 否 否
呢称更改 否 否 否 是
好友添加 是 是 是 是
好友管理 否 否 是 否
聊天记录管理 否 否 是 否
界面美观度 理想 理想 一般 理想
总体印象 较好 一般 一般 较好

Sohu小纸条
vqq
vqq.com-vqq围围圈是一款国内在线聊天室,分免费和VIP付费两种方式。提供自定义图标的功能非常新颖。
功能特点:提供widget,把"VQQ围圈圈"聊天窗口嵌入在你自己的网页,自定义风格丰富,widget演示-点击rorol网页右下角的绿色图标展开聊天室。
radiusim
radiusim.com–基于ajax技术构建的在线web聊天服务,试用感觉不是很稳定,速度也比较慢.
支持的IM: Yahoo, AIM, MSN or GTalk
功能特点:会自动检测你的地址位置,并在radiusim中置入GOOGLE MAP标识这个位置.通过拖拉GOOGLE MAP,你能发现在地图中标注出来的各地会员.如果你想去某个城市旅游,通过radiusim的地图找位会员先了解情况倒是不错的方法.
Meebo
Meebo.com–称得上网页聊天的元老级别了,不论功能还是界面设计来说都很讨人喜欢,有几十种国家语言版本,能很好的支持中文.现在所看到的是新版的设计(原老版的报道).
支持的IM: AIM, Yahoo!, MSN, Google Talk, ICQ and Jabber
功能特点:meebome提供widget,用户可自定义widget的标题和联系人名称,大小尺寸,和聊天模板的颜色。
Skype
1. 网络结构:
1台服务器控制登陆,其余都是节点Node,拥有独立公网IP的节点为Super Node,其余为一般Node。普通Node必须通过Super Node才能加入Skype的世界。
2. 采用STUN(Simple Traversal of UDP through NAT)和TURN(Traversal Using Relay NAT) 协议来检测所处的NAT及防火墙环境
参见《私网穿越技术在软交换体系中的应用(详解)》
3. 编解码器
Global IP Sound在他的网站上专用明它为Skype提供点对点语音通讯软件:Global IP Sound provides voice processing software to Skype's peer-to-peer voice-communications。
最大特点是音频动态范围超过普通电话,能够处理50~8000hz的声音,出来的效果自然要比PSTN300~3400Hz好得多,声音更加圆润饱满
4.加密技术: AES
控制信号采用TCP传输,数据传输采用UDP传输;

6相关技术简介
6.1 Pushlets
6.2 Openfire
Openfire(原来的Wildfire)是一个跨平台,采用Java开发,开源的实时协作(RTC)服务器基于XMPP(Jabber)协议。Openfire安装和使用都非常简单,并利用Web进行管理。单台服务器可支持上万并发用户。
6.7 Json
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScript Programming Language, Standard ECMA-262 3rd Edition – December 1999的一个子集。 JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C, C++, C#, Java, JavaScript, Perl, Python等)。这些特性使JSON成为理想的数据交换语言。
JSON建构于两种结构:
· "名 称/值"对的集合(A collection of name/value pairs)。不同的语言中,它被理解为对象(object),纪录(record),结构(struct),字典(dictionary),哈希表 (hash table),有键列表(keyed list),或者关联数组 (associative array)。
· 值的有序列表(An ordered list of values)。在大部分语言中,它被理解为数组(array)。
这些都是常见的数据结构。事实上大部分现代计算机语言都以某种形式支持它们。这使得一种数据格式在同样基于这些结构的编程语言之间交换成为可能。
JSON具有以下这些形式:
对象是一个无序的"'名称/值'对"集合。一个对象以"{"(左括号)开始,"}"(右括号)结束。每个"名称"后跟一个":"(冒号);"'名称/值' 对"之间使用","(逗号)分隔。
 
数组是值(value)的有序集合。一个数组以"["(左中括号)开始,"]"(右中括号)结束。值之间使用","(逗号)分隔。
 
值(value)可以是双引号括起来的字符串(string)、数值(number)、true、false、 null、对象(object)或者数组(array)。这些结构可以嵌套。
 
字符串(string)是由双引号包围的任意数量Unicode字符的集合,使用反斜线转义。一个字符(character)即一个单独的字符串(character string)。
字符串(string)与C或者Java的字符串非常相似。
 
数值(number)也与C或者Java的数值非常相似。除去未曾使用的八进制与十六进制格式。除去一些编码细节。
 
空白可以加入到任何符号之间。 以下描述了完整的语言。
参考文献:http://www.json.org/json-zh.html
6.8 Server Push技术
推 送技术(Server Push)的基础思想是将浏览器主动查询信息改为服务器主动发送信息。服务器发送一批数据,浏览器显示这些数据,同时保证与服务器的连接。当服务器需要再 次发送一批数据时,浏览器显示数据并保持连接。以后,服务器仍然可以发送批量数据,浏览器继续显示数据,依次类推。
客户端拉曳(Client Pull)  :在客户端拖曳技术中,服务器发送一批数据,在HTTP响应或文档头标记中插入指令 ,让浏览器"在5秒内再次装入这些数据"或"10秒内前往某URL装入数据"。当指定的时间达到时,客户端就按照服务器的指示去做,或者刷新当前数据,或 者调入新的数据。
其实push 和 pull 这两种技术手段非常不同,但目的几乎一致,都是为了给最终用户方便的提供最新信息。
在服务器推送技术中,HTTP 连接一直保持着,直到服务器知道自己已结束发送数据并发送一个结束信号,或者客户端中断连接。而在客户端拖曳技术中,并不保持HTTP连接,相反,客户端被告知何时建立新连接,以及建立连接是获取什么数据。
在服务器推送中,奇妙之处在于"multipart/mixed"格式的MIME,它能够使一个报文(或HTTP响应)包含许多数据项、在客户端拖曳中,奇妙之处在于HTTP响应头标(或等效的HTML元素),它能告知客户端在指定的延时时间后执行何种动作。
服务器推送通常效率要比客户端拖曳效率高,因为它不必为后续数据建立新的连接。由于始终保持连接,即使没有数据传输时也是这样,因此服务器必须愿意分配这些TCP/IP端口,对于TCP/IP端口数有限的服务器这将是一个严重的问题。
客户端拖曳效率低,因为这必须每次为传送数据建立新的连接。但是它不必始终保持连接。
在实际情况中,建立HTTP连接通常需要花费相当多的时间,多达一秒甚至更多。因此从性能上考虑,服务器推送对于最终用户更有吸引力,特别是对于需要经常更新信息的情况下。
服 务器推送相对客户端拖曳的另一点优势是,服务器推送相对比较容易控制。例如,服务器每一次推送时都保持一个连接,但它又随时可以关闭其中的任何连接,而不 需要在服务器上设置特殊的算法。而客户端拖曳在同样的情况下要麻烦许多,它每次要与服务器建立连接,服务器为了处理将客户端拖曳请求与特定的最终用户匹配 等情况,需要使用相当麻烦的算法。
如果实现服务器推送的CGI程序是使用Shell脚本语言编写的,有时会存在一些问题。例如,客户端最终用 户中断连接,Shell程序通常不能注意到,这将使资源毫无用处的浪费掉,解决这一问题的办法是用Perl或者C来编写这类CGI程序,以使用户中断连接 时能够结束运行。
如上所述,在服务器推送中,多个响应中连接始终保持,使服务器可在任何时间发送更多的数据。一个明显的好处是服务器完全能够控制更新数据的时间和频率。另外,这种方法效率高,因为始终保持连接。缺点是保持连接状态会浪费服务器端的资源。服务器推送还比较容易中断。

接下来就大概说说服务器推送技术。
服 务器在响应请求时,HTTP使用MIME报文格式来封装数据。通常一个HTTP响应只能包含一个数据块。但MIME有一种机制可用一个报文(或HTTP响 应)表示将多个数据块,这种机制就是成为"multipart/mixed"的标准MIME类型。multipart/mixed报文大体格式如下:
Content-type:multipart/mixed;boundary=ThisRandomString
–ThisRandomString
Content-type:text/plain
第一个对象的数据。
–ThisRandomString
Content-type:text/plain
第二个对象的数据。
–ThisRandomString–

上述报文包括两上数据块,二者的类型都是"text/plain"。最后一个"ThisRandomString"后的两条短线(–)表示报文结束,后面没有数据。

对于服务器推送,使用一个"multipart/mixed"类型的变种
–multipart/x-mixed-replace。这里,"x-"表示属于实验类型。"replace"表示每一个新数据块都会代替前一个数据块。也就是说,新数据不是附加到旧数据之后,而是替代它。
 
下面是实际使用的"multipart/x-mixed-replace"类型:
Content-type:multipart/x-mixed-replace;boundary=ThisRandomString
–ThisRandomString
Content-type:text/plain
第一个对象的数据
–ThisRandomString
Content-type:text/plain
第二个(最后一个)对象的数据。
–ThisRandomString–
使 用这一技术的关键是,服务器并不是推送整个"multipart/x-mixed-replace" 报文,而是每次发送后数据块。HTTP连接始终保持,因而服务器可以按自己需要的速度和频率推送新数据,两个数据块之间浏览器仅需在当前窗口等候,用户甚 至可以到其他窗口做别的事情,当服务器需要发送新数据时,它只是源(ABC输入法没那个字*&^$#)传输管道发送数据块,客户端相应的窗口进行 自我更新。
在服务器推送技术中,"multipart/x-mixed-replace"类型的报文由唯一的边界线组成,这些边界线分割每个数据 块。每个数据块都有自己的头标,因而能够指定对象相关的内容类型和其他信息。由于"multipart/x-mixed-replace"的特性是每一新 数据块取代前一数据对象,因而浏览器中总是显示最新的数据对象。
"multipart/x-mixed-replace"报文没有结尾。也就是 说,服务器可以永远保持连接,并发送所需的数据。如果用户不再在浏览器窗口中显示数据流,或者浏览器到服务器间的连接中间(例如用户按"STOP"按 钮),服务器的推送才会中断。这是人们使用服务器推送的典型方式。
当浏览器发现"Content-type"头标或到达头标结束处时,浏览器窗口中的前一个文档被清除,并开始显示下一个文档。发现下一个报文边界时,就认为当前数据块(文档)已经结束。
总之,服务器推送的数据由一组头标(通常包括"Content-type")、数据本身和分割符(报文边界)三部分组成。浏览器看到分割符时,它保持状态不变,直到下一个数据块到达。
将以上概念进行用编程方法实现,就可以得到实际的服务器推送程序。例如,下面的Unix shell程序将使浏览器每5秒显示一次服务器上的进程列表:

#!/bin/sh
echo "HTTP/1.1 200"
echo "Content-type: multipart/x-mixed-replace; boundary=–ThisRandomString–"
echo ""
echo "–ThisRandomString–"
while true
do
echo "Content-type: text/html"
echo ""
echo "h2Processes on this machine updated every 5 seconds/h2"
echo "time:"
date
echo "p"
echo "plaintext"
ps -el
echo "–ThisRandomString–"
sleep 5
done

注意到,边界设置在sleep语句之前发送,这能够确保浏览器清除其缓冲区,并显示所接收到的最新数据。
NCSA HTTPD用户在内容类型中不能使用空格,包括边界参数。NCSA HTTPD只能将不带空格字符的字符串作为内容类型。如果在内容类型行中存在空格(冒号后面的空格除外),空格后的任何文本都会被删除。
 
下面的示例是正确的:
Content-type: multipart/x-mixed-replace;boundary=ThisRandomString
而下例则不能正常工作,因为它在中间有空格:
Content-type: multipart/x-mixed-replace; boundary=ThisRandomString
服务器推送的另一个优点是它可以针对单个内联图象进行。包括图象的文档可以由服务器定时或定周期进行更新。而实现这一点非常简单:只需使IMG元素的SRC属性指向推送一系列图象的URL即可。
如果服务器推送用于单个内联图象,文档中的图象就会一次次被新推送来的图象所代替,而文档本身不需变化(假设文档没有进行服务器推送)。这样,WEB页面中有限的动画就可以为静态画面所代替。

客户端拖曳
客户端拖曳的一个简单用法是使文档按固定周期自动重载。例如,考虑下面的HTML文档:
<META HTTP-EQUIV="Refresh" CONTENT=1>
<TITLE>Document ONE</TITLE>
<H1>This is Document ONE!</H1>
Here's some text.<P>
如果将它载入支持动态文档的浏览器(Netscape 1.1以上,Internet Explorer和Mosaic也支持客户端拖曳),它将每隔一秒将自己重载一次。
由于META元素实际是在HTML文档中模拟HTTP响应头标,所以它能够告知浏览器将自身信息当作HTTP响应使用。上例中的META标记相当于:
Refresh:1
这样,实际上就是HTTP头标告知浏览器每一秒更新一次文档。如果需要延时是12秒,那么就是这样的指令:
<META HTTP-RQUIV="Refresh" CONTENT=12>
那么它等效于:
Refresh:12

关于客户端的拖曳我也懒的继续写下去,关于怎么使客户端自动申请其他URL的数据话,请使用如下:
<META HTTP-EQUIV="Refresh" CONTENT="12;URL=">http://icools.yeah.net/">
注意的是,此处的URL不能使用相对路径,必须全部指定。

其中时间间隔可以设置为0,这样浏览器在当前文档显示完毕后,以最快的速度载入新的数据!

实 现Server   push技术非常简单。Server   push在服务器的CGI脚本声明HTML文档类型时,把传统的content-type:text/html改为content- type:multipart/x-mixed-replace;boundary=BOUNDARY这样的文档类型,就会反馈给用户一个 Server   push类型的连接。这是Server   push和Client   pull的根本区别。如果CGI脚本中提供了这样的HTML文档头,服务器在处理客户机请求调用CGI脚本程序时,就会把CGI脚本中指定的数据强行推给 客户机。  
        Server   push在生成页面时会采用很多的技巧来处理用户端浏览器页面的生成。主程序和传统方式没有本质的区别,但记得在脚本中加入print"Content- Type:multipart/x-mixed-replace;boundary=BOUNDARY
6.9 Comet
目前最强大的开源Comet解决方案是:
Dojo+Jetty Cometd+Jetty Continuation+Bayeux协议
一些相关的文档先放在这里,我就不多介绍了,大家都完全有能力读懂。
Jetty的作者,Servlet规范专家组成员Greg Wilkins写的两篇文章:
Ajax, Comet and Jetty:
http://www.webtide.com/downloads/whitePaperAjaxJetty.html
Cometd with Jetty:
http://blogs.webtide.com:8080/gregw/2006/08/03/1154583360000.html
Bayeux协议:
http://svn.xantus.org/shortbus/trunk/bayeux/protocol.txt
一种基于JSON的、平台中立的分路复用协议,可以由任何Comet客户端和服务器端实现。目前客户端的Dojo、服务器端的Jetty Cometd已经实现了对这个协议的支持。
一个使用这个解决方案的实例:
Active AJAX based live dashboards:
http://www.qenet.co.uk/warwick/whitepaper-pushTech.pdf
根据Greg Wilkins的测试,最后Jetty Cometd服务10000个用户875个线程,只用了57M内存。
http://groups.google.com/group/cometd-dev/browse_thread/thread/09d80fb4abdc4f5c
Pushlets作者Just van den Broecke也承认,Pushlets存在着可伸缩性的问题:
"Yes, I am aware of the scalability limitations of the Pushlets framework. A dedicated server-side technique based on NIO (such as Greg, hi there, is working on ?) could help."

并且申请加入Cometd的开发工作:
"With great interest I have been following recent COMET developments and would like to join cometd developments in whatever way."
6.9.1 Dojo
Dojo是一个非常强大面向对象,开源的JavaScript工具箱。它为开发Web胖客户端程序提供了一套完整的Widget和一些特效操作。
官方网站:http://www.dojochina.com

6.10 Ruby、Rails
ruby是一种面向对象的动态脚本语言。它的语法很灵活,而且提供了丰富的类库。因此,用ruby编写程序的效率是非常高的。
Rails 是使用纯ruby编写的框架(framework)。它对web开发提供了强有力的支持,如支持数据映射、MVC模式、Web Services、安全等。而且这些功能操作起来要比同类的产品容易的多,如MVC模式就比struts更容易使用。除了这些,rails还可以根据模板 自动生成web程序。这样可以省去我们很多的时间。
6.11 Erlang

附录1.常用开源Jabber服务器介绍
1. Openfire (Wildfire) 3.x
授权:GPL or 商用
操作系统平台:所有(使用Java开发)
XMPP Jabber 协议实现情况:98%
Tim 评价:
安 装和使用非常简单,安装后进入Web界面进行2~3分钟的配置所有的东西都设好了。使用Java语言开发,在目前Java开发人员到处普及的情况下进行维 护和扩展的成本非常低。在我的测试中加上 Connection Manager 的情况下单台服务器可支持 30 万并发用户。缺点是目前还不支持cluster。如果企业内部部署IM使用 Wildfire + Spark 是最佳的组合。
见:http://hi.baidu.com/jabber/blog/category/Wildfire
2. ejabberd
授权:GPL
操作系统平台:Linux,Windows,MacOS X等
XMPP Jabber 协议实现情况:91%
Tim 评价:
Ejabberd目前是可扩展性最好的一种Jabber/XMPP服务器,支持分布多个服务器,并且具有容错处理,单台服务器失效不影响整个cluster运作。
顾虑就是它采用一种大家都没听过的语言Erlang开发,所以很多人可能会象我一样因为这个原因放弃了它。
3. Jabberd 2.x
授权:GPL
操作系统平台:主要是 Linux,(Windows 也支持,但不知道性能怎样)
XMPP Jabber 协议实现情况:76%
Tim 评价:
自从jabber.org改用ejabberd之后,Jabberd一直都在走下坡路。扩展性比不上ejabberd, 易用性比不上 Wildfire,唯一的优势是使用C开发,如果你坚持要用C开发,那么还是选择jabberd吧。
4. Jabberd 1.x
授权:GPL
操作系统平台:主要是 Linux, (Windows 也支持,但不知道性能怎样)
XMPP Jabber 协议实现情况:45%
Tim 评价:
在几年前 jabberd 就是 Jabber 的代名词,至今很多 Jabber 文档仍然介绍的是 Jabber 1.4,在我以前写的《Jabber 服务器占有率比较》中仍然排名第一。但是它很多新的规范都不支持,相信大部分用户都将转向新的服务器。
见:http://hi.baidu.com/jabber/blog/item/7d25bb199f31a44542a9ad02.html
5. 后起之秀 DJabberd
授权:open source
操作系统平台:主要是 Linux,(Perl写的,其他平台应该也支持)
XMPP Jabber 协议实现情况:N/A
Tim 评价:
djabberd 使用 epoll 技术,理论上单台服务器可以支持更多用户。Djabberd目前主要应用在LiveJournal上,大部分XMPP协议都支持,稳定性也不用置疑。但是因为推出时间尚短,很多细节功能可能需要时间慢慢完善。
djabberd介绍见:http://hi.baidu.com/jabber/blog/category/Djabberd
附录2.使用comet技术的实例
GMail's GTalk integration
Jot Live
Renkoo
cgi:irc
Meebo

信息来源:http://alex.dojotoolkit.org/?p=545
附录3.基于SIP协议的即时通讯产品
这 类产品,目前也有一些。但大部分属于个人或小团体开发,因此功能上还很不完善。一般只支持文本信息的实时传递,也有一些产品可以利用SIP协议进行语音的 实时通讯,主要应用在SIP电话上,而且成熟的产品也并不多见。可以实现文本,语音,视频的真正意义上的利用SIP协议开发的多媒体即时通讯产品还并不多 见。所以,开发基于SIP协议的即时通讯系统,是具有一定价值和意义的。下面列举一些利用SIP协议的开发的即时通讯产品。
(1) X- Pro/X-Lite
Xten Network公司的这一产品,是一个比较成熟的商业化产品,属于基于SIP协议的软电话工具。支持最多六条线路并发的语音通讯,支持最多十方参加的语音电话会议,自动应答,自动选择编解码方式等。
(2 ) KPhone
KPhone是一个运行在Linux平台的SIP客户端程序。利用KPhone,可以和远端用户建立VOIP连接,实现双方的即时通讯。
(3 ) SIPPS
Ahead Software AG公司推出的SIPPS也是一个基于SIP协议的软电话工具。SIPPS具有很多特点,包括良好的语音效果,易用性,加解密机制,电话录音,以及兼容MSN Messenger等。
(4)SIP Multimedia PC Client
Nortel Networks公司推出的SIP Multimedia PC Client是一个功能全面的真正意义的多媒体即时通讯工具。利用SIP Multimedia PC Client,可以不仅仅实现传递文本信息,也可以建立语音和视频连接,从而真正实现多媒体信息的实时通讯。
附录4.为comet技术改进的Server
(1)Jetty http://www.mortbay.org/ 100% Java
(2)Tomcat6
(3)下面的Server都是专门为Comet设计的,也提供了客户端的JS库:
Lightstreamer http://www.lightstreamer.com Demo with Dojo
Orbited http://www.orbited.org/
Meteor http://meteorserver.org/
(4)iPush Server[台湾] www.icetechnology.com
附录4. Comet实现
一些其他Comet Ajax服务器推送模型的实现:
· Orbited :一种开源的分布式Comet服务器
· AjaxMessaging :Ruby on Rails的Comet插件
· Pushlets :一个开源框架,可以让服务器端java对象推送事件到浏览器端javascript,java applet,或者flash应用程序
· Lightstreamer :提供基于AJAX-COMET模式的HTTP流的商业实现
· Pjax :Ajax的推送技术
· Virgil's One?
· SmartClient
· Fjax
· Server-Sent Events
· COMETd
· Ajax for IBM WebSphere Platform
信息来源:http://wiki.matrix.org.cn/Wiki.jsp?page=Comet
附录5. 前台技术
AJAX框架
Ext
是一组扩展自Yahoo!UI,具有CS风格的Web用户界面组件。主要UI包括:dialog,grid,layout,tabs等。
http://www.ajaxjs.com
基于ruby的rails框架
Flash
附录6. 后台技术
1. 开发语言
(1) Erlang
(2) C
(3) Perl
(4) C#
(5) JAVA
2. 操作系统
UNIX/LINUX/Windows都有,一般都采用多台服务器集群实现负载均衡。
3. 服务器结构
(1) 循环服务器
(2) 并发服务器:多进程/多线程
4. 数据交换格式
(1) JSon
(2) XML

[C#]《你不常用的c#之五》:Thread与ThreadPool的内存之战

mikel阅读(915)

《你不常用的c#之五》:Thread与ThreadPool的内存之战

Thread与ThreadPool使用的时候在内存里对象是如何分布的呢?
今天我们就从内存堆的角度分析下两者。
先上小白鼠代码:

static void Main(string[] args)         {             for (int i = 0; i < 30; i++)             {                 Thread t = new Thread(new ThreadStart(ThreadProc));                 t.Name = "Overred_" + i;                 t.Start();             }             Console.Read();         }         static void ThreadProc()         {             try             {                 for (int i = 0; i < 10; i++)                 {                      Console.WriteLine("{0}  Value:{1}",Thread.CurrentThread.Name,i);                 }                             }             catch (Exception ex)             {                 Console.WriteLine(ex.Message);             }         }
以上代码非常简单,就是循环启动30个线程去执行同一个方法ThreadProc(),然后打印出结果。 现在提出问题1:当Main里的30个线程都把ThreadProc()方法执行完毕后,这些Threads是自动消亡还是被GC回收,还是变成DeadThread? 好,拿出我们的看家工具windbg,来debug一把。 首先启动我们的程序,然后打开windbg,然后F6,Attach我们的exe 1,加载mscorwks(.net 2.0或者以上)
0:003> .loadby sos mscorwks 

2,查看该程序的线程情况

0:003> !Threads
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for 
C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorwks.dll – 
PDB symbol for mscorwks.dll not loaded
ThreadCount: 32
UnstartedThread: 0
BackgroundThread: 1
PendingThread: 0
DeadThread: 30
Hosted Runtime: no
                                      PreEmptive   GC Alloc           Lock
       ID OSID ThreadOBJ    State     GC       Context       Domain   Count APT Exception
   0    1 25e4 00518858      a020 Enabled  013f878c:013f9fe8 00514818     1 MTA
   2    2 24b8 00526f20      b220 Enabled  00000000:00000000 00514818     0 MTA (Finalizer)
XXXX    3    0 00533028      9820 Enabled  00000000:00000000 00514818     0 Ukn
XXXX    4    0 00536858      9820 Enabled  00000000:00000000 00514818     0 Ukn
XXXX    5    0 005385c8      9820 Enabled  00000000:00000000 00514818     0 Ukn
XXXX    6    0 005393d0      9820 Enabled  00000000:00000000 00514818     0 Ukn
XXXX    7    0 00534fd8      9820 Enabled  00000000:00000000 00514818     0 Ukn
XXXX    8    0 0053a5c0      9820 Enabled  00000000:00000000 00514818     0 Ukn
XXXX    9    0 0053b3c8      9820 Enabled  00000000:00000000 00514818     0 Ukn
XXXX    a    0 0053bfc0      9820 Enabled  00000000:00000000 00514818     0 Ukn
XXXX    b    0 0053eba8      9820 Enabled  00000000:00000000 00514818     0 Ukn
XXXX    c    0 00543370      9820 Enabled  00000000:00000000 00514818     0 Ukn
XXXX    d    0 00543b38      9820 Enabled  00000000:00000000 00514818     0 Ukn
XXXX    e    0 00544700      9820 Enabled  00000000:00000000 00514818     0 Ukn
XXXX    f    0 00544ec8      9820 Enabled  00000000:00000000 00514818     0 Ukn
XXXX   10    0 00545690      9820 Enabled  00000000:00000000 00514818     0 Ukn
XXXX   11    0 00545ee0      9820 Enabled  00000000:00000000 00514818     0 Ukn
XXXX   12    0 005466c0      9820 Enabled  00000000:00000000 00514818     0 Ukn
XXXX   13    0 00546a88      9820 Enabled  00000000:00000000 00514818     0 Ukn
XXXX   14    0 00546e50      9820 Enabled  00000000:00000000 00514818     0 Ukn
XXXX   15    0 00547218      9820 Enabled  00000000:00000000 00514818     0 Ukn
XXXX   16    0 005475e0      9820 Enabled  00000000:00000000 00514818     0 Ukn
XXXX   17    0 005479a8      9820 Enabled  00000000:00000000 00514818     0 Ukn
XXXX   18    0 00547d70      9820 Enabled  00000000:00000000 00514818     0 Ukn
XXXX   19    0 00548138      9820 Enabled  00000000:00000000 00514818     0 Ukn
XXXX   1a    0 00548500      9820 Enabled  00000000:00000000 00514818     0 Ukn
XXXX   1b    0 005488c8      9820 Enabled  00000000:00000000 00514818     0 Ukn
XXXX   1c    0 00548c90      9820 Enabled  00000000:00000000 00514818     0 Ukn
XXXX   1d    0 00549058      9820 Enabled  00000000:00000000 00514818     0 Ukn
XXXX   1e    0 00549420      9820 Enabled  00000000:00000000 00514818     0 Ukn
XXXX   1f    0 005497e8      9820 Enabled  00000000:00000000 00514818     0 Ukn
XXXX   20    0 00549bb0      9820 Enabled  00000000:00000000 00514818     0 Ukn

看红色加粗部分,我们总共有32个线程,而DeadThread为30个(其他2个为程序自身所有,其中一个BackgroundThread), 先告诉你这30个死线程正式我们循环创建的线程,可以回答我提的第一个问题拉,没错,他们统统死拉,而且不会醒来,还占地方(不是永远占地方,待会我们用 GC手动让它们消亡)。

3,然后我们继续看看内存堆上它们这些坏家伙如何分布:

0:003> !DumpHeap -type System.Threading -stat
total 155 objects
Statistics:
      MT    Count    TotalSize Class Name
79108930        1           32 System.Threading.ContextCallback
790fe284        2          144 System.Threading.ThreadAbortException
79124b74       30          600 System.Threading.ThreadHelper
79104de8       31         1116 System.Threading.ExecutionContext
790fe704       31         1736 System.Threading.Thread
791249e8       60         1920 System.Threading.ThreadStart
Total 155 objects
 
红色部分,31个Thread,对应着31个Context,每个线程在windows底层都是一个内核对象和一个栈空间,内核对象存放一些线程的统计信息,比如计数器以及一个上下文,就是我上次执行到那里等。而栈空间则是用来存放线程参数等。
 

4,我们来具体看下这些Thread们的MethodTable

0:003> !DumpHeap -MT 790fe704 
 Address       MT     Size
013c1708 790fe704       56     
013c178c 790fe704       56     
013c235c 790fe704       56     
013c2474 790fe704       56     
013c258c 790fe704       56     
013c26a4 790fe704       56     
013c27bc 790fe704       56     
013c28d4 790fe704       56     
013c29ec 790fe704       56     
013c2b04 790fe704       56     
013c2c1c 790fe704       56     
013c2d34 790fe704       56     
013c2e54 790fe704       56     
013c2f74 790fe704       56     
013c3094 790fe704       56     
013c31b4 790fe704       56     
013c32d4 790fe704       56     
013c33f4 790fe704       56     
013c3514 790fe704       56     
013c3634 790fe704       56     
013c3754 790fe704       56     
013c3874 790fe704       56     
013c3994 790fe704       56     
013c3ab4 790fe704       56     
013c3bd4 790fe704       56     
013c3cf4 790fe704       56     
013c3e14 790fe704       56     
013c3f34 790fe704       56     
013f8084 790fe704       56     
013f81a4 790fe704       56     
013f82c4 790fe704       56     
total 31 objects
Statistics:
      MT    Count    TotalSize Class Name
790fe704       31         1736 System.Threading.Thread
Total 31 objects

 

5,随便拿一个线程的Address来看看到底是谁占着我们的Thread而不让我们的GC回收掉


0:003> !GCRoot 013c3bd4 Note: Roots found on stacks may be false positives. Run "!help gcroot" for more info. Scan Thread 0 OSTHread 25e4 Scan Thread 2 OSTHread 24b8 DOMAIN(00514818):HANDLE(WeakSh):241298:Root:013c3bd4(System.Threading.Thread)

结果另我们很失望,他自己就是根,并没被其他任何对象所引用,什么情况下会出现此情况呢?我们先来看看对象在内存中分布的几种方式,我们只需在windbg里执行如下命令则知:

0:003> !Help gcroot
——————————————————————————-
!GCRoot [-nostacks] 
<Object address>
!GCRoot looks for references (or roots) to an object. These can exist in four
places:
   1. On the stack
   2. Within a GC Handle
   3. In an object ready for finalization
   4. As a member of an object found in 1, 2 or 3 above.

First, all stacks will be searched for roots, then handle tables, and finally
the freachable queue of the finalizer. Some caution about the stack roots: 
!GCRoot doesn't attempt to determine if a stack root it encountered is valid 
or is old (discarded) data. You would have to use !CLRStack and !U to 
disassemble the frame that the local or argument value belongs to in order to 
determine if it is still in use.
Because people often want to restrict the search to gc handles and freachable
objects, there is a -nostacks option.
 
windbg已经很清楚的告诉我们,
一个对象可以
1,在栈上
2,在一个GCHandle里(可以执行!GCHandles命令查看)
3,在FinalizeQueue里
4,是一个对象的成员
难道对象就必定在以上的“四行”之中吗?答案是不一定,还有个Gchandleleaks,就是你在内存里看不到这个Handle,它已经leak。(这种也算在GCHandle里吧)。
回头我们接着说他自己没被其他任何对象所引用,自己就是个根,但是GC却不搭理它,为何?那就是他在GCHandle里,
 
0:003> !GCHandles
GC Handle Statistics:
Strong Handles: 14
Pinned Handles: 4
Async Pinned Handles: 0
Ref Count Handles: 0
Weak Long Handles: 0
Weak Short Handles: 31
Other Handles: 0
Statistics:
      MT    Count    TotalSize Class Name
790fd0f0        1           12 System.Object
790fcc48        1           24 System.Reflection.Assembly
790feba4        1           28 System.SharedStatics
790fe17c        1           72 System.ExecutionEngineException
790fe0e0        1           72 System.StackOverflowException
790fe044        1           72 System.OutOfMemoryException
790fed00        1          100 System.AppDomain
79100a18        4          144 System.Security.PermissionSet
790fe284        2          144 System.Threading.ThreadAbortException
790fe704       32         1792 System.Threading.Thread
7912d8f8        4         8736 System.Object[]
Total 49 objects
而且在FinalizeQueue里也有它的踪影:
 
0:003> !FinalizeQueue
SyncBlocks to be cleaned up: 0
MTA Interfaces to be released: 0
STA Interfaces to be released: 0
———————————-
generation 0 has 35 finalizable objects (00526658->005266e4)
generation 1 has 0 finalizable objects (00526658->00526658)
generation 2 has 0 finalizable objects (00526658->00526658)
Ready for finalization 0 objects (005266e4->005266e4)
Statistics:
      MT    Count    TotalSize Class Name
791037c0        1           20 Microsoft.Win32.SafeHandles.SafeFileMappingHandle
79103764        1           20 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle
79101444        2           40 Microsoft.Win32.SafeHandles.SafeFileHandle
790fe704       31         1736 System.Threading.Thread
Total 35 objects
 
下面就来解释下什么才可以在FinalizeQueue里出现呢?答案就是有身份的人,很有身份的人,享受特殊待遇的哦!
啥身份,就是自身实现拉析构函数。
啥待遇,就是GC两次才有可能把他们部分清理掉!为啥部分,是我们不知道windows到底何时去把所有的清理掉(赖皮阿)
具体原理大家可以看.net框架去,我这里不多说。
 

说到此,也就找到我们当初30个彪形大汉为啥赖着不走的原因拉,是在0代的第一次GC时候,他们被放进FinalizeQueue,等着第二次GC他们部分才会从内存堆上消亡。
为证明我们的观点,我们可以修改程序为 :

static void Main(string[] args)         {             for (int i = 0; i < 30; i++)             {                 Thread t = new Thread(new ThreadStart(ThreadProc));                 t.Name = "Overred_" + i;                 t.Start();             }             GC.Collect();             GC.Collect();             Console.Read();         }

首先声明一点就是当我们调用一次GC.Collect();时,并不是执行一次垃圾收集,只是告诉系统我要强制进行垃圾收集,系统听到这个命令后乖不乖那就不一定拉。
当我们用Reflector查看mscorlib对Thread实现的使用也会发现他实现拉析构:

    ~Thread()     {         this.InternalFinalize();     }
 

来个虎头蛇尾吧,当我们把小白鼠程序使用ThreadPool修改为:

 static void Main(string[] args)
        {
            
for (int i = 0; i < 30; i++)
            {
                ThreadPool.QueueUserWorkItem(
new WaitCallback(ThreadProc));
            }
            Console.Read();
        }
        
static  void ThreadProc(object o)
        {
            
try
            {
                
for (int i = 0; i < 10; i++)
                {
                     Console.WriteLine(
" Value:{0}",i);
                }
               
            }
            
catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
 
再用windbg查看线程时则为:

0:006> !Threads
*** ERROR: Symbol file could not be found.  Defaulted to export symbols 
for C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorwks.dll – 
PDB symbol for mscorwks.dll not loaded
ThreadCount: 4
UnstartedThread: 0
BackgroundThread: 3
PendingThread: 0
DeadThread: 0

而FinalizeQueue则为:

0:006> !FinalizeQueue
SyncBlocks to be cleaned up: 0
MTA Interfaces to be released: 0
STA Interfaces to be released: 0
———————————-
generation 0 has 7 finalizable objects (00266658->00266674)
generation 1 has 0 finalizable objects (00266658->00266658)
generation 2 has 0 finalizable objects (00266658->00266658)
Ready for finalization 0 objects (00266674->00266674)
Statistics:
      MT    Count    TotalSize Class Name
791037c0        1           20 Microsoft.Win32.SafeHandles.SafeFileMappingHandle
79103764        1           20 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle
79101444        2           40 Microsoft.Win32.SafeHandles.SafeFileHandle
790fe704        3          168 System.Threading.Thread
Total 7 objects

为啥还有3个,呵呵,这你可以继续用反射工具查看ThreadPool的实现,在其核心代码ExecutionContext : ISerializable里使用拉一个Thread。
那现在又出现问题拉,既然ThreadPool这么好,那我们为啥还使用Thread呢?这个问题就是ThreadPool有个 GetMaxThreads,可以通过GetMaxThreads(out int workerThreads, out int completionPortThreads);方法获取到,如果线程池满拉,则会死锁更严重!
另:ThreadPool都为后台线程。
究竟使用那个,根据情况而定,理解拉内在的东西,一切表象就简单拉。
OK,到此吧。。。

希望本文能对你有所帮助,谢谢!

Taheta 标签: ,,

http://www.taheta.org/

[Tools]Windbg下载

mikel阅读(867)

下载地址:
http://www.microsoft.com/whdc/devtools/Debugging/installx86.mspx
Symbols path的设置
   打开windbg之后,按Ctrl+S,然后把这段(带下划线的部分)paste上:srv*c:\symcache*http://msdl.microsoft.com/download/symbols;c:\symcache;
   注意的是,那个c:\symcache是我常用的路径,你可以根据你的习惯,设置为d:\symbols等。

加载sos.dll
   
SOS是为了方便调试.NET程序的一个add-in,不知道哪个牛人写的……在.net framework 1.x下面,直接在windbg的命令行里面,执行.load clr10\sos.dll即可。
   在.net framework 2.0里面,我们可以到%windir%\microsoft.net\framework\v2.0.50727下面,把sos.dll,复制到 windbg安装目录下面。我一般是这样:在windbg目录下面,创建一个目录叫做clr20,然后copy sos.dll %programfiles%\Debugg~1\clr20。搞好后,加载命令是一样的:.load clr20\sos.dll即可。

[C#]使用Action、Func和Lambda表达式 在.NET在,我们经常使用委托,委托的作用

mikel阅读(1007)

使用ActionFuncLambda表达式

.NET,我们经常使用委托,委托的作用不必多说,在.NET 2.0之前,我们在使用委托之前,得自定义一个委托类型,再使用这个自定义的委托类型定义一个委托字段或变量。.NET 2.0给我们带来了ActionFunc两个泛型委托,.NET3.0给我们带来了Lambda,这一切使得委托的定义和使用变得简单起来。下面的例子中的委托都使用了Lambda表达式。

.Action系列的泛型委托

Action系列的委托定义的是没有返回值(返回值为void)的委托。它有多个版本包括没有输入参数,1个输入参数,2个输入参数,3个输入参数,4个输入参数共5个版本这几个版本的原型如下:

1.       没有输入参数返回值为void的委托.

Action委托 封装一个方法,该方法不采用参数并且不返回值。

可以使用此委托以参数形式传递一个执行某操作的方法,而不用显式声明一个自定义的委托来封装此方法。该封装的方法必须与此委托定义的方法签名相对应。这意味着该方法不得具有参数和返回值。例:

using System;

using System.Windows.Forms;

public class Name

{

   private string instanceName;

   public Action ShowName;

   public Show()

{

   If(ShowName != null)

    ShowName();

}

   public Name(string name)

   {

      this.instanceName = name;

   }

   public void DisplayToConsole()

   {

      Console.WriteLine(this.instanceName);

   }

   public void DisplayToWindow()

   {

      MessageBox.Show(this.instanceName);

   }

}

public class ActionStudy

{

   public static void Main()

   {

      Name testName = new Name("Koani");

      testName.ShowName  = () => testName.DisplayToWindow();

      testName.Show();

   }

}

2.       1个输入参数返回值为void的委托

Action<T>泛型委托封装一个方法,该方法只采用一个参数并且不返回值。

可以使用此委托以参数形式传递方法,而不用显式声明自定义的委托。该方法必须与此

委托定义的方法签名相对应。也就是说,封装的方法必须具有一个通过值传递给它的参数,并且不能返回值。例:

using System;

using System.Windows.Forms;

 

public class ActionStudy

{

   public static void Main()

   {

      Action<string> messageTarget;

      if (Environment.GetCommandLineArgs().Length > 1)

         messageTarget = s => MessageBox.Show(s);

      else

         messageTarget = s => Console.WriteLine(s);

 

      messageTarget("Hello, World!");

   }

}

下面的示例演示如何使用 Action(T) 委托来打印 List(T) 对象的内容。在此示例中,使用 Print 方法将列表的内容显示到控制台上。此外,C# 示例还演示如何使用匿名方法将内容显示到控制台上。

using System;

using System.Collections.Generic;

 

class Program

{

    static void Main()

    {

        Action<string> PrintInConsole = s => Console.WriteLine(s);

        Action<string> PrintInDialog = s=>MessageBox.Show(s);

        List<String> names = new List<String>();

        names.Add("Bruce");

        names.Add("Alfred");

        names.Add("Tim");

        names.Add("Richard");

        names.ForEach(PrintInConsole);

        names.ForEach(PrintInDialog);      

    }

}

3.       2个输入参数返回值为void的委托

Action<T1,T2> 封装一个方法,该方法具有两个参数并且不返回值。

可以使用 Action(T1, T2) 委托以参数形式传递方法,而不用显式声明自定义的委托。该

方法必须与此委托定义的方法签名相对应。也就是说,封装的方法必须具有两个均通过值传递给它的参数,并且不能返回值。

using System;

using System.IO;

 

public class ActinStudy

{

   public static void Main()

  {

      string message1 = "The first line of a message.";

      string message2 = "The second line of a message.";

      Action<string, string>  concat;

 

      if (Environment.GetCommandLineArgs().Length > 1)

         concat = (s1, s2) =>

{

StreamWriter writer = null; 

      try

      {

         writer = new StreamWriter(Environment.GetCommandLineArgs()[1], false);

         writer.WriteLine("{0}"n{1}", s1, s2);

      }

      catch

      {

         Console.WriteLine("File write operation failed…");

      }

      finally

      {

         if (writer != null) writer.Close();

      }

};

      else

         concat = (s1, s2) => Console.WriteLine("{0}"n{1}", s1, s2);

 

      concat(message1, message2);

   }

4.       3个输入参数返回值为void的委托

Action<T1,T2,T3>委托,封装一个方法,该方法采用三个参数并且不返回值。

可以使用 Action(T1, T2, T3) 委托以参数形式传递方法,而不用显式声明自定义的委托。

该方法必须与此委托定义的方法签名相对应。也就是说,封装的方法必须具有三个均通过值传递给它的参数,并且不能返回值。

5.       4个输入参数返回值为void的委托

Action<T1,T2,T3,T4>委托, 封装一个方法,该方法具有四个参数并且不返回值。

可以使用 Action(T1, T2, T3, T4) 委托以参数形式传递方法,而不用显式声明自定义的委托。封装的方法必须与此委托定义的方法签名相对应。也就是说,封装的方法必须具有四个均通过值传递给它的参数,并且不能返回值。

.Func系统的泛型委托

Func系列的委托定义的是返回值的委托。它有多个版本包括没有输入参数,1个输入参数,2个输入参数,3个输入参数,4个输入参数共5个版本这几个版本的原型如下:

1.       没有输入参数有返回值(返回值不为void)的委托

Func<TResult>封装一个不具有参数但却返回 TResult 参数指定的类型值的方法。
可以使用此委托构造一个能以参数形式传递的方法,而不用显式声明自定义的委托。该

方法必须与此委托定义的方法签名相对应。这意味着封装的方法不得具有参数,但必须返回值。

2.       具有一个输入参数有返回值(返回值不为void)的委托

   Func<T,TResult>封装一个具有一个参数并返回 TResult 参数指定的类型值的方法。

可以使用此委托构造一个能以参数形式传递的方法,而不用显式声明自定义的委托。该方法必须与此委托定义的方法签名相对应。也就是说,封装的方法必须具有一个通过值传递给它的参数,并且必须返回值。

3.       具有二个输入参数有返回值(返回值不为void)的委托

  Func<T1,T2,TResult>封装一个具有一个参数并返回 TResult 参数指定的类型值的方法。

可以使用此委托构造一个能以参数形式传递的方法,而不用显式声明自定义的委托。该方法必须与此委托定义的方法签名相对应。也就是说,封装的方法必须具有两个均通过值传递给它的参数,并且必须返回值

4.       具有三个输入参数有返回值(返回值不为void)的委托

   Func<T1,T2,T3,TResut>封装一个具有三个参数并返回 TResult 参数指定的类型值的方法。

可以使用此委托构造一个能以参数形式传递的方法,而不用显式声明自定义的委托。该方法必须与此委托定义的方法签名相对应。也就是说,封装的方法必须具有三个均通过值传递给它的参数,并且必须返回值。

5.       具有四个输入参数有返回值(返回值不为void)的委托

 Func<T1,T2,T3,TResult>封装一个具有四个参数并返回 TResult 参数指定的类型值的方法。

可以使用此委托构造一个能以参数形式传递的方法,而不用显式声明自定义的委托。该方法必须与此委托定义的方法签名相对应。也就是说,封装的方法必须具有四个均通过值传递给它的参数,并且必须返回值。

[MVC]如何使ASP.NET MVC Controller易测试呢?

mikel阅读(863)

昨天那篇文章如何进行ASP.NET MVC 的测试,虽然通过自己写一个Fake的HttpContext,但是同时也暴露出之所以难于测试,是因为设计的代码不易测试,根据Jeffrey Zhao(老赵)的建议(我没完全看懂如何去做),我又重新试了一下,我把对httpcontext的操作全部放到一个ModelBinder里,因为那里可以处理httpcontext,而测试的时候是不执行ModelBinder

下面是实验的代码

Product类

image

ProductBinder

image

HomeController

image

记得在global.asax里注册

image

测试代码

image

很明显,测试是通过的,因为此时Action没有用到任何httpcontext的内容,处理的只是传进去的Product对象。

现在我的问题如下:

1. 很明显About的Action是通不过测试的,这里我只是使用了一个session值,难道我也需要新建一个ModerBinder,和一个类吗?这样 凡是出现调用httpcontext都的新建一个Binder那Binder是不是太多了,ModelBinder是不是主要处理Form?

2. 如果不用ModelBinder怎么样即可以使用session又易测呢?

3. 虽然Action好测了,但ProductBinder如何测?

注:本文没有技术含量,为何放在首页?因为我没有搜索到相关的内容,而园子里很多人肯定有更好的方案,但是他们的时间可能很紧,我想他们能在评论里给点好的思路,文章+评论=一篇好文, 我这里就是想抛砖引玉,把他们的经验为大家引出来。

[C#]独立主机上实现二级别域名解释(泛解释) (asp.net)

mikel阅读(762)

1\那就是把 *.iloveyou.io解释到服务器默认网站

2\在项目Global的事件protected void Application_BeginRequest(Object sender, EventArgs e)中通过

   string _path = Request.Url.AbsoluteUri;

   读取当前路径,然后做处理就是了!

   把

   _path="http://liangsan.iloveyou.io/ "

   的路径变为

  newurl="love/?toName=liangsan"

然后通过

    HttpContext.Current.RewritePath(newurl);

 就搞定了!就这么简单!

[MVC]如何进行ASP.NET MVC 的测试

mikel阅读(805)

如何进行ASP.NET MVC 的测试

本文参考了http://stephenwalther.com/blog/的内容。

今天需要对ASP.NET MVC的Controller进行测试,我们都知道当我们在测试工程里new一个controller时,这个controller里的httpcontext是空的,也就是session,cookie, form等都是空。

方法一:Mock controller的HttpContext, 暂时失败

那么我们如何对controller进行测试呢,我首先想到的是mock一个httpcontext,这里我用的是Rhino Mocks

public static class MvcMockHelpers

    {

        public static HttpContextBase FakeHttpContext(this MockRepository mocks)

        {

            HttpContextBase context = mocks.PartialMock<HttpContextBase>();

            HttpRequestBase request = mocks.PartialMock<HttpRequestBase>();

            HttpResponseBase response = mocks.PartialMock<HttpResponseBase>();

            HttpSessionStateBase session = mocks.PartialMock<HttpSessionStateBase>();

            HttpServerUtilityBase server = mocks.PartialMock<HttpServerUtilityBase>();

 

            SetupResult.For(context.Request).Return(request);

            SetupResult.For(context.Response).Return(response);               

            SetupResult.For(context.Session).Return(session);

            SetupResult.For(context.Server).Return(server);

 

            mocks.Replay(context);

            return context;

        }

 

        public static HttpContextBase FakeHttpContext(this MockRepository mocks, string url)

        {

            HttpContextBase context = FakeHttpContext(mocks);

            context.Request.SetupRequestUrl(url);

            return context;

        }

 

        public static void SetFakeControllerContext(this MockRepository mocks, Controller controller)

        {

            var httpContext = mocks.FakeHttpContext();

            ControllerContext context = new ControllerContext(new RequestContext(httpContext, new RouteData()), controller);

            controller.ControllerContext = context;

        }

下面我们建立一个ASP.NET MVC工程

using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

using System.Web.Mvc;

using System.Web.Mvc.Ajax;

 

namespace MVCTest.Controllers

{

    [HandleError]

    public class HomeController : Controller

    {

        public ActionResult TestSession()

        {

            Session["name"] = "Jack Wang";

            ViewData["Title"] = "Home Page";

            ViewData["Message"] = "Welcome to ASP.NET MVC!";

            if (Session["CurrentCulture"] != null)

            {

                ViewData["CurrentCulture"] = Session["CurrentCulture"];

            }

            return View();

        }

 

        public ActionResult TestForm()

        {

            ViewData["Name"] = Request.Form["Name"];

            ViewData["Age"] = Request.Form["Age"];

            ViewData["count"] = Request.Form.Count;

            return View();

        }

 

        public ActionResult TestLogin()

        {

            if (User.Identity.IsAuthenticated)

            {

                ViewData["userName"] = User.Identity.Name;

                return View("Admin");

            }

            else

            {

                return RedirectToAction("Index");

            }

        }

 

        public ActionResult Admin()

        {

            if (User.IsInRole("Admin"))

            {

                return View("Admin");

            }

            else

            {

                return RedirectToAction("Index");

            }

        }

 

        public ViewResult Details()

        {

            ViewData["PageSize"] = Request.QueryString["PageSize"];

            ViewData["CurrentPage"] = Request.QueryString["CurrentPage"];

            ViewData["count"] = Request.QueryString.Count;

 

            return View();

        }

        public ViewResult TestCookie()

        {

            ViewData["key"] = Request.Cookies["key"].Value;

            return View();

        }

    }

}

 

测试代码

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Web.Mvc;

using Microsoft.VisualStudio.TestTools.UnitTesting;

using MVCTest;

using MVCTest.Controllers;

using MvcFakes;

using System.Web.SessionState;

using System.Web;

using System.Collections.Specialized;

using Rhino.Mocks;

namespace MVCTest.Tests.Controllers

{

    /// <summary>

    /// Summary description for HomeControllerTestByMock

    /// </summary>

    [TestClass]

    public class HomeControllerTestByMock

    {

        public HomeControllerTestByMock()

        {

            //

            // TODO: Add constructor logic here

            //

        }

        private MockRepository mocks;

        [TestInitialize]

        public void Setup()

        {

            mocks = new MockRepository();

        }

        [TestMethod]

        public void TestSession()

        {

            // Arrange

 

            HomeController controller = new HomeController();

            mocks.SetFakeControllerContext(controller);

            controller.Session.Add("name", "Jack Wang");

            SetupResult.For(controller.Session["CurrentCulture"]).Return("zh-CN");

 

            mocks.ReplayAll();

            // Act

            ViewResult result = controller.TestSession() as ViewResult;

 

            // Assert

            ViewDataDictionary viewData = result.ViewData;

            Assert.AreEqual("Home Page", viewData["Title"]);

            Assert.AreEqual("Welcome to ASP.NET MVC!", viewData["Message"]);

            Assert.AreEqual(controller.Session["name"], "Jack Wang");

            Assert.AreEqual("zh-CN", viewData["CurrentCulture"]);

        }

 

    }

}

运行,测试

image

从错误信息可以看到是因为代码里Session["name"] = "Jack Wang"出错

本人排查很久,只知道mock的controllercontext的Session里没有这个Key,但现在还没有找到如何解决,哪位高人能帮吗解决?

 

二,自己写个模拟的Fake类,测试通过

 

既然这种方法不行,我们只能换一种方法,还好controller的ControllerContext可以赋值,这样我们就通过继承ControllerContext来Fake

using System;

using System.Collections;

using System.Collections.Generic;

using System.Collections.Specialized;

using System.Linq;

using System.Text;

using System.Web;

using System.Web.SessionState;

 

namespace MvcFakes

{

    public class FakeHttpSessionState : HttpSessionStateBase

    {

        private readonly SessionStateItemCollection _sessionItems;

 

        public FakeHttpSessionState(SessionStateItemCollection sessionItems)

        {

            _sessionItems = sessionItems;

        }

 

        public override void Add(string name, object value)

        {

            _sessionItems[name] = value;

        }

 

        public override int Count

        {

            get

            {

                return _sessionItems.Count;

            }

        }

 

        public override IEnumerator GetEnumerator()

        {

            return _sessionItems.GetEnumerator();

        }

 

        public override NameObjectCollectionBase.KeysCollection Keys

        {

            get

            {

                return _sessionItems.Keys;

            }

        }

 

        public override object this[string name]

        {

            get

            {

                return _sessionItems[name];

            }

            set

            {

                _sessionItems[name] = value;

            }

        }

 

        public override object this[int index]

        {

            get

            {

                return _sessionItems[index];

            }

            set

            {

                _sessionItems[index] = value;

            }

        }

 

        public override void Remove(string name)

        {

            _sessionItems.Remove(name);

        }

    }

}

 

using System;

using System.Collections.Specialized;

using System.Web;

 

namespace MvcFakes

{

 

    public class FakeHttpRequest : HttpRequestBase

    {

        private readonly NameValueCollection _formParams;

        private readonly NameValueCollection _queryStringParams;

        private readonly HttpCookieCollection _cookies;

 

        public FakeHttpRequest(NameValueCollection formParams, NameValueCollection queryStringParams, HttpCookieCollection cookies)

        {

            _formParams = formParams;

            _queryStringParams = queryStringParams;

            _cookies = cookies;

        }

 

        public override NameValueCollection Form

        {

            get

            {

                return _formParams;

            }

        }

 

        public override NameValueCollection QueryString

        {

            get

            {

                return _queryStringParams;

            }

        }

 

        public override HttpCookieCollection Cookies

        {

            get

            {

                return _cookies;

            }

        }

 

    }

}

 

using System;

using System.Security.Principal;

 

namespace MvcFakes

{

 

 

    public class FakeIdentity : IIdentity

    {

        private readonly string _name;

 

        public FakeIdentity(string userName)

        {

            _name = userName;

 

        }

 

        public string AuthenticationType

        {

            get { throw new System.NotImplementedException(); }

        }

 

        public bool IsAuthenticated

        {

            get { return !String.IsNullOrEmpty(_name); }

        }

 

        public string Name

        {

            get { return _name; }

        }

 

    }

 

 

}

 

using System;

using System.Linq;

using System.Security.Principal;

 

namespace MvcFakes

{

 

    public class FakePrincipal : IPrincipal

    {

        private readonly IIdentity _identity;

        private readonly string[] _roles;

 

        public FakePrincipal(IIdentity identity, string[] roles)

        {

            _identity = identity;

            _roles = roles;

        }

 

        public IIdentity Identity

        {

            get { return _identity; }

        }

 

        public bool IsInRole(string role)

        {

            if (_roles == null)

                return false;

            return _roles.Contains(role);

        }

    }

}

 

using System;

using System.Collections.Specialized;

using System.Security.Principal;

using System.Web;

using System.Web.SessionState;

 

namespace MvcFakes

{

    public class FakeHttpContext : HttpContextBase

    {

        private readonly FakePrincipal _principal;

        private readonly NameValueCollection _formParams;

        private readonly NameValueCollection _queryStringParams;

        private readonly HttpCookieCollection _cookies;

        private readonly SessionStateItemCollection _sessionItems;

 

        public FakeHttpContext(FakePrincipal principal, NameValueCollection formParams, NameValueCollection queryStringParams, HttpCookieCollection cookies, SessionStateItemCollection sessionItems )

        {

            _principal = principal;

            _formParams = formParams;

            _queryStringParams = queryStringParams;

            _cookies = cookies;

            _sessionItems = sessionItems;

        }

 

        public override HttpRequestBase Request

        {

            get

            {

                return new FakeHttpRequest(_formParams, _queryStringParams, _cookies);

            }

        }

 

        public override IPrincipal User

        {

            get

            {

                return _principal;

            }

            set

            {

                throw new System.NotImplementedException();

            }

        }

 

        public override HttpSessionStateBase Session

        {

            get

            {

                return new FakeHttpSessionState(_sessionItems);

            }

        }

 

    }

}

 

using System;

using System.Collections.Specialized;

using System.Web;

using System.Web.Mvc;

using System.Web.Routing;

using System.Web.SessionState;

 

namespace MvcFakes

{

 

    public class FakeControllerContext : ControllerContext

    {

        public FakeControllerContext(ControllerBase controller)

            : this(controller, null, null, null, null, null, null)

        {

        }

 

        public FakeControllerContext(ControllerBase controller, HttpCookieCollection cookies)

            : this(controller, null, null, null, null, cookies, null)

        {

        }

 

        public FakeControllerContext(ControllerBase controller, SessionStateItemCollection sessionItems)

            : this(controller, null, null, null, null, null, sessionItems)

        {

        }

 

 

        public FakeControllerContext(ControllerBase controller, NameValueCollection formParams)

            : this(controller, null, null, formParams, null, null, null)

        {

        }

 

 

        public FakeControllerContext(ControllerBase controller, NameValueCollection formParams, NameValueCollection queryStringParams)

            : this(controller, null, null, formParams, queryStringParams, null, null)

        {

        }

 

 

 

        public FakeControllerContext(ControllerBase controller, string userName)

            : this(controller, userName, null, null, null, null, null)

        {

        }

 

 

        public FakeControllerContext(ControllerBase controller, string userName, string[] roles)

            : this(controller, userName, roles, null, null, null, null)

        {

        }

 

 

        public FakeControllerContext

            (

                ControllerBase controller,

                string userName,

                string[] roles,

                NameValueCollection formParams,

                NameValueCollection queryStringParams,

                HttpCookieCollection cookies,

                SessionStateItemCollection sessionItems

            )

            : base(new FakeHttpContext(new FakePrincipal(new FakeIdentity(userName), roles), formParams, queryStringParams, cookies, sessionItems),

            new RouteData(), controller)

        { }

    }

}

 

下面是测试类

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Web.Mvc;

using Microsoft.VisualStudio.TestTools.UnitTesting;

using MVCTest;

using MVCTest.Controllers;

using MvcFakes;

using System.Web.SessionState;

using System.Web;

using System.Collections.Specialized;

namespace MVCTest.Tests.Controllers

{

    /// <summary>

    /// Summary description for HomeControllerTest

    /// </summary>

    [TestClass]

    public class HomeControllerTest

    {

        [TestMethod]

        public void TestSession()

        {

            // Arrange

            HomeController controller = new HomeController();

            var sessionItems = new SessionStateItemCollection();

            sessionItems["CurrentCulture"] = "zh-CN";

            controller.ControllerContext = new FakeControllerContext(controller, sessionItems);

            // Act

            ViewResult result = controller.TestSession() as ViewResult;

 

            // Assert

            ViewDataDictionary viewData = result.ViewData;

            Assert.AreEqual("Home Page", viewData["Title"]);

            Assert.AreEqual("Welcome to ASP.NET MVC!", viewData["Message"]);

            Assert.AreEqual(sessionItems["name"], "Jack Wang");

            Assert.AreEqual("zh-CN", viewData["CurrentCulture"]);

        }

        [TestMethod]

        public void TestFakeFormParams()

        {

 

            var controller = new HomeController();

 

            var formParams = new NameValueCollection { { "Name", "Jack" }, { "Age", "28" } };

            controller.ControllerContext = new FakeControllerContext(controller, formParams);

 

            var result = controller.TestForm() as ViewResult;

            Assert.AreEqual("Jack", result.ViewData["Name"]);

            Assert.AreEqual("28", result.ViewData["Age"]);

            Assert.AreEqual(formParams.Count, result.ViewData["count"]);

        }

 

        [TestMethod]

        public void TestFakeQueryStringParams()

        {

            var controller = new HomeController();

 

            var queryStringParams = new NameValueCollection { { "PageSize", "10" }, { "CurrentPage", "5" } };

            controller.ControllerContext = new FakeControllerContext(controller, null, queryStringParams);

 

            var result = controller.Details() as ViewResult;

            Assert.AreEqual("10", result.ViewData["PageSize"]);

            Assert.AreEqual("5", result.ViewData["CurrentPage"]);

            Assert.AreEqual(queryStringParams.Count, result.ViewData["count"]);

        }

 

        [TestMethod]

        public void TestFakeUser()

        {

            var controller = new HomeController();

 

            controller.ControllerContext = new FakeControllerContext(controller, "Jack Wang");

            var result = controller.TestLogin() as ActionResult;

            Assert.IsInstanceOfType(result, typeof(ViewResult));

            ViewDataDictionary viewData = ((ViewResult)result).ViewData;

            Assert.AreEqual("Jack Wang", viewData["userName"]);

 

 

            controller.ControllerContext = new FakeControllerContext(controller);

            result = controller.TestLogin() as ActionResult;

            Assert.IsInstanceOfType(result, typeof(RedirectToRouteResult));

        }

 

 

        [TestMethod]

        public void TestFakeUserRoles()

        {

 

            var controller = new HomeController();

 

 

            controller.ControllerContext = new FakeControllerContext(controller, "Jack Wang", new string[] { "Admin" });

            var result = controller.Admin() as ActionResult;

            Assert.IsInstanceOfType(result, typeof(ViewResult));

 

 

            controller.ControllerContext = new FakeControllerContext(controller);

            result = controller.Admin() as ActionResult;

            Assert.IsInstanceOfType(result, typeof(RedirectToRouteResult));

        }

 

 

 

        [TestMethod]

        public void TestCookies()

        {

 

            var controller = new HomeController();

 

 

            var cookies = new HttpCookieCollection();

            cookies.Add(new HttpCookie("key", "a"));

            controller.ControllerContext = new FakeControllerContext(controller, cookies);

            var result = controller.TestCookie() as ViewResult;

 

 

            Assert.AreEqual("a", result.ViewData["key"]);

        }

    }

}

测试完全通过

image 

 

总结: 个人感觉ASP.NET MVC的测试是很不友好的,和ruby on rails相比,在测试这一块还有很大的距离,希望正式版里这一块能够加强

 

王德水 写于2008-01-05 00:30分

 

本文源码下载

[Javascript]javascript中setTimeout 和setInterval的区别

mikel阅读(688)

window对象有两个主要的定时方法,分别是setTimeout 和 setInteval 他们的语法基本上相同,但是完成的功能取有区别。
  setTimeout方法是定时程序,也就是在什么时间以后干什么。干完了就拉倒。
  setInterval方法则是表示间隔一定时间反复执行某操作。

  如果用setTimeout实现setInerval的功能,就需要在执行的程序中再定时调用自己才行(setTimeout( ) 預設只是執行一次, 但我們可以使用一個循環方式, 使到一個setTimeout( ) 再啟動自己一次, 就會使到第二個 setTimeout( ) 執行, 第二個又啟動第三個, 這樣循環下去, 這 setTimeout( ) 就會不斷執行)。如果要清除计数器需要根据使用的方法不同,调用不同的清除方法:

  1. var timeID = null;   
  2.   
  3. //timeout方法   
  4. timeID=setTimeout('northsnow()',1000);   
  5. clearTimeout(timeID);     
  6.   
  7. //interval方法   
  8. timeID=setInterval('northsnow()',1000);   
  9. clearInteval(timeID);  

 eg:

  1. <script type="text/JavaScript">   
  2. var count = 0;   
  3. var timeID = null;   
  4. function counter() {   
  5.     count++;       
  6.     $("#count").html(count + '%');   
  7.     timeID = setTimeout("counter()"1000);//注意:执行的函数需要加引号,否则会报错的
  8.     if (count > 10) {   
  9.         clearTimeout(timeID);   //注意:clearTimeout(timeID)必须位于setTimeout()之后,不能是之前,原因很容易呦!   
  10.     }   
  11. }   
  12. $(function() {   
  13.     counter();   
  14. });   
  15. </script>  

 

  1. <script type="text/JavaScript">   
  2. var count = 0;   
  3. var timeID = null;   
  4. function counter() {   
  5.     count++;       
  6.     $("#count").html(count + '%');   
  7.     if (count > 10) {   
  8.         clearInterval(timeID);   
  9.     }       
  10. }   
  11. $(function() {       
  12.     timeID = setInterval('counter()', 1000);  //注意:执行的函数需要加引号,否则会报错的 
  13.        
  14. });   
  15. </script>  

    好了,基本原理明白了,剩下的就是自己灵活运用了,可以开发一些定时器或延时器之类的东东^_^.

[Javascript]关闭浏览器的事件

mikel阅读(877)

在做图片刷新功能的时候,会出现IE图片缓存的问题,试了N多方法都没有效果,最后只有使用每刷新一次修改图片文件名加随机数的方法才勉强解决。但是这样 的方法是下次打开浏览器读取图片的时候,上次产生的随机数是动态的,这时候只有让用户关闭浏览器的时候捕捉关闭事件然后将图片更名为一个统一的名字。由于 浏览器是无状态的,在这时候捕捉浏览器关闭会出现两种情况:1.真正的关闭浏览器 2.刷新浏览器。如何判断区分这两种动作呢。
一. JavaScript代码处理方法:
      function window.onbeforeunload()  
      {        
        //用户点击浏览器右上角关闭按钮
        if(event.clientX>document.body.clientWidth&&event.clientY<0||event.altKey)  
        {   
                document.getElementById("btnCompelete").click();
//              window.event.returnValue="确定要退出本页吗?";  
        }
        //用户点击任务栏,右键关闭
        else if(event.clientY > document.body.clientHeight || event.altKey)
        {
            document.getElementById("btnCompelete").click();
//            window.event.returnValue="确定要退出本页吗?";  
        }
         else//其他情况为刷新   
         {   
              alert("你在刷新");
         }  
      } 
其中 event.clientX   鼠标光标X坐标     document.body.clientWidth窗体工作区宽度     event.clientY鼠标光标Y坐标     event.altKey   是否按下alt键
二. 事件捕捉方法:
<body scroll="no" onbeforeunload="return CloseEvent();" onunload="UnLoadEvent()" >
</body>

<script language="JavaScript" type="text/JavaScript">
 
  
    var DispClose = true;
    function CloseEvent()
    {
        if (DispClose)
        {
            return "是否离开当前页面?";
        }
    }
   
    function UnLoadEvent()
    {
        DispClose = false;
        //在这里处理关闭页面前的动作
    }
在页面卸载之前引发onbeforeunload事件,如果用户选择“是”即确定卸载页面将引发onunload事件,否则返回页面不做任何操作。

[C#]static变量与asp.net

mikel阅读(982)

 在C#中,static变量表示该变量属于类,而不是类的实例。可以说是该类的所有实例共享一个static变量。
ASP.NET的页面就是一个类,我们访问一个页面。就会在服务器上实例化一个该类的实例,来响应我们的请求。
“所有实例共享一个static变量” 这就意味着,所有的客户端访问到的ASP.NET页面中static变量都是同一个变量。

由于我们每次访问asp.net页面都是一个全新的对象,而不是我们上一次访问的对象。所以上次页面访问时我们对页面中变量的改动都没有保留。遇到 这个问题的时候,很多初学者的直觉就是将这个变量申明为static,自己在测试的时候发现还真的保留住了页面的状态。窃喜之余没有发现这又有引入了另外 一个错误。因为你要的只是页面能保留住状态,而这个状态是针对一个客户端的(session的效果)。而得到的结果是只要一个客户端改变了该值所有的其他 客户端都受到了影响(如同Applicatin的效果)。这种情况下,需要的极有可能就是个ViewState或者是Session。

Application与static变量
Application是通过一个集合保存所有的对象。

强类型:
Application中保存的是object,对对象的保存和使用需要作cast动作。对于值类型更需要Box&UnBox。对性能的影响较大。
而static变量是强类型的对象。

线程同步:
Application将所有的对象放到一个集合,这样对访问集合中的任何对象都会锁定这个集合。
假如有Application["A"]、Application["B"]、Application["C"],有线程访问Application["A"]其他线程不能访问Application["B"] and Application["C"]。
而static变量,可以根据他们的作用分别放在不同的class当中。这样可以并行访问不同的static变量,而不存在线程安全问题。

友情提示:
1. 对static变量,做lock时。可以通过lock(typeof(classname))来锁定该变量所在的类的类型,达到线程同步的目的。
2. 由于Aplication,static member是全局变量,而我们是在多线程服务器环境写程序,对他们的使用需要注意线程安全的问题