学习笔记之自己动手写WEB服务器

来源:互联网 发布:js如何转换日期格式 编辑:程序博客网 时间:2024/05/18 16:57

欢迎关注本人公众号


浏览器请求服务器过程:

通过浏览器来访问网站时,其实就相当于你通过浏览器去访问一台电脑上的文件一样,只不过浏览器的访问请求是由被访问的电脑上的一个WEB服务器软件来接收处理,它会分析接收到的请求信息,从而按照请求信息来找到服务器电脑上的文件,经过处理,最终将生成的内容发回到浏览器。

浏览器:一个可以通过HTTP协议向服务器发送各种请求,并对从服务器发出的超文本信息和各种多媒体数据格式进行解释、显示和播放的程序
服务器:一个管理资源并为用户提供服务的计算机软件

浏览器和服务器软件,其实就是两个使用Socket进行通信的的两个应用程序:双方都发送按照Http协议语法规范组织的数据,接收到数据后都按照Http协议语法规范来解释。

浏览器只负责解释执行html+css+javascript代码
服务器可执行服务器端语言:.NET,JAVA,...分别同由不同的运行环境执行代码(Framework,JVM)等

HTTP协议:一个基于应用层的通信规范,现在主流的是Http/1.1版本
  连接(Connection):浏览器和服务器之间传输数据的通道。一般请求完毕就关闭,HTTP不保持连接,不保持连接会降低处理速度(因为建立连接速度很慢),保持连接的话会就会降低服务器处理的客户端请求数,而不保持连接,服务器可以处理更多的请求
  请求(Request):浏览器向服务器发送的"我要***"之类的消息,包含请求的类型、数据,浏览器的信息(语言、版本等)
  响应(Response):服务器对浏览器请求返回的数据,包含是否成功、状态码等

HTTP协议请求报文
  GET/HTTP/1.1 表示向服务器用GET方式请求首页,使用HTTP/1.1协议
  Accept-Encoding gzip, daflate表示浏览器支持gzip、deflate两种压缩算法
  Accept-Language zh-cn 表示浏览器支持的语言,很多进入后自动就是中文界面的国际网站就是通过读取这个值实现的
  Connection Keep-Alive 一般情况下,一旦Web服务器向浏览器发送了请求数据,它就要旁门关闭TCP连接,如果浏览器在其头信息中加入了Connection Keep-Alive,则TCP连接在发送后将仍然保持打开这个状态,于是浏览器可以继续通过相同的连接发送请求,保持连节省了为每个请求建立新连接所需的时间,还节约了网络带宽。
  Cookie 是浏览器向服务器发送和当前网站关联的Cookie,这样在服务器端也能读取浏览器端的Cookie了
  User-Agnet 是浏览器的版本信息,通过这个信息可以读取浏览器是IE还是FireFox、支持的插件、.NET版本等

HTTP协议-响应码
  浏览器向服务器发出请求,服务器处理可能是成功、可能是失败,可能没有权限访问等原因,服务器会通过响应码来告诉浏览器处理结果
  "200" OK
  "302" Found重定向
  "400" Bad Request错误请求,发出错误的不符合Http协议的请求
  "403" Forbidden禁止
  "404" NOT Found 未找到
  "500" Internal Server Error 服务器内部错
  "503" Service Unavaliable 一般是访问人数过多
  200段是成功,300段需要对请求做进一步处理,400段表示客户端请求错误,500段是服务器的错误

HTTP协议-响应报文
  Server:Cassinin/3.5.0.5 表示服务器的类型
  Content-Type:text/html; charset=utf-8 表示返回数据的类型
    服务器通过Content-Type告诉客户端响就的数据的类型,这样浏览器就根本返回数据的类型来时行不同的处理
    常用Conten-Type:text/HTML、image/GIF、image/JPEG、text/plain、text/javascript、application/x-excel、application/octet-stream(二进制文件)
  Conntent-Length:19944 表示响应报文体的字节长度,报文头只是个描述

