基于C# Socket的Web服务器---静态资源处理

  Web服务器是Web资源的宿主,它需要处理用户端浏览器的请求,并指定对应的Web资源返回给用户,这些资源不仅包括HTML文件,JS脚本,JPG图片等,还包括由软件生成的动态内容。为了满足上述需求,一个完整的Web服务器工作流程:

1)   服务器获得浏览器通过TCP/IP连接向服务器发送的http请求数据包。

2)   HTTP请求经过Web服务器的HTTP解析引擎分析得出请求方法、资源地址等信息,然后开始处理。

3)   对于静态请求,则在服务器上查询请求url路径下文件,并返回(如果未找到则返回404 No Found)。

4)   涉及动态请求,如CGI, AJAX, ASP等,则根据http方法,采取不同处理。对于POST方法,则执行相应的服务器API函数。

  

  Web服务器的核心由C# Socket通讯,http解析引擎,静态资源文件查找,动态数据接收和发送4部分组成,本节因为个人编写进度原因主要实现前3个部分(即能够查询静态资源的Web服务器),动态数据处理因为涉及的处理方式CGI,AJAX,ASP的方法不同,后续完成后在总结相关知识。

1. C# Socket通讯 

  C# Socket通过对TCP/IP协议进行封装,用于实现满足TCP通讯的API。在B/S架构中,服务器端的处理和C/S连接基本相同,主要工作包含:创建Socket套接字,监听连接,建立连接,获得请求,处理并返回数据,关闭连接等。

  程序入口函数,采用轮询方式实现对客户端请求的监听。

            //创建监听线程
            Thread Listen_thread = new Thread(socket_listen);
            Listen_thread.IsBackground = false;
            Listen_thread.Start();        

  监听线程,创建Socket套接字,绑定并监听指定端口,等待连接建立,连接建立后,考虑到网页请求高并发的特性,采用另开线程的方式来处理建立的连接,从而实现并发服务器模式。

            Socket server_socket = null;

            //监听的IP地址和端口 作为服务器,绑定的只能是本机Ip地址或者环回地址(不能与系统其它进程端口冲突)
            //如果绑定为本节IP地址,局域网下其它设备可以通过http://host:port来访问当前服务器
            string host = "127.0.0.1";
            int port = 3000;

            IPAddress ip = IPAddress.Parse(host);
            IPEndPoint ipe = new IPEndPoint(ip, port);

            //新建Socket套接字,绑定在指定的端口并开始监听
            server_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            server_socket.Bind(ipe);
            server_socket.Listen(100);
            Console.WriteLine("Server Binding at " + host + ":" + port.ToString() +"...");
            Console.WriteLine("Wait for connect....");

            while (true)
            {
                Socket CurrentSocket;

                //三次握手成功,新建一个连接
                CurrentSocket = server_socket.Accept();

                Console.WriteLine("New TCP Socket Create...");

                //单开一个线程用来处理服务器收发, 并发服务器模式
                ParameterizedThreadStart tStart = new ParameterizedThreadStart(socket_process);
                Thread process_thread = new Thread(tStart);
                process_thread.IsBackground = false;
                process_thread.Start(CurrentSocket);
            }

  连接处理,socket通讯处理主要负责接收连接产生的数据,并将http引擎处理后数据提交给客户端浏览器。

            Socket CurrentSocket = (Socket)obj;
            try
            {
                string recvStr = "";
                byte[] recvBytes = new byte[2000];
                int length;

                //获得当前Socket连接传输的数据,并转换为ASCII码格式
                length = CurrentSocket.Receive(recvBytes, recvBytes.Length, 0);
                recvStr = Encoding.ASCII.GetString(recvBytes, 0, length);

                //http引擎处理,返回获得数据
                byte[] bs =  http_engine(recvStr, length);

                //通过socket发送引擎处理后数据
                CurrentSocket.Send(bs, bs.Length, 0);
                Console.WriteLine("File Send Finish, Socket Close....\r\n");

                //关闭socket连接
                CurrentSocket.Close();
            }
            catch (Exception exception)
            {
                Console.WriteLine(exception);
                CurrentSocket.Close();
            }

  C# Socket通讯架构的实现和C/S结构没有什么区别,如果了解过Socket可以轻松实现上述socket通讯架构。 不过下面这部分将讲述Web服务器的实现核心--http解析引擎,这也是B/S架构和C/S架构中服务器端最大的区别。

