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); } |
总结
至此,一个小型的服务器软件就构建好了,赶紧测试一下呵呵。
标签:
ASP.NET本质论
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~