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

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

  总结

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

本博客为 木宛城主原创,基于 Creative Commons Attribution 2.5 China Mainland License发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名 木宛城主(包含链接)。如您有任何疑问或者授权方面的协商,请给我留言。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值