日期:2013-12-24  浏览次数:20424 次

大概半年前曾写过一个在 WinForm 程序中嵌入 ASP.NET 的简单例子,《在WinForm程序中嵌入ASP.NET》。因为是试验性质的工作,所以当时偷懒直接使用系统自带的 SimpleWorkerRequest 完成 ASP.NET 页面请求的处理工作。使用自带工具类在实现上虽然简单,但受到系统的诸多功能限制,如后面有朋友提到无法直接处理多级子目录的问题等等。(如虚拟目录为 "/" 时无法处理 "/help/about.aspx" 类型的页面请求)
对于此类需求,一个最好的实现实例就是 www.asp.net 提供的 Cassini。这个例子完整地演示了如何实现一个支持 ASP.NET 的简单 Web 服务器功能,并被 Borland 的 Delphi.NET 等许多开源项目,当作调试用 Web 服务器。虽然只有几十 K 的源代码,但麻雀虽小五脏俱全,还是非常值得一看的。但因为 Cassini 是为处理 Web 服务而设计,因此需要在了解其结构的基础上,做一些定制来满足我们的需求。

首先来看看 Cassini 的程序结构。

与我前文例子中采用的结构类似,Cassini 包括界面(CassiniForm)、服务器(Server)、宿主(Host)和请求处理器(Request)等几个主要部分,并通过 Connection 等几个工具类,完成 Web 请求的解析与应答功能。

总体工作流程图如下:
以下内容为程序代码:

+-------+ [1] +-------------+ [2] +--------+
| Admin |---->| CassiniForm |---->| Server |
+-------+ +-------------+ +--------+
| [3]
V
+--------+ [4] +------+
| Client |---->| Host |
+--------+ +------+
^ | [5]
| V
| +------------+ [6] +---------+
[7]| | Connection |---->| Request |--+
| +------------+ +---------+ | [7]
+----------------------------------------+



[1] Cassini 的管理者(Admin)首先通过 CassiniForm 的界面,设定 Web 服务器端口、页面物理目录和虚拟目录等配置信息;
[2] 然后以配置信息构造 Server 对象,并调用 Server.Start 方法启动 Web 服务器;
以下内容为程序代码:

public class CassiniForm : Form
{
private void Start()
{
// ...
try {
_server = new Cassini.Server(portNumber, _virtRoot, _appPath);
_server.Start();
}
catch {
// 显示错误信息
}
// ...
}
}



[3] Server 对象在建立时,将获取或自动初始化 ASP.NET 的注册表配置。这个工作是通过 Server.GetInstallPathAndConfigureAspNetIfNeeded 方法完成的。工作原理是通过 HttpRuntime 所在 Assembly (System.Web.dll) 的版本获得合适的 ASP.NET 版本;然后从注册表中查询 HKEY_LOCAL_MACHINESOFTWAREMicrosoftASP.NET 下是否有正确的 ASP.NET 的安装路径;如果有则返回之;否则会根据 System.Web.dll 的版本,以及 HKEY_LOCAL_MACHINESOFTWAREMicrosoft.NETFramework 下 .NET Framework 按照目录等信息,动态构造一个合适的 ASP.NET 注册表配置。进行这个工作的原因是 ASP.NET 可以在按照 .NET Framework 后,使用 aspnet_regiis.exe 手工注销掉,而运行支持 ASP.NET 的 Web 服务器,又必须有合适的设置。
在完成配置和 ASP.NET 安装路径后,Server 将建立并配置 Host 对象作为 ASP.NET 的宿主。
以下内容为程序代码:

public class Server : MarshalByRefObject
{
private void CreateHost() {
_host = (Host)ApplicationHost.CreateApplicationHost(typeof(Host), _virtualPath, _physicalPath);
_host.Configure(this, _port, _virtualPath, _physicalPath, _installPath);
}

public void Start() {
if (_host != null)
_host.Start();
}
}



[4] Host 类作为 ASP.NET 的宿主类,主要完成三部分工作:配置 ASP.NET 的运行时环境、响应客户端(Client)发起的 Web 页面请求、以及判断客户端请求的有效性。
配置 ASP.NET 的运行时环境主要工作是,为 ASP.NET 的执行和后面请求有效性的判断获取足够的配置信息。例如 Server 能够提供的 Web 服务端口、页面虚拟路径、页面物理路径以及 ASP.NET 程序安装路径等等,以及 Host 根据这些信息计算出的 ASP.NET 客户端脚本的虚拟和物理路径等等。此外还会接管线程所在 AppDomain 的卸载事件 AppDomain.DomainUnload,在 Web 服务器停止的时候自动终止 Web 服务。
响应客户端(Client)发起的 Web 页面请求功能,是通过建立 Socket 监听 Server 对象指定的 Web 服务 TCP 端口来完成的。Host.Start 方法建立 Socket,并通过线程池异步调用 Host.OnStart 方法在后台监听请求;Host.OnStart 方法则在 接收到 Web 请求后,通过线程池异步调用 Host.OnSocketAccept 方法完成请求的响应工作;Host.OnSocketAccept 则负责在处理 Web 请求的时候,建立 Connection 对象,并进一步调用 Connection.ProcessOneRequest 方法处理 Web 请求。虽然 Host 没有使用复杂的请求分配算法,但因为线程池的灵活使用,使得其性能完全不受处理瓶颈的限制,也是线程池使用的良好范例。
以下内容为程序代码:

internal class Host : MarshalByRefObject
{
public void Start() {
if (_started)
throw new InvalidOperationException();

// 建立 Socket 监听 Web 服务端口
_socket = new Socket(AddressFamily.InterNetwork, Socke