2. http解析引擎

  Web服务器主要实现对浏览器请求数据包的处理,并返回指定的http资源或者数据,这些都是由http解析引擎实现,在写http解析引擎之前,我们要知晓接收到的数据才能进行后续的处理,这里提供通过WireShark抓取的http请求包:

  虽然HTTP请求包的内容很多,但因为目前实现的功能较少,所以关注的只有http报文起始行就可以,而首部字段可以直接丢弃不处理,后续如果使用认证机制,如白名单,黑名单过滤,帐号/密码保护,资源权限管理等,首部仍然要处理。

对于http报文起始行, 内部以space隔开,并以‘\r\n‘作为结尾与首部隔开。其中GET:HTTP方法, ‘/‘ :资源路径url, HTTP/1.1:协议版本,参照http权威指南的内容,  HTTP协议的常见方法有GET, PUT, DELETE, POST, HEAD这5种,本节中的静态服务器主要涉及到GET方法。了解了需要如何解析HTTP请求报文后,我们先定义一个HTTP报文解析结构,用于存储到解析的信息。

        public class HTTPPrase
        {
           //http方法
           public string http_method;

           //http资源
           public string url;

           //http版本号
           public string version;

           //url解析的请求网页类型
           public string type;
        };

    下面我们就要开始利用C#提供的String方法来截取http报文来实现上述结构体内参数的初始化。

            int pos;

            //根据\r\n截断,获取http报文首部并转换为小写,方便后续处理
            //Get / HTTP/1.1/r/n
            pos = str.IndexOf("\r\n");
            string str_head = str.Substring(0, pos);
            str_head = str_head.ToLower();

            //根据‘ ‘来截断起始行,并赋值给对应参数
            string[] arr = Regex.Split(str_head, @"\s+");
            HTTPServer.HTTPPrase http_head = new HTTPServer.HTTPPrase();
            http_head.http_method = arr[0];      // "Get"
            http_head.url = arr[1];              // "/"
            http_head.version = arr[2];          // "HTTP/1.1"

            //判断是否有通过ajax要求获得或者提交的动态数据
            http_head.ajax_status = str_head.IndexOf(".ajax") != -1 ? true : false;

            byte[] bs = http_head.ajax_status == true ? ajax_process(http_head, str) : static_process(http_head, str);

            return bs;

  下面就可以把数据提交给后端接口,进行处理。因为动态网页处理需要网页端和后端相互的配合,工作量较大,因此本节主要阐述静态网页请求的实现。局域网Web请求一般是通过ip+port的模式直接访问服务器端,所以第一个接收到的请求的url为‘/‘,这时我们需要将它映射到服务端定位的访问主页,目前设置为index.html,对于其它请求,url的值一般是‘/xxx/xxx.js", ‘/xxx/xxx.jpg"等,而在服务器中读取时我们需要定义绝对地址,所以还要在前面添加资源存储的根地址,目前将程序当前所在文件夹+html作为资源的根地址,而且操作系统存储的数据路径为\xxx\xxx.js,所以对于请求中url数据还要替换为‘\\‘(为了保证转义符能够转变为路径符,需要用‘\\‘表示实际的‘\‘),此外为了后续的http响应报文中返回正确的Content-Type字段,还有截取‘.‘后字段,来获取请求文件的类型。

                //获得当前程序所在的文件夹
                string url_str = System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
                if (string.Compare(head.url, "/") == 0)
                {
                    //对于首个请求127.0.0.1:3000/ 返回index.html
                    url_str += "html\\index.html";
                    head.type = "html";
                }
                else
                {
                    //其它请求 如/spring.js 替换为 ...\html\spring.js便于C#查询文件路径
                    url_str =url_str + "html\\" + head.url.Substring(1);
                    url_str = url_str.Replace(‘/‘, ‘\\‘);
                    int pos = url_str.IndexOf(‘.‘);

                    //获得当前请求的网页类型
                    head.type = url_str.Substring(pos + 1);
                }

  到此为止,完成了整个http解析的过程,包括http方法, url资源地址获得并转换为windows系统路径,协议版本获得三个部分。对于静态网页请求,后续就比较简单,查询系统路径下资源,通过文件流打开,并以字符流的形式放置在内存中,作为http响应报文的正文部分。

             //以文件流的方式打开指定路径内文件
             using (FileStream fs = new FileStream(url_str, FileMode.Open, FileAccess.Read))
             {
                  //StreamReader temp = new StreamReader(fs, Encoding.Default);
                  int fslen = (int)fs.Length;
                  byte[] fbyte = new byte[fslen];
                  int r = fs.Read(fbyte, 0, fslen);
                  fs.Close();   

                  //......
            }    

