[转载]T-SQL查询进阶—理解SQL Server中的锁

mikel阅读(1176)

[转载]T-SQL查询进阶—理解SQL Server中的锁 – CareySon – 博客园.

简介

SQL Server中,每一个查询都会找到最短路径实现自己的目标。如果数据库只接受一个连接一次只执行一个查询。那么查询当然是要多快好省的完成工作。但对于 大多数数据库来说是需要同时处理多个查询的。这些查询并不会像绅士那样排队等待执行,而是会找最短的路径执行。因此,就像十字路口需要一个红绿灯那 样,SQL Server也需要一个红绿灯来告诉查询:什么时候走,什么时候不可以走。这个红绿灯就是锁。

1

图1.查询可不会像绅士们那样按照次序进行排队

 

为什么需要锁

在开始谈锁之前,首先要简单了解一下事务和事务的ACID属性。可以参看我之前的一篇关于ACID的文章。 如果你了解了事务之间的影响方式,你就应该知道在数据库中,理论上所有的事务之间应该是完全隔离的。但是实际上,要实现完全隔离的成本实在是太高(必须是 序列化的隔离等级才能完全隔离,这个并发性有点….)。所以,SQL Server默认的Read Commited是一个比较不错的在隔离和并发之间取得平衡的选择。

SQL Server通过锁,就像十字路口的红绿灯那样,告诉所有并发的连接,在同一时刻上,那些资源可以读取,那些资源可以修改。前面说到,查询本身可不是什么 绅士,所以需要被监管。当一个事务需要访问的资源加了其所不兼容的锁,SQL Server会阻塞当前的事务来达成所谓的隔离性。直到其所请求资源上的锁被释放,如图2所示。

1

图2.SQL Server通过阻塞来实现并发

 

如何查看锁

了解SQL Server在某一时间点上的加锁情况无疑是学习锁和诊断数据库死锁和性能的有效手段。我们最常用的查看数据库锁的手段不外乎两种:

    使用sys.dm_tran_locks这个DMV

SQL Server提供了sys.dm_tran_locks这个DMV来查看当前数据库中的锁,前面的图2就是通过这个DMV来查看的.

