zoukankan      html  css  js  c++  java
  • 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);
    
    
            }
    

      总结

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

  • 相关阅读:
    忘记线上MySQL密码:
    Auth认证
    swoole定时
    hashMap,hashTable,concurrentHashmap的区别
    JSP中URL路径获取问题
    #Spring代理的简单例子#
    #动态代理#
    #类加载机制#
    #算法#二分查找和插入(start end交叉的地方)
    #tomcat#生成的jsp转换问题
  • 原文地址:https://www.cnblogs.com/OceanEyes/p/aspnet_melodies_server.html
Copyright © 2011-2022 走看看