ASP.NET那点不为人知的事(三)
ASP.NET那点不为人知的事(三)
有了以下的知识:
想必开发一个小型服务器以不是问题了,功能补复杂,能够响应客户端浏览器的请求,并根据请求文件的类型返回响应的信息,如能处理静态页面、图片、样式、脚本、动态页面等。
回顾
由于客户端和服务端的通信是通过Socket通信,且它们通信的“语言”是基于Http1.1协议。根据这个线索,我们完全可以自己开发服务器软件,暂且叫他Melodies Server,当然这是一个很简单的样例,和真正的服务器还是有差距的,好,我们进入正题,首先需要了解以下几个知识点:
- 客户端和服务端是由Socket进行通信,在服务器端需要有监听请求的套接字,他绑定在某个端口号上,如果发现有请求过来,socket.Accept()产生一个套接字和客户端进行通信。
- 客户端发送的请求(报文)交给服务器软件分析,判断是否为静态页面、图片还是动态aspx文件,若是静态文件能直接返回。
- 处理动态页面稍稍麻烦,需要反射创建页面类(原因详见ASP.NET那点不为人知的事(二))
开启服务
public partial class WebServerForm : Form
{
public WebServerForm()
{
InitializeComponent();
TextBox.CheckForIllegalCrossThreadCalls = false ;
}
private Socket socketWatch; //负责监听浏览器连接请求的套接字
private Thread threadWatch; //负责循环调用Socket.Accept 监听线程
private void btnStartServer_Click( object sender, EventArgs e)
{
socketWatch= new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
IPAddress address = IPAddress.Parse(txtIPAddress.Text.Trim());
IPEndPoint endPoint= new IPEndPoint(address, int .Parse(txtPort.Text.Trim()));
socketWatch.Bind(endPoint);
socketWatch.Listen(10);
threadWatch = new Thread(WatchConnect);
threadWatch.IsBackground = true ;
threadWatch.Start();
}
private bool isWatch = true ;
//Dictionary<>
void WatchConnect()
{
while (isWatch)
{
Socket socketConnection=socketWatch.Accept();
ShowMsg( "浏览器:" +socketConnection.RemoteEndPoint.ToString()+ ",连接成功*********************" );
ConnectionClient connectionClient = new ConnectionClient(socketConnection, ShowMsg);
}
}
void ShowMsg( string msg)
{
txtLog.AppendText(msg+ "\r\n" );
}
}
|
分析报文,处理请求
- 在异步线程创建的与客户端通信的Socket,它的主要职责就是分析保文:
/// <summary> /// 与客户端连接通信类(包含一个与客户端通信的套接字和通信线程) /// </summary> public class ConnectionClient
{ private Socket socketMsg; //与客户端通信套接字
private Thread threadMsg; //通信线程
private DGShowMsg dgShowMsg; //负责向主窗体文本框显示消息的委托
public ConnectionClient(Socket socket,DGShowMsg dgShowMsg)
{
this .socketMsg = socket;
this .dgShowMsg = dgShowMsg;
//负责启动一个接受客户端浏览器请求报文的线程
threadMsg = new Thread(ReceiveMsg);
threadMsg.IsBackground = true ;
threadMsg.Start();
}
private bool isRec = true ;
void ReceiveMsg()
{
while (isRec)
{
byte [] arrMsg= new byte [1024*1024*3];
//接受对应客户端发送过来的请求报文
int length = socketMsg.Receive(arrMsg);
string strMsg = System.Text.Encoding.UTF8.GetString(arrMsg, 0, length);
dgShowMsg(strMsg);
//处理报文
string [] arrStr = strMsg.Replace( "\r\n" , "韘" ).Split( '韘' );
string [] firstRow=arrStr[0].Split( ' ' );
string requestFile = firstRow[1];
ExcuteRequest(requestFile); //todo:长连接多少时间
}
}
private void ExcuteRequest( string requestFile)
{
//获得被请求页面的后缀名
string fileExtension = System.IO.Path.GetExtension(requestFile);
if (! string .IsNullOrEmpty(fileExtension))
{
switch (fileExtension.ToLower())
{
case ".html" :
case ".htm" :
case ".css" :
case ".js" :
ExcuteStaticPage(requestFile,fileExtension);
break ;
case ".jpg" :
ExcuteImg(requestFile,fileExtension);
break ;
case ".aspx" :
ExcuteDymPage(requestFile,fileExtension);
break ;
}
}
}
|
- 针对不同的请求执行不同的操作,其中静态页面、css、js、图片处理操作一样,都是属性静态文件,直接返回字节流:
/// <summary> /// 处理静态页面,直接输出
/// </summary>
private void ExcuteStaticPage( string requestPath, string fileExtension)
{
StringBuilder sb= new StringBuilder();
//获得请求文件的文件夹的物理路径
string dataDir = AppDomain.CurrentDomain.BaseDirectory;
if (dataDir.EndsWith( @"\bin\Debug\" ) || dataDir.EndsWith( @"\bin\Release\" ))
{
dataDir = System.IO.Directory.GetParent(dataDir).Parent.Parent.FullName;
}
string phyPath = dataDir + requestPath;
//读取静态页面内容
string fileContent = System.IO.File.ReadAllText(phyPath);
//获得响应体字节数组
byte [] fileArr = System.Text.Encoding.UTF8.GetBytes(fileContent);
//获得响应报文头:
string responseHeader = GetResponseHeader(fileArr.Length, fileExtension);
byte [] arrHead = System.Text.Encoding.UTF8.GetBytes(responseHeader);
//发送响应报文头回浏览器
socketMsg.Send(arrHead);
//发送响应报文体回浏览器
//todo:sleep 1分钟会怎样
socketMsg.Send(fileArr);
}
/// <summary>
/// 处理图片
/// </summary>
/// <param name="requestPath"></param>
/// <param name="extentionName"></param>
private void ExcuteImg( string requestPath, string extentionName)
{
//获得请求文件的文件夹的物理路径
string dataDir = AppDomain.CurrentDomain.BaseDirectory;
if (dataDir.EndsWith( @"\bin\Debug\" ) || dataDir.EndsWith( @"\bin\Release" ))
{
dataDir = System.IO.Directory.GetParent(dataDir).Parent.Parent.FullName;
}
//获得请求文件的物理路径(绝对路径)
string phyPath = dataDir + requestPath;
int imgLength;
byte [] fileArr;
//读取图片内容
using (FileStream fs = new FileStream(phyPath, FileMode.Open))
{
fileArr = new byte [fs.Length];
imgLength = fs.Read(fileArr, 0, fileArr.Length);
//获得响应报文头
string responseHeader = GetResponseHeader(imgLength, extentionName);
byte [] arrHeader = System.Text.Encoding.UTF8.GetBytes(responseHeader);
socketMsg.Send(arrHeader);
socketMsg.Send(fileArr, imgLength, SocketFlags.None);
}
}
/// <summary>
/// 得到响应头信息
/// </summary>
/// <param name="contentLength"></param>
/// <param name="fileExtentionName"></param>
/// <returns></returns>
private string GetResponseHeader( int contentLength, string fileExtentionName)
{
StringBuilder sbHeader= new StringBuilder();
sbHeader.Append( "HTTP/1.1 200 OK\r\n" );
sbHeader.Append( "Content-Length: " +contentLength+ "\r\n" );
sbHeader.Append( "Content-Type:" + GetResponseHeadContentType(fileExtentionName) + ";charset=utf-8\r\n\r\n" );
return sbHeader.ToString();
}
/// <summary>
/// 根据后缀名获取响应保文中的内容类型
/// </summary>
/// <param name="fileExtentionName"></param>
/// <returns></returns>
private string GetResponseHeadContentType( string fileExtentionName)
{
switch (fileExtentionName.ToLower())
{
case ".html" :
case ".htm" :
case ".aspx" :
return "text/html" ;
break ;
case ".css" :
return "text/plain" ;
break ;
case ".js" :
return "text/javascript" ;
break ;
case ".jpg" :
return "image/JPEG" ;
case ".gif" :
return "image/GIF" ;
break ;
default :
return "text/html" ;
break ;
}
}
|
- 同样,针对动态页面反射创建其页面类,注意记得让其实现IHttpHandler接口
- 创建一个页面类View
public class View:IHttpHandler
{
public string ProcessRequest()
{
string dataDir = AppDomain.CurrentDomain.BaseDirectory;
//获得模板物理路径
if (dataDir.EndsWith( @"\bin\Debug\" ) || dataDir.EndsWith( @"\bin\Release" ))
{
dataDir = System.IO.Directory.GetParent(dataDir).Parent.Parent.FullName;
}
string phyPath = dataDir + "/model.htm" ;
string modelContent=System.IO.File.ReadAllText(phyPath);
modelContent = modelContent.Replace( "@Title" , "动态页面" ).Replace( "@Content" , "反射创建页面类" );
return modelContent;
}
}
|
- 反射View,调用其ProcessRequest方法执行服务端代码
/// <summary> /// 反射创建动态页面对象
/// </summary>
/// <param name="requestFile"></param>
/// <param name="extentionName"></param>
private void ExcuteDymPage( string requestFile, string extentionName)
{
string pageClassName = System.IO.Path.GetFileNameWithoutExtension(requestFile);
string assemblyName = Assembly.GetExecutingAssembly().GetName().Name;
//获得页面类全名称
pageClassName = assemblyName + "." + pageClassName;
//通过反射创建页面类对象
object pageObj = Assembly.GetExecutingAssembly().CreateInstance(pageClassName);
IHttpHandler page = pageObj as IHttpHandler;
byte [] fileArr= null ;
if (page!= null )
{
string strHtml=page.ProcessRequest();
fileArr= System.Text.Encoding.UTF8.GetBytes(strHtml);
}
//获得响应报文头
string responseHeader = GetResponseHeader(fileArr.Length, extentionName);
byte [] arrHeader = System.Text.Encoding.UTF8.GetBytes(responseHeader);
socketMsg.Send(arrHeader);
socketMsg.Send(fileArr);
}
|
总结
至此,一个小型的服务器软件就构建好了,赶紧测试一下呵呵。
本博客为木宛城主原创,基于Creative Commons Attribution 2.5 China Mainland License发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名木宛城主(包含链接)。如您有任何疑问或者授权方面的协商,请给我留言。