这里值得注意的是sys.dm_tran_locks这个DMV看到的是在查询时间点的数据库锁的情况,并不包含任何历史锁的记录。可以理解为数据库在查 询时间点加锁情况的快照。sys.dm_tran_locks所包含的信息分为两类,以resource为开头的描述锁所在的资源的信息,另一类以 request开头的信息描述申请的锁本身的信息。如图3所示。更详细的说明可以查看MSDN(http://msdn.microsoft.com/en-us/library/ms190345.aspx)

2

图3.sys.dm_tran_locks

 

这个DMV包含的信息比较多,所以通常情况下,我们都会写一些语句来从这个DMV中提取我们所需要的信息。如图4所示。

4

图4.写语句来提取我们需要的锁信息

 

使用Profiler来捕捉锁信息

       我们可以通过Profiler来捕捉锁和死锁的相关信息,如图5所示。

5

图5.在Profiler中捕捉锁信息

 

但默认如果不过滤的话,Profiler所捕捉的锁信息包含SQL Server内部的锁,这对于我们查看锁信息非常不方便,所以往往需要筛选列,如图6所示。

6

图6.筛选掉数据库锁的信息

 

所捕捉到的信息如图7所示。

8

图7.Profiler所捕捉到的信息

 

锁的粒度

锁是加在数据库对象上的。而数据库对象是有粒度的,比如同样是1这个单位,1行,1页,1个B树,1张表所含的数据完全不是一个粒度的。因此,所谓锁的粒度,是锁所在资源的粒度。所在资源的信息也就是前面图3中以Resource开头的信息。

对于查询本身来说,并不关心锁的问题。就像你开车并不关心哪个路口该有红绿灯一样。锁的粒度和锁的类型都是由SQL Server进行控制的(当然你也可以使用锁提示,但不推荐)。锁会给数据库带来阻塞,因此越大粒度的锁造成更多的阻塞,但由于大粒度的锁需要更少的锁, 因此会提升性能。而小粒度的锁由于锁定更少资源,会减少阻塞,因此提高了并发,但同时大量的锁也会造成性能的下降。因此锁的粒度对于性能和并发的关系如图 8所示。

9

图8.锁粒度对于性能和并发的影响

 

SQL Server决定所加锁的粒度取决于很多因素。比如键的分布,请求行的数量,行密度,查询条件等。但具体判断条件是微软没有公布的秘密。开发人员不用担心SQL Server是如何决定使用哪个锁的。因为SQL Server已经做了最好的选择。

在SQL Server中,锁的粒度如表1所示。

 

资源 说明
RID 用于锁定堆中的单个行的行标识符。
KEY 索引中用于保护可序列化事务中的键范围的行锁。
PAGE 数据库中的 8 KB 页,例如数据页或索引页。
EXTENT 一组连续的八页,例如数据页或索引页。
HoBT 堆或 B 树。 用于保护没有聚集索引的表中的 B 树(索引)或堆数据页的锁。
TABLE 包括所有数据和索引的整个表。
FILE 数据库文件。
APPLICATION 应用程序专用的资源。
METADATA 元数据锁。
ALLOCATION_UNIT 分配单元。
DATABASE 整个数据库。

表1.SQL Server中锁的粒度

 

锁的升级

       前面说到锁的粒度和性能的关系。实际上,每个锁会占96字节的内存,如果有大量的小粒度锁,则会占据大量的内存。

下面我们来看一个例子,当我们选择几百行数据时(总共3W行),SQL Server会加对应行数的Key锁,如图9所示

10

图9.341行,则需要动用341个key锁

 

但当所取得的行的数目增大时,比如说6000(表中总共30000多条数据),此时如果用6000个键锁的话,则会占用大约96*6000=600K左右 的内存,所以为了平衡性能与并发之间的关系,SQL Server使用一个表锁来替代6000个key锁,这就是所谓的锁升级。如图10所示。

11

图10.使用一个表锁代替6000个键锁

 

虽然使用一个表锁代替了6000个键锁,但是会影响到并发,我们对不在上述查询中行做更新(id是50001,不在图10中查询的范围之内),发现会造成阻塞,如图11所示。

12

图11.表升级提升性能以减少并发为代价

 

 

锁模式

当SQL Server请求一个锁时,会选择一个影响锁的模式。锁的模式决定了锁对其他任何锁的兼容级别。如果一个查询发现请求资源上的锁和自己申请的锁兼容,那么 查询就可以执行下去,但如果不兼容,查询会被阻塞。直到所请求的资源上的锁被释放。从大类来看,SQL Server中的锁可以分为如下几类:

共享锁(S锁):用于读取资源所加的锁。拥有共享锁的资源不能被修改。共享锁默认情况下是读取了资源马上被释放。比如我读100条数据,可以想像成读完了 第一条,马上释放第一条,然后再给第二条数据上锁,再释放第二条,再给第三条上锁。以此类推直到第100条。这也是为什么我在图9和图10中的查询需要将 隔离等级设置为可重复读,只有设置了可重复读以上级别的隔离等级或是使用提示时,S锁才能持续到事务结束。实际上,在同一个资源上可以加无数把S锁

排他锁(X锁): 和其它任何锁都不兼容,包括其它排他锁。排它锁用于数据修改,当资源上加了排他锁时,其他请求读取或修改这个资源的事务都会被阻塞,知道排他锁被释放为止。

更新锁(U锁) :U锁可以看作是S锁和X锁的结合,用于更新数据,更新数据时首先需要找到被更新的数据,此时可以理解为被查找的数据上了S锁。当找到需要修改的数据时, 需要对被修改的资源上X锁。SQL Server通过U锁来避免死锁问题。因为S锁和S锁是兼容的,通过U锁和S锁兼容,来使得更新查找时并不影响数据查找,而U锁和U锁之间并不兼容,从而 减少了死锁可能性。这个概念如图12所示。

13

图12.如果没有U锁,则S锁和X锁修改数据很容易造成死锁

 

意向锁(IS,IU,IX):意向锁与其说是锁,倒不如说更像一个指示器。在SQL Server中,资源是有层次的,一个表中可以包含N个页,而一个页中可以包含N个行。当我们在某一个行中加了锁时。可以理解成包含这个行的页,和表的一 部分已经被锁定。当另一个查询需要锁定页或是表时,再一行行去看这个页和表中所包含的数据是否被锁定就有点太痛苦了。因此SQL Server锁定一个粒度比较低的资源时,会在其父资源上加上意向锁,告诉其他查询这个资源的某一部分已经上锁。比如,当我们更新一个表中的某一行时,其 所在的页和表都会获得意向排他锁,如图13所示。

14

图13.当更新一行时,其所在的页和表都会获得意向锁

 

其它类型的构架锁,键范围锁和大容量更新锁就不详细讨论了,参看MSDN(http://msdn.microsoft.com/zh-cn/library/ms175519.aspx

 

锁之间的兼容性微软提供了一张详细的表,如图14所示。

1

图14.锁的兼容性列表

 

理解死锁

当两个进程都持有一个或一组锁时,而另一个进程持有的锁和另一个进程视图获得的锁不兼容时。就会发生死锁。这个概念如图15所示。

15

图15.死锁的简单示意

 

下面我们根据图15的概念,来模拟一个死锁,如图16所示。

16

图16.模拟一个死锁

 

可以看到,出现死锁后,SQL Server并不会袖手旁观让这两个进程无限等待下去,而是选择一个更加容易Rollback的事务作为牺牲品,而另一个事务得以正常执行。

 

总结

本文简单介绍了SQL Server中锁的概念,原理,以及锁的粒度,模式,兼容性和死锁。透彻的理解锁的概念是数据库性能调优以及解决死锁的基础。

[转载]千万级数据的分页

mikel阅读(1020)

[转载]千万级数据的分页 – OK_008 – 博客园.

问题

想说这个问题,来源来自于CSDN论坛的一个帖子:《sqlserver2005,1690万的数据量怎么快速分页查询

 

在之前也有很多人问类似这样的问题,回复这样的问题,我们一般会从索引,水平分区,垂直分区和硬件的升级等方面考虑。

 

 分析

对于千万级数据的分页,要求在秒级内响应,解决方案除了刚列的几个方面考虑,这里说一个非常重要的考虑(评估)是,现实意义。

拿 CSDN论坛的那1690万数据来说,我按每页显示200行数据,需要84500页。从站在用户(使用者)角度看,对于查看1万页以后的数据的概率是非常 小。假设我们是使用者,让我们一页一页的点,点到100页都够郁闷的了,更何况是1万页后的数据了。这里从现实意义角度考虑,1万页以后的现实意义有多 大?

 

解决方案

根据从现实意义角度分析,对千万级数据的分页,我们呈现给用户的,首先考虑的是用户最关心的,对用户来最有价值的信息。对于过期,没意义的数据需要考虑不呈现给用户。因为呈现没意义的数据,对用户来说多余,对后台服务器来说负荷,严重的可能会导致服务器瘫痪。

在大型网站都有这方面的考虑,控制呈现的总页数,下面我们列出几个网站:

 

 

搜狗最多显示100页

 

 

 百度最多显示76页,其实你点到76页,显示第75页,感觉这位置有问题,而且相关结果就是1亿个。这里有玩文字的,使用了一个“约”,云里雾里的,蒙人呀。

很多时候你稍注意下,都是返回1亿个,O(∩_∩)O~。

 

测试Google,大概分页最大页范围是71-79页,它好一点就是没像百度笼统用“1亿”。

 

 淘宝的宝贝搜索最大页是200页

 

小结

从前边的问题到分析,及解决方案,我们可以了解对千万级数据分页的处理,最重要的一个评估方面就是,现实意义,需要多站在使用者角度分析问题,参考成功案例,从而找到可行的解决方案。

 

[转载]ASP.NET MVC测试方法与技巧

mikel阅读(966)

[转载]ASP.NET MVC测试方法与技巧_IT新闻_博客园.

作者 Roopesh Shenoy 译者 曹如进

一个 ASP.NET MVC 3 Web 应用程序有几个部分组成——模型(model)、控制器(controller)、路由处理器(route-handler)、视图(view)、 html-helper、客户端代码等。其中大部分模块都可以进行单元测试,有一些则需要集成测试。关于如何让测试更易维护以及如何避免它们过于脆弱,有 一些最佳实践能够帮得到你。

以下是一些测试相关的资源介绍:

  • 不仅是 actions,你还可以使用 MvcScaffolding 生成单元测试的基架模板 (scaffolding) ;
  • 使用自定义 HTML Helpers,而不要在视图中编写大量逻辑,因为前者更容易进行单元测试。另外,测试自定义 html-helper 时应避免硬编码 html 元素
  • 使用类似 Specflow 的 BDD 框架编写端到端的验收测试;
  • 尽管在路由表之外添加新的路由很容易破坏已有路由,但你可以通过对路由表进行单元测试来避免该问题;
  • 打开视图编译开关。 这虽然并不完全是单元测试,但它可以帮助发现运行时才能看到的错误;
  • 如果 UI 变化不大,可以使用 WaTiN 来测试 UI。另外,可以通过使用页面对象(Page Object)调整 UI 设计改动,来让集成测试变得稍微灵活些;
  • 使用一款 JS 测试框架,如使用 Jasmine测试 JavaScript

您还知道什么其他的测试 ASP.NET MVC 应用程序的技巧吗?请留言支招。

查看英文原文:http://www.infoq.com/news/2012/03/aspnet-unit-test

[转载]WebBrowser一点心得,如果在Javascript和Winform代码之间实现双向通信

mikel阅读(1023)

[转载]WebBrowser一点心得,如果在Javascript和Winform代码之间实现双向通信 – BobLiu – 博客园.

最近工作需要,学习了一下winform内嵌webbrowser控件,然后与htm页面中的JavaScript交互调用的技术,因此有了这篇心得。

总的来说,JavaScript与winform的code互相调用,和web开发中JavaScript与服务器端代码通过ajax互相调用有类似之处。

下面就用三个例子来说明:

 

一.将WebBrowser控件放置在winform中,然后,写一个Page1.htm,内容如下:

<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.0 Transitional//EN”>
<html>
<head>
<title></title>
<script type=”text/javascript”>
function test(message)
{
alert(message);
}
</script>
</head>
<body>
<button onclick=”test(‘test called from script code’)”>Button</button>
</body>
</html>

我将此Page1.htm显示在WebBrowser中看看,这个不难写,在winform中加上下面一句即可。
webBrowser1.Url= new Uri(“C:\\workspace\\WindowsFormsApp\\WindowsFormsApp\\Page1.htm”);

然后运行,在winform中的webbrowser显示出来这个htm了,点按钮调用javascript函数,弹出alert提示,一切都很正常,没什么稀奇。

 

二.如果我把javascript中的函数挪到winform的cs代码里,htm页面还能调用的到吗?
这有点ajax的味道了,在客户端的javascript里如何调用webpage.aspx.cs里的代码,在ajaxpro那时候,是需要在webpage.aspx.cs的代码里注册一下本页供ajax使用,在函数前也要声明一下是ajax函数的。

再说回来,如果想调用winform中的代码,也类似的,要给winform设置一下ComVisibleAttribute(true), 并给webbrowser控件设置一下webBrowser1.ObjectForScripting属性。
webBrowser1.Url= new Uri(“C:\\workspace\\WindowsFormsApp\\WindowsFormsApp\\Page1.htm”);
webBrowser1.ObjectForScripting = this;

其实,如果做的好,可以把这些代码专门归入一个类中,方便管理,这里就变为:webBrowser1.ObjectForScripting = new 某类()了;

 

然后,再在winform里写一个函数。
public void Test(String message)
{
MessageBox.Show(message, “client code”);
}

最后,htm里调用时要用window.external前缀一下Test方法名。
<button onclick=”window.external.Test(‘test called from windows code’)”>Button</button>

 

然后再运行,就发现,htm里的onclick事件,居然能调用winform里的code了,真是神奇!

 

完整winform代码如下:
using System;
using System.Windows.Forms;
using System.Security.Permissions;

namespace WindowsFormsApp
{
[PermissionSet(SecurityAction.Demand, Name = “FullTrust”)]
[System.Runtime.InteropServices.ComVisibleAttribute(true)]
public partial class Form2 : Form
{
private WebBrowser webBrowser1 = new WebBrowser();

public Form2()
{
InitializeComponent();

button1.Text = “call script code from client code”;
button1.Dock = DockStyle.Top;
button1.Click += new EventHandler(button1_Click);
webBrowser1.Dock = DockStyle.Fill;
Controls.Add(webBrowser1);
Load += new EventHandler(Form2_Load);

}

private void Form2_Load(object sender, EventArgs e)
{
webBrowser1.AllowWebBrowserDrop = false;
webBrowser1.IsWebBrowserContextMenuEnabled = false;
webBrowser1.WebBrowserShortcutsEnabled = false;
webBrowser1.ObjectForScripting = this;
webBrowser1.Url= new Uri(“C:\\workspace\\WindowsFormsApp\\WindowsFormsApp\\Page1.htm”);
}

public void Test(String message)
{
MessageBox.Show(message, “client code”);
}
}
}

总结一下,关键的webBrowser1.ObjectForScripting属性,ComVisibleAttribute(true)和window.external。
msdn说webBrowser1.ObjectForScripting属性的作用是:获取或设置一个对象,该对象可由显示在 WebBrowser 控件中的网页所包含的脚本代码访问。使用该属性可以启用 WebBrowser 控件承载的网页与包含 WebBrowser 控件的应用程序之间的通信。使用该属性可以将动态 HTML (DHTML) 代码与客户端应用程序代码集成在一起。为该属性指定的对象可作为 window.external 对象(用于主机访问的内置 DOM 对象)用于网页脚本。

 

可以将此属性设置为希望其公共属性和方法可用于脚本代码的任何 COM 可见的对象。可以通过使用 ComVisibleAttribute 对类进行标记使其成为 COM 可见的类。
这 一步也至关重要,如果不设置ComVisibleAttribute(true),那这个程序就不能加载显示htm页面,因为htm里用了 window.external.Test()方法,该方法所在的类如果不ComVisible,就无法访问到了。反过来,如果设置了 ComVisible,却不设置webBrowser1.ObjectForScripting属性,那代码执行时会报 错:window.external无效或找不到对象。

 

而缺少了window.external,就更甭提了,因此,这三者缺一不可。

 

再看看这个window.external,在常见的javascript书中不见踪影,但却非常有用,一个常见的应用是:
<input type=”button” name=”Button” value=”add” onclick=”window.external.AddFavorite(location.href,document.title)” />

 

引用别人的”在嵌入了浏览器的工程中,除了IE默认提供的外部方法之外,需要网页的脚本中能调用c++代码,要实现这种交互,就必须实现脚本扩展。 实现脚本扩展就是在程序中实现一个IDispatch接口,通过CHtmlView类的OnGetExternal虚函数返回此接口指针,这样就可以在脚 本中通过window.external.XXX(关键字window可以省略)来引用接口暴露的方法或属性(XXX为方法或属性名)。”

 

再看看在C#中的脚本扩展,只需要webBrowser1.ObjectForScripting和ComVisibleAttribute(true)简单一设置就完事了,简单吧!幸福吧!悲催吧!
三.再来看一个,从winform的code里,能调用html页面里的javascript吗?
Page1.htm,删掉button,只保留javascript脚本。
<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.0 Transitional//EN”>
<html>
<head>
<title></title>
<script type=”text/javascript”>
function test(message)
{
alert(message);
}
</script>
</head>
<body>

</body>
</html>
using System;
using System.Windows.Forms;
using System.Security.Permissions;

namespace WindowsFormsApp
{
[PermissionSet(SecurityAction.Demand, Name = “FullTrust”)]
[System.Runtime.InteropServices.ComVisibleAttribute(true)]
public partial class Form2 : Form
{
private WebBrowser webBrowser1 = new WebBrowser();
private Button button1 = new Button();

public Form2()
{
InitializeComponent();

button1.Text = “call script code from client code”;
button1.Dock = DockStyle.Top;
button1.Click += new EventHandler(button1_Click);
webBrowser1.Dock = DockStyle.Fill;
Controls.Add(webBrowser1);
Controls.Add(button1);
Load += new EventHandler(Form2_Load);

}

private void Form2_Load(object sender, EventArgs e)
{
webBrowser1.AllowWebBrowserDrop = false;
webBrowser1.IsWebBrowserContextMenuEnabled = false;
webBrowser1.WebBrowserShortcutsEnabled = false;
webBrowser1.ObjectForScripting = this;
webBrowser1.Url= new Uri(“C:\\workspace\\WindowsFormsApp\\WindowsFormsApp\\Page1.htm”);
}

private void button1_Click(object sender, EventArgs e)
{
webBrowser1.Document.InvokeScript(“test”,
new String[] { “called from client code” });
}

}
}

这回关键的因素就是webBrowser1.Document.InvokeScript了,而webBrowser1.ObjectForScripting,ComVisible不再需要了。

HtmlDocument.InvokeScript 方法的作用是:执行在 HTML 页面中定义的动态脚本函数。

至此,javascript与winform的code就可以互相调用了,感觉和web开发也有些类似。

 

这项技术叫在javascript(DHTML)代码和客户端应用程序代码之间实现双向通信.

[转载]ASP.NET Web API: 宿主(Hosting)

mikel阅读(1153)

[转载]ASP.NET Web API: 宿主(Hosting) – 张善友 – 博客园.

ASP.NET Web API 处理架构中介绍了ASP.NET Web API主 要有三层组成:宿主(hosting),消息处理管道(message handler pipeline)和控制器处理(controller handling),本篇文章主要介绍宿主(Hosting):包括ASP.NET经典管道上的Web Hosting和WCF堆栈的自宿主SelfHosting。

ASP.NET经典管道上的Web Hosting

1、ASP.NET 路由使您可以使用不必映射到网站中特定文件的 URL。 由于该 URL 不必映射到文件,因此可以使用对用户操作进行描述因而更易于被用户理解的 URL,路由在ASP.NET Web API上一样有重要的位置。在ASP.NET平台上,是通过RouteTable的静态属性Routes添加路由到路由表里,例如下面的代码是 ASP.NET MVC项目模板默认定义的路由:

protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
}

public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute(“{resource}.axd/{*pathInfo}”);

routes.MapRoute(
“Default”, // Route name
“{controller}/{action}/{id}”, // URL with parameters
new { controller = “Home”, action = “Index”, id = UrlParameter.Optional }
);
}