HTTP协议-其它
  http是无状态的,不会记得"上个请求***",所以哪怕是同一个页面中的js、css、jpg也都要重复的提交Accept-Language、Accept-Encoding、Cookie等
    网页中如果有图片、css、js等外部文件的话图片、css、js都在单独的请求中,也就是并不是页面的所有内容都在一个请求中完成,而是每个资源一个请求
    一般情况下,只有浏览器请求服务器端,服务器端才有给浏览器响应数据,不会主动向浏览器推送数据,这样是安全考虑,也是提高服务器的性能考虑,如果要服务器推送数据,则需要使用ServerPush(ajax隔一段时间到服务器请求最新的数据)等额外的技术
  HTTP是“请求-响应”的工作方式,因此页面会不断刷新,如果不希望页面刷新则要使用AJAX等新技术
  断点续传的原理,多线程下载基于断点续传。

服务器编写基本步骤:
  1.监听浏览器连接
  2.接收浏览器发送的请求报文数据
  3.分析报文数据
  4.根本请求文件类型读取服务器响应资源
  5.生成响应报文 
  6.向浏览器发送响应报文

  7.断开连接




Web服务器代码代码片断:

1.监听浏览器连接
  Socket socketWatch;
  Thread thAccept; 
  socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);  // 创建监听Socket
  IPAddress ip = IPAddress.Parse(txtIP.Text.Trim());
  IPEndPoint endpoint = new IPEndPoint(ip,int.Parse(txtPort.Text));
  socketWatch.Bind(endpoint);
  socketWatch.Listen(10);
  ShowMessage("服务器已启动……");