文件打开成功后,我们就要生成http响应报文了,http响应报文和请求报文相同,也由三部分构成。

  状态码:主要为客户端提供一种理解事务处理结果的便捷方式。主要实现的有:

  HTTP/1.1 200 OK 请求没有问题,实体的主体部分包含请求的资源

HTTP/1.1 400 Bad Request 通知客户端它发送了一个错误的请求

  HTTP/1.1 401 Unauthorized 与适当的首部一同返回,通知客户端进行相应的认证

HTTP/1.1 404 No Found 说明服务器无法找到请求的URL

  响应首部:为客户端提供额外的关于服务器的消息,本项目中实现比较简单:

  Content-type:CurrentType\r\n

  Server:C# Web\r\n

  Content-Length:CurrentLength\r\n

  Connection: close

 其中Contenet-type需要根据我们上文获得的type类型来替换,这里阐述常见的替换规则。

Content-Length字段是http响应报文正文的长度,即我们获得资源的总长度(上文中fslen), 最后将状态码,响应首部和正文数据整合在一起通过socket发送到客户端,就实现了静态服务器的全部过程。

                       string HTTP_Current_Head = HTTPServer.HTTP_OK_Head.Replace("CurrentLength", Convert.ToString(fslen));

                            //根据不同url需要返回不同的首部类型 具体对比详见http://tool.oschina.net/commons
                            switch (head.type)
                            {
                                case "jpg":
                                    HTTP_Current_Head = HTTP_Current_Head.Replace("CurrentType", "application/x-jpg");
                                    break;
                                case "png":
                                    HTTP_Current_Head = HTTP_Current_Head.Replace("CurrentType", "image/png");
                                    break;
                                case "html":
                                    HTTP_Current_Head = HTTP_Current_Head.Replace("CurrentType", "text/html");
                                    break;
                                case "gif":
                                    HTTP_Current_Head = HTTP_Current_Head.Replace("CurrentType", "image/gif");
                                    break;
                                case "js":
                                    HTTP_Current_Head = HTTP_Current_Head.Replace("CurrentType", "application/x-javascript");
                                    break;
                                case "asp":
                                    HTTP_Current_Head = HTTP_Current_Head.Replace("CurrentType", "text/asp");
                                    break;
                                default:
                                    HTTP_Current_Head = HTTP_Current_Head.Replace("CurrentType", "text/html");
                                    break;
                            }

                            send_str = HTTPServer.HTTP_OK_Start + HTTP_Current_Head;
                            byte[] head_byte = new byte[send_str.Length];
                            head_byte = Encoding.UTF8.GetBytes(send_str);

                            //字符串流合并,生成发送文件
                            //之前采用的是byte[]->string, string合并, string->byte[],这种方法读取图片乱码
                            //因此修改为,string合并, string->byte[], byte[]合并方式,读取图片成功
                            byte[] send_byte = new byte[send_str.Length + fbyte.Length];
                            Buffer.BlockCopy(head_byte, 0, send_byte, 0, head_byte.Length);
                            Buffer.BlockCopy(fbyte, 0, send_byte, head_byte.Length * sizeof(byte), fbyte.Length);

                            Console.WriteLine("File Send....");
                            return send_byte;

到现在为止,一个简单的静态web服务器就实现了,将希望访问的资源文件放入当前程序文件夹/html/下, 并将首页定义为index.html, 点开服务器程序,浏览器中输入http://127.0.0.1:3000, 就可以查看返回的网页。

具体程序参考: http://files.cnblogs.com/files/zc110747/Web%E6%9C%8D%E5%8A%A1%E5%99%A8.7z

 

时间: 2024-10-07 19:26:30

基于C# Socket的Web服务器---静态资源处理的相关文章

TCP/IP协议学习(四) 基于C# Socket的Web服务器---静态资源处理

目录 1. C# Socket通讯 2. HTTP 解析引擎 3. 资源读取和返回 4. 服务器测试和代码下载 Web服务器是Web资源的宿主,它需要处理用户端浏览器的请求,并指定对应的Web资源返回给用户,这些资源不仅包括HTML文件,JS脚本,JPG图片等,还包括由软件生成的动态内容.为了满足上述需求,一个完整的Web服务器工作流程: 1) 服务器获得浏览器通过TCP/IP连接向服务器发送的http请求数据包. 2) HTTP请求经过Web服务器的HTTP解析引擎分析得出请求方法.资源地址等

LAMP+NFS实现多个web服务器静态资源统一存储

