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); }
总结
至此,一个小型的服务器软件就构建好了,赶紧测试一下呵呵。