※天道酬勤※

§水至清则无鱼,人至察则无徒§
posts - 65, comments - 11, trackbacks - 0, articles - 0
  IT博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

实现自己的ASPNET宿主系统

Posted on 2006-07-28 13:41 五指魅力 阅读(172) 评论(0)  编辑 收藏 引用

托管是 .NET 的一个很基础的概念,所有的 .NET 应用程序代码要完全发挥作用需要进入托管的环境( CLR --Common Language Runtime ),而这个环境实际上就是称作宿主 (Host) 为将要启动的 .NET 代码准备的。目前来讲 windows 系统上,能够担负这个重任的有 3 类已存程序:
1
shell( 通常是 Explorer) ,提供从用户桌面启动 .NET 程序 , 创建一个进程,启动此进程建立 CLR
2
浏览器宿主( Internet Explorer ),处理从 web 下载的 .NET 代码执行。
3
服务器宿主(如 IIS 的辅助进程 ASPnet_wp.exe
通常来讲,我们开发的 ASP.NET 的程序运行在 IIS 的环境下(实际上由一个 ISAPI 控制启动 CLR ),但实际上 ASP.NET 程序可以摆脱 IIS 单独在任何托管环境下运行。本文讨论了 ASP.NET 程序如何在自定义的环境中启动,希望有助于我们了解 ASP.NET 的执行原理,同时使我们开发的 ASP.NET 能够在任何 .NET 环境下执行,不管是服务器操作系统还是普通的桌面操作系统。

二、 IIS 宿主中 ASP.NET 的执行分析
关于 IIS ASP.NET 的执行细节,很多文章做了详尽权威的分析,本文不打算赘述,在此给出一些参考:
http://www.yesky.com/SoftChannel/72342380468043776/20030924/1731387.shtml
http://chs.gotdotnet.com/quickstart/ASPplus/doc/procmodel.ASPx
这些文章大致重点分析了:宿主环将如何启动、 ASP.NET 应用程序如何产生程序集、如何加载,同宿主的交互等细节。

三、 构造自己的 ASP.NET 宿主程序
ASP.NET
是作为微软 ASP 的替代技术出现的,所以我们重点讨论如何通过 web 方式应用 ASP.NET (显然还有其他方式),具体就是:我们用 .NET 平台的语言编写一个控制台程序,这个程序启动一个 ASP.NET 应用环境,执行关于 ASPx 的请求。具体来讲,需要做以下工作:
1
、实现一个 Web Server ,监听所有的 web 请求,实现 Http web hosting
2
、启动一个应用程序域,创建一个 ASP.NET ApplicationHost ,建立一个 ASP.NET 的应用程序域,另外还建立一个 HttpWorkerRequest 的具体实现类,该类可以处理 ASPx 请求,编译 ASPx 页,编译后的托管代码缓存入当前应用程序域,然后执行代码,得到执行结果。建议在继续阅读下文前,仔细翻查 MSDN 中的关于这两个类得参考说明。
System.Web.Hosting.ApplicationHost
类用于建立一个独立的应用程序域。当然不是普通的应用程序域,而是为 ASP.NET 建立执行环境,准备需要的空间、数据结构等。仅有一个静态方法 static object CreateApplicationHost(
Type host //
具体的用户实现类,就是 ASP.NET 应用域需要加载的类
string virtualDir, //
此应用域在整个 web 中的执行目录,虚拟目录
string physicalDir //
对应的物理目录
);
而其中的 host 参数指向一个具体的类,由于该类实际上属于两个应用域之间的联系类,在两个应用程序域之间编组传递数据,所以必须要继承自 MarshalByRefObject ,以允许在支持应用程序中跨应用程序域边界访问(至于为什么,建议翻查参考 3 )。
可以看到,我们需要启动两个应用程序域( web server 功能应用程序域和 ASP.NET 应用程序域),而这两个(应用程序)域之间通过跨(应用程序)域的流对象引用来实现,使得在 ASP.NET 域中执行的结果可以通过 web server 域返回给请求者。
可以大致下图表达
执行 ASP.NET Web 服务器端

 

 

 

 

WEB 客户端

代码实现分析:
using System;
using System.Web ;
using System.Web.Hosting;
using System.IO;
using System.NET;
using System.NET.Sockets ;
using System.Text ;
using System.Threading ;

namespace MyIIS
{
class ASPHostServer
{
[STAThread]
static void Main(string[] args)
{
//
创建并启动服务器
MyServer myserver=new MyServer(“/”, ”c:\\inetpub\\wwwroot\\myWeb”);
}
}

class MyServer // 处理 HTTP 协议的服务器类
{
private ASPDOTNETHost ASPnetHost; //ASP.NET host
的实例
private TcpListener mytcp; //Web
监听套接字
bool bSvcRunning=true; //
服务是否运行指示
FileStream fs; //
处理 http 请求的普通文本要求

public MyServer(string virtualDir ,vstring realPath)
{//
在构造函数中启动 web 监听服务
try
{
mytcp=new TcpListener(8001);
mytcp.Start(); //
启动在 8001 端口的监听
Console.WriteLine("
服务启动 ...");
//
利用 CreateApplicationHost 方法建立一个独立的应用程序域执行 ASP.NET 程序
ASPnetHost = ( ASPDOTNETHost )ApplicationHost.CreateApplicationHost
( typeof( ASPDOTNETHost ) , virtualDir , realPath);
Thread t=new Thread(new ThreadStart(MainSvcThread));
t.Start(); //
服务线程启动 负责处理每一个客户端的请求
}
catch(NullReferenceException)
{
Console.WriteLine("NullReferenceException throwed!") ;
}
}

public void MainSvcThread() //ASP.NET Host web 服务器的主要服务线程
{
int s=0;
string strRequest; //
请求信息
string strDir; //
请求的目录
string strRequestFile; //
请求的文件名
string strErr=""; //
错误信息
string strRealDir; //
实际目录
string strWebRoot=rpath; //
应用根目录
string strRealFile=""; //
正在请求的文件的磁盘路径
string strResponse=""; //
回应响应缓冲区
string strMsg=""; //
格式化响应信息
byte[] bs; //
输出字节缓冲区

while(bSvcRunning)
{
Socket sck=mytcp.AcceptSocket(); //
每个请求到来
if(sck.Connected)
{
Console.WriteLine("Client {0} connected!",sck.RemoteEndPoint);
byte[] bRecv=new byte[1024]; //
缓冲区
int l=sck.Receive(bRecv,bRecv.Length,0);
string strBuf=Encoding.Default.GetString(bRecv); //
转换成字符串,便于分析
s=strBuf.IndexOf("HTTP",1);
string httpver=strBuf.Substring(s,8); // HTTP/1.1
之类的
strRequest=strBuf.Substring(0,s-1);
strRequest.Replace("\\","/");
if((strRequest.IndexOf(".")<1) && (!strRequest.EndsWith("/")))
{
strRequest += "/";
}
s=strRequest.LastIndexOf("/")+1;
strRequestFile = strRequest.Substring(s); strDir=strRequest.Substring(strRequest.IndexOf("/"),strRequest.LastIndexOf("/")-3); //
取得访问的 URL
if(strDir=="/")
{
strRealDir=strWebRoot;
}
else
{
strDir=strDir.Replace("/","\\");
strRealDir=strWebRoot + strDir;
}
Console.WriteLine("Client request dir: {0}" , strRealDir);
if(strRequestFile.Length==0)
{
strRequestFile="default.htm"; //
缺省文档
}
int iTotlaBytes=0; //
总计需要输出的字节
strResponse=""; //
输出内容
strRealFile = strRealDir +"\\"+ strRequestFile;
if(strRealFile.EndsWith(".ASPx")) //
这里有 Bug!!
{
string output="";
//
注意我下面的语句们给 host 对象 ProcessRequest 方法传递了一个 ref 类型的参数,
//ASPnetHost
会从 ASP.NET 的执行应用程序域执行一个请求后返回流给当前 web server 所在的域,这实际上发生了一个域间的调用
ASPnetHost.ProcessRequest (strRequestFile, ref output);//
转换成字节流
bs=System.Text.Encoding.Default.GetBytes (output);
iTotlaBytes=bs.Length ; //
调用套接字将执行结果返回
WriteHeader(httpver,"text/html",iTotlaBytes,"200 OK",ref sck);
FlushBuf(bs,ref sck);
}
else
{try
{
fs=new FileStream( strRealFile,FileMode.Open,FileAccess.Read,FileShare.Read );
BinaryReader reader=new BinaryReader(fs); //
读取
bs=new byte[fs.Length ];
int rb;
while((rb=reader.Read(bs,0,bs.Length ))!=0)
{
strResponse =strResponse +Encoding.Default.GetString(bs,0,rb);
iTotlaBytes =iTotlaBytes+rb;
}
reader.Close();
fs.Close();
WriteHeader(httpver,"text/html",iTotlaBytes,"200 OK",ref sck);
FlushBuf(bs,ref sck);
}
catch(System.IO.FileNotFoundException )
{//
假设找不到文件,报告 404 WriteHeader(httpver,"text/html",iTotlaBytes,"404 OK",ref sck);
}
}
}
sck.Close(); //Http
请求结束
}
}

// WriteHeader 想客户端发送 HTTP
public void WriteHeader(string ver,string mime,int len,string statucode,ref Socket sck) {
string buf="";

if(mime.Length ==0)
{
mime="text/html";

buf=buf+ver+ statucode + "\r\n";
buf=buf+"Server:MyIIS"+"\r\n";
buf=buf+"Content-Type:"+mime +"\r\n";
buf=buf+"Accept-Rabges:bytes"+"\r\n";
buf=buf+"Content-Length:"+ len +"\r\n\r\n";
byte[] bs=Encoding.Default.GetBytes(buf);
FlushBuf(bs,ref sck);
}
}

// FlushBuf 刷新向客户发送信息缓冲区
public void FlushBuf(byte[] bs,ref Socket sck)
{
int iNum=0;
try
{
if(sck.Connected)
{
if((iNum=sck.Send(bs,bs.Length ,0))==-1)
{
Console.WriteLine("Flush Err:Send Data err");
}
else
{
Console.WriteLine("Send bytes :{0}",iNum);
}
}
else
{
Console.WriteLine("Client diconnectioned!");
}
}
catch(Exception e)
{
Console.WriteLine("Error:{0}",e);
}
}
}

// ASPDOTNETHost 类实例需要跨越两个应用程序域,所以继承自 MarshalByRefObject
class ASPDOTNETHost:MarshalByRefObject
{
public void ProcessRequest( string fileName ,ref string output)
{
MemoryStream ms=new MemoryStream(); //
内存流,当然为了速度
StreamWriter sw = new StreamWriter(ms); //
输出
sw.AutoFlush = true; //
设为自动刷新 / 先构造一个 HttpWorkRequest 请求类,以便 ASP.NET 能够分析获取请求信息,同时传入一个输出流对象供 ASP.NET 执行期间返回 html
HttpWorkerRequest worker = new SimpleWorkerRequest( fileName, "" ,sw) ; //
调度某个页,这里面的包含很多细节,后面分析
HttpRuntime.ProcessRequest( worker ) ;
StreamReader sr= new StreamReader(ms); //
准备从内存流中读取
ms.Position =0; //
移动指针到头
output = sr.ReadToEnd();
}
}
}
HttpRuntime.ProcessRequest( worker ) ;
包括了那些细节呢?大体上如下:
1
、首先, worker 对象传入给 ASP.NET 的应用程序域,告知发生了对于哪一个 ASPx 文件的请求,以及当前目录是什么,如果在执行期间发生的输出内容应该写到哪里 (sw 对象 ) 。这发生一个由 web server 当前应用程序域到我们自己建立的 ASP.NET 应用程序域的跨(应用程序)域调用,还可能由于是第一次访问,会发生了全局事件、或者 session 事件等。
2
ASP.NET 的应用程序域会检测请求的 ASPx 文件是否存在,不存在,就报错;如果存在还要看看代码缓存中是否存在上次编译的代码,如果存在且 ASP.NET 检测到不需要重新编译,会直接执行缓存中的代码;如果不存在或者代码过期需要重新编译,就需要读取 ASPx 文件,编译成 .NET 的代码,存入缓存。可能有些页存在代码和模板分离成多个文件,甚至包括一些资源文件,这些都需要读取后编译成 .NET 的虚拟机代码,然后在托管环境里执行。
3
、执行 ASP.NET 的编译代码缓存中的代码,输出数据利用 sw 对象输出。
当然,根据不同的配置,还有很多方法的调用 / 事件的发生等细节不同。
如何调试运行以上程序,观察结果呢?
建立一个控制台类型工程,将上述代码录入后编译,将得到的程序拷贝在作为站点应用起始目录(譬如 c:\inetpub\wwwroot\myweb )的 bin 子目录下,然后启动,这样在其中创建 ASP.NET 应用程序域才不会因为程序集加载失败而出错。建立一个 asp.net 工程在目录下,添加 default.htm 文件和测试用的 test.aspx ,加入 .NET 执行代码,然后启动 IE ,在地址栏分别输入: http://127.0.0.1:8001/default.htm http://127.0.0.1:8001/test.aspx 感受一下执行过程。甚至你可以建立的工程中设定断点之类,仔细调试和观察其中的细节。亲手试一试吧,一定有收获的!

四、 自己构造 ASP.NET 宿主的意义
费了半天劲搞自己的 ASP.NET 宿主,对于我们有何意义呢?
首先,是大致从代码级清楚分析 ASP.NET 执行细节,自己学习了解执行细节,除了可以在出现 ASP.NET 故障可以进行精确定位和排除外,还可以帮助我们在写 ASP.NET 应用程序时写出更有效率和健壮的代码。
其次,我们可以提供一个思路,可以将我们的 ASP.NET 程序运行于低配置机器上,脱离 IIS ASP.NET 原配 宿主 IIS 需要运行在 Server OS 上,要知道在安全专家眼中, IIS 可是大隐患的源头之一。我们可以将很多传统程序利用 ASP.NET 编写,但脱离 IIS 独立执行,譬如在 win98 系统上执行 ASP.NET web server ASP.NET 都在托管环境中执行,相比较 ISAPI 建立宿主然后执行,除提高效率外,还可以使用 .NET 平台提供的丰富管理调控功能,写 B/S 程序更接近传统程序编写方式,这对于程序员来讲都是效率(编写代码的效率和执行效果效率)的保证。
另外,对于采用 ASP.NET 做的项目,大家可以很方便进行开发调试、运行维护、安装。即使是普通桌面程序,我们也可以通过类似制作网页的方式编写这些界面和代码,然后独立建立类似本例中的 Host 环境,根据用户交互请求加载执行某些页面,然后将界面在客户端通过相关组件显示出来。你可以通过此获得 ASP.NET 的即时编译功能和 ASP.NET 宿主托管环境,大量可自由使用的 API ,便于开发、安装、维护。毕竟,托管环境几乎准备了您需要的一切功能。
只有注册用户登录后才能发表评论。