需求分析: 1.前端需支持更大的访问量,单台Web服务器已无法满足需求了,则需扩容Web服务器: 2.虽然动态内容可交由后端的PHP服务器执行,但静态页面还需要Web服务器自己解析,那是否意味着多台Web服务器都需要在各自的系统中都存有一份静态页面数据呢? 其实这样也不是不可以,毕竟文件本地访问,速度还是有优势的,但这却涉及到多台Web服务器间内容的一致性问题,这种问题也不可避免: 那么如果能将静态页面集中存放,所有Web服务器都来集中地取文件,对于文件的一致性就有了保障,这个集中地就叫做"文件

TCP/IP协议学习(五) 基于C# Socket的Web服务器---动态通讯实现

目录 (1).基于Ajax的前端实现 (2).Web服务器后端处理 一个完整的web服务器,不仅需要满足用户端对于图片.文档等资源的需求:还能够对于用户端的动态请求,返回指定程序生成的数据.支持动态请求处理是web服务器的必要组成部分,现有比较成熟的前端动态技术有CGI,ASP/ASP.net, PHP,原生javascript实现的Ajax技术以及基于HTML5的webSocket通讯,它们每一项都涉及很多相关知识,不过归结到核心都是前后端的数据交互,特别是对于后端来说并没有太大区别.作为动态

基于socket的web服务器检测

# coding=utf-8 import sys import socket import re def check_webserver(address, port, resource): address = socket.gethostbyname(address) if not resource.startswith('/'): resource = '/' + resource request_string = 'GET %s HTTP/1.0\r\n\r\n' % (resource)

详谈socket请求Web服务器过程(转)

最开始我们需要明白一件事情,因为这是这篇文章的前提: HTTP协议只是一个应用层协议,它底层是通过TCP进行传输数据的.因此,浏览器访问Web服务器的过程必须先有“连接建立”的发生. 而有人或许会问:众所周知,HTTP协议有两大特性,一个是“无连接”性,一个是“无状态”性.这里的“无连接”岂不是跟上面的说法有冲突?其实这里并没有矛盾,只是人们对“连接”这个词的理解有差异.首先我们来看一下浏览器向Web服务器发出Http请求以及Web服务器给浏览器回复的过程: 1)浏览器创建Socket,按给定I

详谈socket请求Web服务器过程

最开始我们需要明白一件事情,因为这是这篇文章的前提: HTTP协议只是一个应用层协议,它底层是通过TCP进行传输数据的.因此,浏览器访问Web服务器的过程必须先有“连接建立”的发生. 而有人或许会问:众所周知,HTTP协议有两大特性,一个是“无连接”性,一个是“无状态”性.这里的“无连接”岂不是跟上面的说法有冲突?其实这里并没有矛盾,只是人们对“连接”这个词的理解有差异.首先我们来看一下浏览器向Web服务器发出Http请求以及Web服务器给浏览器回复的过程: 1)浏览器创建Socket,按给定I

socket请求Web服务器过程

最开始我们需要明白一件事情,因为这是这篇文章的前提: HTTP协议只是一个应用层协议,它底层是通过TCP进行传输数据的.因此,浏览器访问Web服务器的过程必须先有“连接建立”的发生. 而有人或许会问:众所周知,HTTP协议有两大特性,一个是“无连接”性,一个是“无状态”性.这里的“无连接”岂不是跟上面的说法有冲突?其实这里并没有矛盾,只是人们对“连接”这个词的理解有差异.首先我们来看一下浏览器向Web服务器发出Http请求以及Web服务器给浏览器回复的过程: 1)浏览器创建Socket,按给定I

基于http协议的web服务器

1.http协议是一个属于应用层协议,主要特点概括如下 1)支持客户服务器模式 2)简单快速,客户向服务器请求服务时,只需要传送方法和路径,请求方法有GET,POST.每种方法规定了客户与服务器的联系类型不同,由于http的协议简单,http协议简单,使得web服务器的规模小,因而通信速度很快. 3)灵活,http允许传输任意类型的数据对象. 4)无连接,无连接并不是不需要连接,无连接的含义是每次只处理一个请求,服务器处理完客户的请求,并受到客户应答后,即断开连接 5)无状态,http协议是无状

008 web整合--静态资源的引入

一 .概述 在springboot之中,我们的静态资源文件和页面的内容都没法放置在webapp之中了,因此springboot指定了默认的静态资源存放位置. 二 .静态资源文件 一般情况下,springboot支持我们将静态资源文件,如js,css,html等存放在一些默认的文件下. 我们常用的就是下面的几个文件夹: static文件夹 ,public文件夹. 三.欢迎页的配置 我们知道在web.xml之中,我们可以配置一个欢迎页,现在springboot为我们提供了一个默认的欢迎页,我们只要在