ASP.NET那点不为人知的事(三)

简介:

有了以下的知识:

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);
 
 
       }

  总结

至此,一个小型的服务器软件就构建好了,赶紧测试一下呵呵。

88x31.png
本博客为 木宛城主原创,基于 Creative Commons Attribution 2.5 China Mainland License发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名 木宛城主(包含链接)。如您有任何疑问或者授权方面的协商,请给我留言。

本文转自木宛城主博客园博客,原文链接:http://www.cnblogs.com/OceanEyes/archive/2013/03/23/aspnet_melodies_server.html,如需转载请自行联系原作者
目录
相关文章
|
SQL 开发框架 .NET
ASP.NET学生常犯错误
自己在学习.NET中常犯的错误(持续更新
59 0
|
Web App开发 API C#
ASP.NET资源大全-知识分享
API 框架 NancyFx:轻量、用于构建 HTTP 基础服务的非正式(low-ceremony)框架,基于.Net 及 Mono 平台。官网 ASP.NET WebAPI:快捷创建 HTTP 服务的框架,可以广泛用于多种不同的客户端,包括浏览器和移动设备。
2816 0
|
Java .NET 开发框架