大部分的路由逻辑是由UrlRoutingModule附加到ASP.NET 的管道事件PostResolveRequestCache,对每一个Http请求,这个模块在路由集合中匹配符合路由规则,如果有一个匹配,则:

  • 从Route Data中获取到一个route handler
  • 从Route Handler中获取到一个实现IHttpHandler接口的Http处理程序
  • 最后,当前的请求上下文映射到上述Http处理程序

因此,在ASP.NET 管道的最后将请求发送这个处理器。

2、集成Web API,当宿主与ASP.NET,Web API的配置定义在单件的HttpConfiguration对象里,通过静态属性GlobalConfiguration.Configuration访问。Web API还定义了几个RouteCollection 扩展方法,叫做 MapHttpRoute,用来定义Web APi的配置。请看下面的示例:

HttpConfiguration config = GlobalConfiguration.Configuration;
config.Routes.MapHttpRoute(“default”, “{controller}/{id}”,  new {id = UrlParameter.Optional});
// other configuration settings

上述代码主要完成2项功能:

通过MapHttpRoute扩展方法添加到路由集合的Route Handler是HttpControllerRouteHandler,匹配使用MapHttpRoute添加的请求,相关的Route Handler返回一个新的处理器类型 HttpControllerHandler,它实现了异步IAsyncHttpHandler  接口,这个处理器使用路由数据初始化,包含匹配的信息。