2.接受浏览器发送的连接请求并接收请求报文 
  //创建接受浏览器连接请求线程
  thAccept = new Thread(Accept);
  thAccept.IsBackground = true;
  thAccept.Start();

  void Accept()  //循环接受浏览器连接请求
        {
            while (true)
            {
                Socket socketConn = socketWatch.Accept();  //接受浏览器连接请求并创建连接Socket与浏览器通信
                ShowMessage("用户" + socketConn.RemoteEndPoint + "连接成功");
      // 调用数据连接类,并传入连接Socket及向文本框显示信息方法  
                DataConnection conn = new DataConnection(socketConn, ShowMessage);
            }
        }
  //创建数据连接类,接收浏览器请求报文并发送响应报文   
   public class DataConnection
    {
        public delegate void DGShowMessage(string msg);  //用来调用向文本框显示信息方法的委托
        Socket socketConn;
        Thread thReceive;
        DGShowMessage dgShow;
        public DataConnection(Socket socketConn, DGShowMessage dgShow)
        {
            this.socketConn = socketConn;
            this.dgShow = dgShow;

      //创建接受浏览器请求报文线程    
            thReceive = new Thread(Receive);
            thReceive.IsBackground = true;
            thReceive.Start();
        }

        void Receive()   //循环接受浏览器请求报文
        {
            while (true)
            {
                byte[] arrMsg = new byte[1024 * 1024 * 2];
                int len = socketConn.Receive(arrMsg);
                string strMsg = Encoding.Default.GetString(arrMsg, 0, len);  // 转换为二进制数组
                dgShow(strMsg);

3.分析报文数据
      // 调用请求报文实体
                HttpRequestModel requestModel = new HttpRequestModel(strMsg);

4.根本请求文件类型读取服务器响应资源
      // 调用判断请求类型方法,并根据类型进行处理
                JugeRequestType(requestModel);
            }
        }
    //判断请求类型方法
        void JugeRequestType(HttpRequestModel requestModel)
        {
            string dataDir = AppDomain.CurrentDomain.BaseDirectory;   // 获取当前程序的基目录
            dataDir = Directory.GetParent(dataDir).Parent.Parent.FullName;   // 获取当前项目的目录,即HTML网页的目录
            string filePath = dataDir + requestModel.path;   // 获取所访问的文件的路径
            string extentionName = Path.GetExtension(filePath);  // 获取文件扩展名,以便用来判断文件类型
            switch (extentionName)
            {
                case ".html":
                case ".css":
                case ".js":
                case ".jpg":
                case ".gif":
                case ".png":
                    {
                        ProcessStaticPage(filePath);   // 调用处理静态网页的方法
                        break;
                    }
                case ".aspx":
                case ".jsp":
                case ".php":
                    {
                        ProcessDynamicPage(filePath);  // // 调用处理动态网页的方法
                        break;
                    }
            }

        }
        void ProcessStaticPage(string filePath)  // 处理静态网页的方法
        {
            byte[] arrFile;
            using (FileStream fs = new FileStream(filePath, FileMode.Open))
            {
                arrFile = new byte[fs.Length];
                fs.Read(arrFile, 0, arrFile.Length);
            }

5.生成响应报文 
    // 调用响应报文实体
            HttpResponseModel responseModel = new HttpResponseModel(arrFile, Path.GetExtension(filePath));

6.向浏览器发送响应报文
            socketConn.Send(responseModel.ResponseHeader());   // 发送响应报文头 
            socketConn.Send(responseModel.arrFile);  // 发送响应报文体 
        }

        void ProcessDynamicPage(string filePath)   // 处理动态网页的方法
        {
            byte[] arrFile;
            string className = Path.GetFileNameWithoutExtension(filePath);   // 根据要访问文件名获取其类名
            string asName = Assembly.GetEntryAssembly().GetName().Name;  // 获取当前运行程序集的名称
            object objPage = Assembly.GetEntryAssembly().CreateInstance(asName + "." + className);  // 所要调用的类的全名
            IHttpHandler iPage = objPage as IHttpHandler; // 将创建页面类转成统一的接口对象 , 以便动态的调用不同的类           
            string strHtml = iPage.GetHtml();   // 获取HTML字符串
            arrFile = Encoding.Default.GetBytes(strHtml);
    // 调用响应报文实体
            HttpResponseModel responseModel = new HttpResponseModel(arrFile, Path.GetExtension(filePath));
    // 向浏览器发送请求报文 
            socketConn.Send(responseModel.ResponseHeader());
            socketConn.Send(responseModel.arrFile);
        }
    }

//请求报文实体类
 public class HttpRequestModel
    {   
        public string path;
        public HttpRequestModel(string strRequest)
        {
            string[] arrRequest = strRequest.Replace("/r/n", "★").Split('★'); // 换行分隔请求报文头
            string[] firstRow = arrRequest[0].Split(' ');  // 获取请求报文头中的请求文件路径
            path = firstRow[1];
        }
    }

//响应报文实体类
public class HttpResponseModel
    {
        public byte[] arrFile;
        public string contentType;
        public HttpResponseModel(byte[] arrFile, string fileExtention)
        {
            this.arrFile = arrFile;   // 响应报文体
            switch (fileExtention)   //根据不同的文件扩展名生成不同的响应报文类型
            {
                case ".aspx":
                case ".html": contentType = "text/html";
                    break;
                case ".css": contentType = "text/css";
                    break;
                case ".jpg": contentType = "image/jpeg";
                    break;
                case ".gif": contentType = "image/gif";
                    break;
                case ".png": contentType = "image/png";
                    break;
                case ".js": contentType = "application/javascript";
                    break;               

            }
        }
        public byte[] ResponseHeader()
        {
            StringBuilder header = new StringBuilder("HTTP/1.1 200 OK\r\nContent-Type: " + contentType + ";charset=utf-8\r\n");
            header.Append("Content-Length:" + arrFile.Length + "\r\n\r\n");
            byte[] arrHeader = Encoding.Default.GetBytes(header.ToString());
            return arrHeader;  //返回响应报文头
        }
    }


0 0
原创粉丝点击