当调用HttpControllerHandlerBeginProcessRequest方法,执行下面的动作:

HttpServer获取请求之后,它就进入了宿主处理新阶段(Web API的新管道)。下面的图总结了路由处理过程和分发到HttpServer实例(信息处理的管道)。

aspnethosting

基于WCF堆栈的自宿主SelfHosting

上面我们介绍完了在ASP.NET上的Web宿主,接下来我们来介绍基于WCF堆栈的自宿主SelfHosting。我们先来看一段使用自宿主的代码:

var config = new HttpSelfHostConfiguration(“http://localhost:8080″);
config.Routes.MapHttpRoute(“default”, “{controller}/{id}”,
new { id = RouteParameter.Optional });
var server = new HttpSelfHostServer(config);
server.OpenAsync().Wait();
Console.WriteLine(“Server is opened”);

HttpSelfHostServer 继承与HttpServer,并使用HttpSelfHostConfiguration 作为配置类,类图结构如下:

selfhosting

HttpSelfHostServer 内部使用的WCF堆栈从传输媒体获取消息然后把他们推送到上层的消息处理管道。下面部分简要介绍WCF的高层架构,Web API自宿主的特征。

WCF架构

WCF架构主要划分为2层,Channel Stack和Service Model,具体看下图:

wcfarchitecture

较低的通道堆栈层是由一堆通道和行为类似于经典的网络协议栈。通道分为两类:传输渠道和协议的渠道。负责传输通道由接口与传输介质(例如 TCP,MSMQ,HTTP)(是的,我知道,HTTP不仅仅是一个传输协议),即由讯息的接收与传递。协议的信息渠道流量过程上下通过叠加。一个典型的 使用案例的增加协议通道数字签名在发送方和验证签名的人在接收的一面。传输通道,使用编码器转换字节流和信息输送介质字节实例。

上层的Service Model执行消息和方法调用,所处理的人物如下:

  • 把收到的消息转换成参数序列
  • 获取到使用的服务实例
  • 选择调用的方法
  • 选择线程处理调用的方法

然而HttpSelfHostServer不使用Service Model层,相反,他直接消费了从传输通道堆栈获取的消息。传输通道堆栈层使用Binding来组织,如下图所示:

wcfbindingelementschannels_thumb

Binding是一个有序的Binding Element的集合,每一个元素描述一个通道或者编码器。第一个Binding Element描述了上层通道,最后一个Binding Element描述了底层的通道,总之这是一个传输通道。

HttpSelfHostServer 和HttpSelfHostConfiguration 类

在内部HttpSelfHostserver.OpenAsync 方法基于HttpSelfHostConfiguration实例属性创建和配置创建一个HttpBinding实例。然后他利用这个Binding异步创建一个WCF传输堆栈,他也创建一个Pump把消息推入这个堆栈,并转换为HttpRequestMessage 实例并把新的请求推入HttpServer,下图是消息的处理流程:

selfhostingdiagram

使用自宿主的时候,大多数的WCF HTTP binding 约束和设置都是可用的,在配置的内部创建的HttpBinding实例可以用2种方式创建。第一种是使用HttpSelfHostConfiguration属性,例如MaxBufferSizeTransferMode,这些被用于内部创建HttpBinding实例,第二种方法是创建一个HttpSelfHostConfiguration的子类,然后重写OnConfigureBinding方法,这个方法中有机会在创建通道堆栈之前修改绑定配置。

selfhostingdetail

[转载]C#程序员请不要混淆引用类型和ref引用传参

mikel阅读(1056)

转载C#程序员请不要混淆引用类型和ref引用传参 – slmk – 博客园.

先看一段代码1:

static void Main(string[] args)
{
StringBuilder sb = new StringBuilder("1");
test(sb);
Console.WriteLine(sb.ToString());
Console.Read();
}

static void test(StringBuilder sb)
{
sb = new StringBuilder("2");
}

错误的观念:

程序输出”2″,因为StringBuilder是引用类型,函数内部sb变量重新指向了托管堆中的新对象,函数返回后,外部的sb变量也指向了这个新对象,因为是引用类型吗,我传的是引用。

在错误的观念中,认为 “ref引用传参” 仅对.net中的另一种基本类型–值类型有用。

这种错误观念通常源于这样的经验,看代码2:

static void Main(string[] args)
{
StringBuilder sb = new StringBuilder("1");
test(sb);
Console.WriteLine(sb.ToString());
Console.Read();
}

static void test(StringBuilder sb)
{
sb.Append("2");
}

我们通常会很自豪的说:“看吧,程序输出12,这就是引用类型的特点,如果换成值类型就不是了!”。我们得出的结论并没有错,实际上,这正是引用类型的特点!然而将这个观念扩大到代码1的情况,就错了!
正确的理解:

程序正确的输出是“1”,并没有因为StringBuilder是引用类型,就应该输出“2” 。如果要输出“2”,需要加ref:

static void Main(string[] args)
{
StringBuilder sb = new StringBuilder("1");
test(ref sb);
Console.WriteLine(sb.ToString());
Console.Read();
}

static void test(ref StringBuilder sb)
{
sb = new StringBuilder("2");
}

对于ref传参,只要记住一点:对于值类型来说传的是值的地址,对于引用类型来说传的是地址的地址。
对于引用类型,同样记住一点:引用类型本身的地址是一个值类型。就像我们学习c时,指针本身的地址就是一个int。

好吧,用c来理解C#果然有点拗! 直观的理解:引用类型对象本身不改变,只改变对象的属性时,我们在操作同一个对象;如果连对象本身都可能会改变,就用ref传引用类型的对象吧!
为什么不用strng作测试呢?

因为string对象虽然是引用类型,但不能改变对象的属性CharArray,每次返回的都是新对象(看起来像值类型,其实不是!)。

[转载]ASP.NET Web API 处理架构

mikel阅读(1078)

[转载]ASP.NET Web API 处理架构 – 张善友 – 博客园.

这篇文章主要是介绍ASP.NET Web API的处理架构:当一个HTTP请求到达直到产生一个请求的过程。ASP.NET Web API 的处理架构图如下,主要有三层组成:宿主(hosting),消息处理管道(message handler pipeline)和控制器处理(controller handling).

processing-architecture

宿主(Hosting)

底层负责Web API的宿主,Web API之间的接口和HTTP 处理引擎。一句话,这一层负责创建HttpRequestMessage实例。然后把他们推入到上层的消息处理管道。宿主层也负责消息处理管道返回的HttpResponseMessage 。目前在ASP.NET Web API里头已经内建的宿主选项有2个:self-hosting 和 web hosting, web hosting也就是宿主在IIS的ASP.net 的处理管道里,Self-hosting 是基于WCF channel stack,的 WCF Message 实例  ,然后转换到 HttpRequestMessage 实例然后把他们推给上层的消息处理管道。 Web-hosting 是基于IHttpAsyncHandler, 命名为 HttpControllerHandler, 它把 HttpRequest 转换为HttpRequestMessage.当然Web API hosting 是可扩展的,不仅仅局限于这两个选项,你可以根据自己的需求定制,社区已经有人实现第三方的宿主Louis DeJardinOWIN created a host

消息处理管道(Message Handler Pipeline)

中间层是 message handler pipeline,这一部分就是 WCF Web API 的内容了,通过 HttpServer 类暴露, 他也扩展了 HttpMessageHandler 。这条管道提供了中间层的各种扩展点(addressing cross-cutting concerns)例如: 日志, HTTP 验证, ……

通常在这个管道的顶端是一个特殊的处理器: HttpControllerDispatcher。这个处理器负责获取和调用 一个  控制器(Controler) 处理请求。只是在使用基于控制器的编程模型(ApiController的派生类)的时候才使用HttpControllerDispatcher ,也可以使用完全不同的模型,只需要把最顶端的这个消息处理器替换掉就可以哦。

控制器处理(Controller Handling)

最后, 上层的控制器处理相关的流程,即:

这些处理过程都在 ApiController 实例里头完成, 被 HttpControllerDispatcher所调用。

上面的整个处理流程还是非常清晰地,本文只是简单的介绍下整个处理流程,后续的文章详细介绍各个部分。

[原创]EasyUI的Tab组件添加与关闭

mikel阅读(1523)

项目中需要根据不同的属性设置EasyUI的Tab组件的某页的动态关闭和显示,tab标签的代码如下:

<div id='tt' class="easyui-tabs" cache="false">
 <div title='基本信息'></div>
 <div title='联系方式'></div>
 <div id='product' title='产品'></div>
</div>

需要根据用户的类型来显示和隐藏产品tab页,开始以为直接根据状态直接$(‘#product’).hide();就可以了,可是试过后发现不行,于是查看了easyUI的tab组件的文档,发现有close方法可以关闭tab页,于是改写成:

 if(data.state=='1') $('#tt').tabs('close','产品');

可是,如果data.state==2则需要打开tabs可是没有open方法,tab已经被关闭了,没办法只好创建一个了,代码如下:

        if ($('#tt').tabs('exists', title)) {
            //$('#tt').tabs('select', title);
            reloadTab(title,url);
        } else {
            $('#tt').tabs('add', {
                title: title,
                href:url
            });
        }

[转载]必须要知道的session

mikel阅读(1361)

[转载]必须要知道的session – for certain – 博客园.

     越深入的了解ASP.NET,就会非常的感叹它的设计,功能强大,任何阶段的开发人员,不管是菜鸟、大牛,都能找到自己的用武之地:灵活,即可以利用本身 提供程序通过配置加简单代码方便的完成需求,也可以充分利用它的扩展性完成一些特定或本身不具备的功能。我们可以从session的设计中看到一些这样的 例子。

 

session设计简述

      准确的来说是会话状态,它是服务状态管理的一部分,每个访问应用程序的客户端有不同的会话且包含不同的信息,我们可以通过属性session来访问它。与 viewstate存储方式不同的是,它把信息存储在服务器,当用户访问多时,可能会给服务器带来不小的压力。

 

      既然session的数据存储在服务器,这就涉及到服务端要识别客户端的问题。ASP.NET使用一个标识来跟踪会话,我们可以session实例的SessionID的属性来获取这个标识,你可以通过配置来决定这个标识是通过cookie或url的方式传递,这个标识是一个私有的算法来生成的一个120位的值,它能保证唯一且随机,保证各个客户端不会出现相同的标识且不会被其它人通过其它方式生成。服务器端获取到这个ID,就会查找相应的会话,如果能找到相应的数据,就会把它填充到session中,供我们使用。另外还有一点要注意的是,如果你还没有用session存储信息,那么每次新请求,ASP.NET就会生成一个新的ID。

 

      那么这整个的流程,又是asp.net的那个模块来负责的呢?我们知道每个http请求都会经过多个模块的处理管道,其中有一个叫做SessionStateModule的模块,它负责产生会话ID,从session提供程序中获取数据,并绑定到请求的上下文中。当请求处理完毕时,它还负责保存会话信息到session提供程序中。

 

     这个session提供程序是什么?对,就是我们配置中sessionState mode中所指出的InProc,StateServer,SQLServer,我们可以根据需求很简单通过配置来实现会话信息存储在当前应用程序进程内、独立的服务或数据库中。但其实这个提供程序要求是一个实现了IHttpSessionState的任意类,以上的三种只不过asp.net内置的三种经常会用到的提供程序而已,如果我们有特殊需求时,我们完全可以自己定义提供程序。还有另一个极端,不使用会话状态。mode中还有另外两个值:Custom,Off。

 

压缩和安全

      我们前文中说到的session存储到服务器端的,如果大量用户访问时,这对服务器的内存是一个不小的压力;还有如果使用的其它计算机存储会话信息,这其 中涉及到数据的网络传输。这些情况下,通过设置enableCompression=true启用压缩来减小数据的大小,会是一个较好的选择。但是要注意 的,虽然省了内存或网络传输流量和时间,但是数据的压缩和解压缩都是需要耗费额外的cpu时间的。究竟如何取舍,这就要看实际的情况了。

 

      session把信息存储到服务器端,整个过程中唯一发送到客户端的只有一个session的ID,唯一的突破点就在于黑客入侵用户的电脑,并且复制用户 cookie在另一个电脑使用,从而达到以一个用户身份来访问应用程序,即使这种情况,就算你把用户私密如信用卡的账号、密码存储到session中,只 要你只是用它来进行和用户输入的进行校验,而不是2的把它输出的页面或客户端,黑客是没办法获取到这些session信息的。但是这始终是一个隐患,用户 的一些信息可能会泄露,这样我们可以使用自定义会话模块来检测cookie和用户IP或其它客户端特定信息来检测是否同一个用户的请求,更有效的方法启用 SSL,这样cookie被加密只能在此计算机使用,并且启用 Request.Cookies[“Assp.NET_SessionId”].Secure=true,这样cookie只能通过SSL即URL的 Https://传递

 

      另外对于使用无cookie的会话,尽管这个ID是加密过的,但是还是可以通过一个含有有效会话ID的Url来加入这个会话,对于此方法启用SSL也无法 杜绝其它用户,但是我们可以通过启用regenerateExpiredSessionId=true,这样可以让过期的ID失效,减少攻击的机率。

 

结尾

     本文没有对session的细节使用做太多的说明,只是希望能从session的设计中学习一些设计的思路,可以用于自己应用的开发当中,然后对 session实际的应用使用何种提供程序有个取舍,其它一些细节都会迎刃而解,最后使用任何中机制都不要忘了安全性。

 

[转载]Android上解析Json格式数据(一)

mikel阅读(1108)

[转载]Android上解析Json格式数据(一) – 淡如雪 – 博客园.

最近发现在做解析数据时,用到Json这种格式去解析数据挺方便,也得到不少人的青睐,下面是我个人总结的一些Json用法以及用列

/*
 * Json 一.Json主要是以键值对的形式存储数据的 ,Json描述很简单 {"name":"zhenhua","age":"23"}
 * 表示一个Json对象 [{"name":"zhenhua","age":"23"}]中括号表示Json对象的数组
 * [{"name":"zhenhua","age":"23"},{"name":"zhenhua","age":"23"}] 1.开始解析数组
 * 2.开始解析对象 3.开始解析键值对2 4.开始解析键值对 5.解析对象结束 6.开始解析对象 7.开始解析键值对 8.开始解析键值对 9.解析对象结束
 * 10.解析数组结束 二:如何把Json数组转换成java对象 String
 * date=[{"name":"zhenhua","age":"23"},{"name":"zhenhua","age":"23"}]
 */

 

public class Json extends Activity {
 String TAG = "Json message";

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  // TODO Auto-generated method stub
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
  detectJSON();
 }

 private void detectJSON() {
  String str = "{"
    + "\"日期\" : \"2011-06-06\","
    +
    // Like 是 JSONObject
    "\"Like\" : {" + "\"Name\" : \"加内特\","
    + "\"Height\" : \"2.11cm\"," + "\"Age\" : 35"
    + "},"
    +
    // LikeList 就是一个 JSONObject
    "\"LikeList\":" + "{\"List\": "
    + "["
    +
    // 这里也是JSONObject
    "{" + "\"Name\" : \"Rose\"," + "\"Height\" : \"190cm\","
    + "\"Age\" : 23" + "},"
    +
    // 这里也是JSONObject
    "{" + "\"Name\" : \"科比\"," + "\"Height\" : \"198cm\","
    + "\"Age\" : 33" + "}" + "]" + "}" + "}";
  try {

   JSONObject dataJson = new JSONObject(str);
   Log.d(TAG, dataJson.getString("日期"));

   JSONObject nbaJson = dataJson.getJSONObject("Like");
   Log.d(TAG, nbaJson.getString("Name"));
   Log.d(TAG, nbaJson.getString("Height"));
   Log.d(TAG, nbaJson.get("Age").toString());

   JSONObject listJson = dataJson.getJSONObject("LikeList");
   JSONArray arrayJson = listJson.getJSONArray("List");
   for (int i = 0; i < arrayJson.length(); i++) {
    JSONObject tempJson = arrayJson.optJSONObject(i);
    Log.d(TAG, tempJson.getString("Name"));
    Log.d(TAG, tempJson.getString("Height"));
    Log.d(TAG, tempJson.getString("Age").toString());
   }
  } catch (JSONException e) {
   System.out.println("Something wrong...");
   e.printStackTrace();

  }

 }
}