好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

自己写的Web服务器

自己写的Web服务器

自己写的Web服务器

自己写一个使用Http协议的服务器。在谷歌搜了一下,发现其实.NET Framework里面本身提供了HttpListener类,看别人的博文介绍是它是对Socket的简单封装,也有一些人没有用这个类,还是直接用Socekt写了服务器。说是Socket的扩展性反而比较好。HttpListener毕竟是微软封装好的,安全性应该一般会比用Socket写的要高,如果大牛写的就不同了,像我这等水货,其实还是用HttpListener要好一些。但也是个尝试,也是学习,我尝试用Socket写。虽然说是基于Socket,但实际上用的Socket的连接池。连接池的实现细节在上一篇博文《 Socket连接池 》有介绍。

  写之前肯定看过别人的博文,看了这篇《 C#开发自己的Web服务器 》,是翻译老外的博文的。既然是用到Http协议,那肯定要对它有一定的了解,至少懂得看他它的请求头和响应头吧。本人了解的不多,但知道的都能在写这个程序里用得上。

  用谷歌浏览器随便获取了请求和响应的消息结构,列出来简单看一下

 1  GET  / page / 130970/ HTTP / 1.1
 2   Host: kb.cnblogs.com
  3  Connection: keep- alive
  4  Cache-Control: max-age=0
 5  Accept: text / html,application / xhtml+xml,application / xml;q=0.9,* / *;q=0.8
 6  User-Agent: Mozilla / 5.0 (Windows NT 6.1; WOW64) AppleWebKit / 537.31 (KHTML, like Gecko) Chrome / 26.0.1410.64 Safari / 537.31
 7  Accept- Encoding: gzip,deflate,sdch
  8  Accept-Language: zh-CN,zh;q=0.8
 9  Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3

  这个有些部分被我删了,因为篇幅太长了。这个是GET的请求结构,写这个服务器要用到的信息只是第一行请求行而已。由于是GET请求,请求的方法是GET,请求要获取的资源就是“/page/130970”这段。暂时用到的信息就之后这两个而已。

  下面这个则是POST的请求消息结构

  1  POST  / ws / SideRightList.asmx / GetList HTTP / 1.1
  2   Host: kb.cnblogs.com
   3  Connection: keep- alive
   4  Content-Length: 35
  5  Accept: application / json, text / javascript, * /*; q=0.01
   6   Origin: http://kb.cnblogs.com
   7   X-Requested-With: XMLHttpRequest
   8   User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.64 Safari/537.31
   9   Content-Type: application/json; charset=UTF-8
  10   Referer: http://kb.cnblogs.com/page/130970/
  11   Accept-Encoding: gzip,deflate,sdch
  12   Accept-Language: zh-CN,zh;q=0.8
  13   Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3 

  同样以POST这个请求方法作为开头,请求的资源就同样的跟在后面,但是请求的参数就跟在请求头的下两行,可是这里的没有带参数,参数的长度可以在请求头的Content-Length中获得,如这里的参数长度就是35。

  1  HTTP / 1.1 200 OK
  2   Server: Tengine
   3  Date: Fri, 26 Apr 2013 05:50:11  GMT
   4  Content-Type: text / html; charset=utf-8
  5  Transfer- Encoding: chunked
   6  Connection: keep- alive
   7  Vary: Accept- Encoding
   8  Cache-Control:  public , max-age=300
  9  Expires: Fri, 26 Apr 2013 05:54:43  GMT
  10  Last-Modified: Fri, 26 Apr 2013 05:49:43  GMT
  11  X-AspNet-Version: 4.0.30319
 12  X-Powered- By: ASP.NET
  13  X-UA-Compatible: IE= edge
  14  Content-Encoding: gzip

当服务器接收到浏览器发送的请求处理完之后,要对浏览器进行响应,响应的消息结构如上所示,这里要提供的参数不是很多,首先是响应的状态码,下面则是状态码的类别

1XX  提示信息 - 表示请求已被成功接收,继续处理 2XX  成功 - 表示请求已被成功接收,理解,接受 3XX  重定向 - 要完成请求必须进行更进一步的处理 4XX  客户端错误 -  请求有语法错误或请求无法实现 5XX  服务器端错误 -   服务器未能实现合法的请求

这里用到的状态码有三个 200 OK,404 Not Found,501 Not Implemented。

Server则是服务器的名称,Content-Length则是响应内容的长度,Content-Type是服务器定它们的响应中的内容的类型,也称为MIME,我在以往常见的是这几个

text/html text/xml text/plain text/css text/javascript

  HTTP的内容不讲太多了,知道这些足以写这个服务器了。在博客园的知识库了发现一篇文章讲HTTP协议的挺不错的,叫《 HTTP 协议详解 》。

  吹完Http协议的,说回程序了,既然请求头都是一堆字符串,那现在可以把服务器的职能一份为二,一部分是面向网络的,是Socket那部分,刚好不久前完成了Socket的连接池,在这里可以用得上了;另一部分则是对接收到的请求进行处理,字符串处理为主,充其量就加上文件的IO处理。Socket那部分完成了,只要对连接池配置,使用就行了,剩下是对请求的处理和作出响应。

  这里我也是习惯性的定义了一些实体类,好让信息存取方便些。

  第一个则是服务器的配置信息类,配置过IIS的都知道配置服务器需要IP,端口,虚拟目录,有时候需要控制并发量,起始页。这里就简单地存放了这些信息。

 1       public   class   ServerConfigEntity
  2       {
  3           public   string  IP {  get ;  set ; } //  IP地址 
 4           public   int  Port {  get ;  set ; } //  端口号 
 5           public   int  MaxConnect {  get ;  set ; } //  最大并发量 
 6           public   string  VirtualPath {  get ;  set ; } //  虚拟目录 
 7           public   string  DefaultPage {  get ;  set ; } //  起始页 
 8      }

  接着便是请求消息和响应消息了

  1       class   RequestHeader
   2       {
   3           public   string  ActionName {  get ;  set  ; }
   4           public   string  URL {  get ;  set  ; }
   5           public   string  Host {  get ;  set  ; }
   6           public   string  Accept {  get ;  set  ; }
   7           public   string  Connection {  get ;  set  ; }
   8           public   string  Accept_Language {  get ;  set  ; }
   9           public   string  User_Agent {  get ;  set  ; }
  10           public   string  Accept_Encoding {  get ;  set  ; }
  11  
 12           public   string  Form {  get ;  set  ; }
  13           public   int  Content_Length {  get ;  set  ; }
  14           public   string  Referer {  get ;  set  ; }
  15           public   string  Content_Type {  get ;  set  ; }
  16  
 17           public   static  RequestHeader ConvertRequestHander( string   headerStr)
  18           {
  19               string  regActionName =  "  GET|POST  "  ;
  20               string  regURL =  "  (?<=GET|POST).*?(?=HTTP/1.1)  "  ;
  21               string  regHost =  @"  (?<=Host\:\s).*(?=\r\n)  "  ;
  22               string  regAccept =  @"  (?<=Accept\:\s).*(?=\r\n)  "  ;
  23               string  regConnection =  @"  (?<=Connection\:\s).*(?=\r\n)  "  ;
  24               string  regAcceptLanguage =  @"  (?<=Accept-Language\:\s).*(?=\r\n)  "  ;
  25               string  regUserAgent =  @"  (?<=User-Agent\:\s).*(?=\r\n)  "  ;
  26               string  regAcceptEncoding =  @"  (?<=Accept-Encoding\:\s).*(?=\r\n)  "  ;
  27  
 28               string  regForm =  @"  (?<=\r\n\r\n).*  "  ;
  29               string  regConntenLength =  @"  (?<=Connten-Length\:\s).*(?=\r\n)  "  ;
  30               string  regRefere =  @"  (?<=Refere\:\s).*(?=\r\n)  "  ;
  31               string  regContentType =  @"  (?<=Content-Type\:\s).*(?=\r\n)  "  ;
  32  
 33              RequestHeader hander =  new   RequestHeader();
  34              hander.ActionName =  Regex.Match(headerStr, regActionName).Value;
  35              hander.URL =  Regex.Match(headerStr, regURL).Value;
  36              hander.Host =  Regex.Match(headerStr, regHost).Value;
  37              hander.Accept =  Regex.Match(headerStr, regAccept).Value;
  38              hander.Connection =  Regex.Match(headerStr, regConnection).Value;
  39              hander.Accept_Language =  Regex.Match(headerStr, regAcceptLanguage).Value;
  40              hander.Accept_Encoding =  Regex.Match(headerStr, regAcceptEncoding).Value;
  41              hander.User_Agent =  Regex.Match(headerStr, regUserAgent).Value;
  42               string  tempStr =  Regex.Match(headerStr, regConntenLength).Value;
  43              hander.Content_Length = Convert.ToInt32(tempStr ==  ""  ?  "  0  "   : tempStr);
  44              hander.Referer =  Regex.Match(headerStr, regRefere).Value;
  45              hander.Content_Type =  Regex.Match(headerStr, regContentType).Value;
  46              hander.Form =  Regex.Match(headerStr, regForm).Value;
  47               return   hander;
  48           }
  49      }

  这里用到了正则去提取请求消息的相应内容,虽然前面介绍时只是提到用到一点点信息,但是以后扩展开来说不能用到其他信息的,于是一概提取了。

  1       class   ResponseHeader
   2       {
   3           public   string  ResponseCode {  get ;  set  ; }
   4           public   string  Server {  get ;  set  ; }
   5           public   int  Content_Length {  get ;  set  ; }
   6           public   string  Connection {  get ;  set  ; }
   7           public   string  Content_Type {  get ;  set  ; }
   8  
  9           public   override   string   ToString()
  10           {
  11               string  result =  string  .Empty;
  12              result +=  "  HTTP/1.1   "  +  this .ResponseCode +  "  \r\n  "  ;
  13              result +=  "  Server:   " + this .Server+ "  \r\n  "  ;
  14              result +=  "  Content-Length:   "  +  this .Content_Length +  "  \r\n  "  ;
  15              result +=  "  Connection:   " + this .Connection+ "  \r\n  "  ;
  16              result +=  "  Content-Type:   "  +  this .Content_Type +  "  \r\n\r\n  "  ;
  17               return   result;
  18           }
  19  
 20           public   string   CreateErrorHtml()
  21           {
  22               string  html =  @"  <html><head><meta http-equiv=""Content-Type"" content=""text/html;charset=utf-8""></head><body>{0}</body></html>  "  ;
  23              html = html.Replace( "  {0}  " ,  "  <h2>My Web Server</h2><div>{0}</div>  "  );
  24              html =  string .Format(html,  this  .ResponseCode);
  25               return   html;
  26           }
  27      }

  响应消息这里重写了基类的ToString()方法,能比较方便的根据当前响应的信息转成字符串。还提供了一个生成错误页面内容的方法CreateErrorHtml()。

       接着到服务器的类了,类里面有以下的私有字段

 1           private  SocketPoolController _pool; //  Socket连接池 
 2           private  Dictionary< string ,  string > _supportExtension; //  支持的资源后缀 
 3           private  ServerConfigEntity config; //  服务器的配置信息 
 4           private   bool  _runFlag; //  服务器启动标识 

  类的构造函数,构造一个连接池,给支持的附上支持的后缀以及相对应的MIME。

  1           public   HttpProtocolServer(ServerConfigEntity config)
   2           {
   3               this .config =  config;
   4              _pool =  new  SocketPoolController( 32768  , config.MaxConnect);
   5              _supportExtension =  new  Dictionary< string ,  string > () 
   6               {
   7                  {  "  htm  " ,  "  text/html  "   },
   8                  {  "  html  " ,  "  text/html  "   },
   9                  {  "  xml  " ,  "  text/xml  "   },
  10                  {  "  txt  " ,  "  text/plain  "   },
  11                  {  "  css  " ,  "  text/css  "   },
  12                  {  "  png  " ,  "  image/png  "   },
  13                  {  "  gif  " ,  "  image/gif  "   },
  14                  {  "  jpg  " ,  "  image/jpg  "   },
  15                  {  "  jpeg  " ,  "  image/jpeg  "   },
  16                  {  "  zip  " ,  "  application/zip  "  },
  17                  { "  js  " , "  text/javascript  "  },
  18                  {  "  dll  " ,  "  text/plain  "   },
  19                  { "  aspx  " , "  text/html  "  }
  20               };
  21              _runFlag =  false  ;
  22          }

外放的方法有两个,分别是启动服务器RunServer()和停止服务器StopServer()

  启动服务器要把连接池运行起来,同时给注册一个接收事件,以便在接收到浏览器的请求时做出响应。

 1           public   void   RunServer()
  2           {
  3               if  (_runFlag)  return  ;
  4              _pool.OnReceive +=  new   SocketPoolController.RecevieHandler(HandleRequest);
  5               _pool.RunPool(config.IP, config.Port);
  6              _runFlag =  true  ;
  7          }

       关闭服务器只需要把连接池关闭了就行了

 1           public   void   StopServer()
  2           {
  3               _pool.StopPool();
  4              _pool =  null  ;
  5              _runFlag =  false  ;
  6          }

接收到浏览器请求后处理的方法(也就是跟连接池注册的方法)如下

  1           private   void  HandleRequest( string  uid,  string   header)
   2           {
   3              RequestHeader request =  RequestHeader.ConvertRequestHander(header);
   4              ResponseHeader response =  new   ResponseHeader();
   5              response.Server =  "  My Test WebSite  "  ;
   6              response.Connection =  "  close  "  ;
   7  
  8               //  暂时只支持POST和GET的请求,其他的请求就视为未实现,发501响应 
  9               if  (request.ActionName !=  "  GET  "  && request.ActionName !=  "  POST  "  )
  10               {
  11                  response.ResponseCode =  "  501 Not Implemented  "  ;
  12                  response.Content_Type =  "  text/html  "  ;
  13                   SendErrorResponse(uid, response);
  14                   return  ;
  15               }
  16  
 17               //  对请求资源名称经行处理。主要是去除GET时带的参数,还有把斜杠换过来 
 18               string  fullURL = config.VirtualPath +  request.URL;
  19               string  fileName =(fullURL.Contains( '  ?  ' )? Regex.Match(fullURL,  @"  .*(?=\?)  " ).Value:fullURL).Replace( '  /  ' , '  \\  '  );
  20  
 21               //  如果请求的只是一个斜杠的,那证明请求的是默认页面 
 22               if  (fileName == fullURL +  "  \\  "  )
  23               {
  24                   //  如果配置中有默认页的,发200的响应 
 25                   string  defaultFile =  Path.Combine(config.VirtualPath, config.DefaultPage);
  26                   if   (File.Exists(defaultFile))
  27                   {
  28                      response.Content_Type =  "  text/html  "  ;
  29                      response.ResponseCode =  "  200 OK  "  ;
  30                       SendResponse(uid, File.ReadAllText(defaultFile), response);
  31                       return  ;
  32                   }
  33                   //  如果不存在的,当404处理了 
 34                   else 
 35                   {
  36                      response.ResponseCode =  "  404 Not Found  "  ;
  37                      response.Content_Type =  "  text/html  "  ;
  38                       SendErrorResponse(uid, response);
  39                       return  ;
  40                   }
  41               }
  42  
 43               //  如果请求的资源不存在的,那就发送404 
 44              FileInfo fileInfo =  new   FileInfo(fileName);
  45               if  (! fileInfo.Exists)
  46               {
  47                  response.ResponseCode =  "  404 Not Found  "  ;
  48                  response.Content_Type =  "  text/html  "  ;
  49                   SendErrorResponse(uid, response);
  50                   return  ;
  51               }
  52  
 53               //  如果请求的资源不在支持的范围内,也当作404了,感觉不是404的,貌似是403的 
 54               string  extension = fileInfo.Extension.TrimStart( '  .  '  );
  55               if  ( string .IsNullOrEmpty(extension) || ! _supportExtension.ContainsKey(extension))
  56               {
  57                  response.ResponseCode =  "  404 Not Found  "  ;
  58                  response.Content_Type =  "  text/html  "  ;
  59                   SendErrorResponse(uid, response);
  60                   return  ;
  61               }
  62  
 63               //  既然也不是请求起始页的,也没发生上面列的错误的,就正常响应 
 64              response.Content_Type =  _supportExtension[extension];
  65              response.ResponseCode =  "  200 OK  "  ;
  66              FileStream fs = null  ;
  67               try 
 68               {
  69                  fs =  File.OpenRead(fileInfo.FullName);
  70                   byte [] datas =  new   byte  [fileInfo.Length];
  71                  fs.Read(datas,  0  , datas.Length);
  72                   SendResponse(uid, datas, response);
  73               }
  74               finally 
 75               {
  76                   fs.Close();
  77                   fs.Dispose();
  78                  fs =  null  ;
  79               }
  80               return  ;
  81          }

发送消息的方法有三个,都是内部方法

SendResponse(string uid,string content,ResponseHeader header)是原始版的,发送普通的响应。 SendResponse(string uid, byte[] content, ResponseHeader header)是重载过的,专门发送内容已经是byte[]的响应。 SendErrorResponse(string uid,ResponseHeader header)用于专门发送错误响应的,其实内部也是调用了SendResponse(string uid,string content,ResponseHeader header)方法。从发送消息的情况来看,总共利用Socket发了两次数据,第一次是发响应消息,第二次才是发响应内容。

  1           private   void  SendErrorResponse( string   uid,ResponseHeader header)
   2           {
   3               string  errorPageContent =  header.CreateErrorHtml();
   4              header.Content_Length =  errorPageContent.Length;
   5               SendResponse(uid, errorPageContent, header);
   6           }
   7  
  8           private   void  SendResponse( string  uid, string   content,ResponseHeader header)
   9           {
  10              header.Content_Length =  content.Length;
  11               _pool.SendMessage(uid, header.ToString());
  12               _pool.SendMessage(uid, content);
  13           }
  14  
 15           private   void  SendResponse( string  uid,  byte  [] content, ResponseHeader header)
  16           {
  17              header.Content_Length =  content.Length;
  18               _pool.SendMessage(uid, header.ToString());
  19               _pool.SendMessage(uid, content);
  20          }

  这个简单的Web服务器就完成了,测试了一下,发现效率比不上IIS,普通浏览一个页面察觉不出来,当下载几个文件时就有差别了,IIS的用了接近2秒的时间,而这个服务器去用了接近四秒的时间。不知是哪里慢了,可能是连接池处理得不好。下面提供了源码,要用到连接池的,连接池的代码这里不提供了,需的话可以在上一篇博文《 Socket连接池 》里获取好了,做这个Web服务器是为了抛砖引玉,不足的,错的,遗漏的东西还很多,希望各位园友多多指点,谢谢!

整份源码

  1      class   RequestHeader
    2       {
    3           public   string  ActionName {  get ;  set  ; }
    4           public   string  URL {  get ;  set  ; }
    5           public   string  Host {  get ;  set  ; }
    6           public   string  Accept {  get ;  set  ; }
    7           public   string  Connection {  get ;  set  ; }
    8           public   string  Accept_Language {  get ;  set  ; }
    9           public   string  User_Agent {  get ;  set  ; }
   10           public   string  Accept_Encoding {  get ;  set  ; }
   11  
  12           public   string  Form {  get ;  set  ; }
   13           public   int  Content_Length {  get ;  set  ; }
   14           public   string  Referer {  get ;  set  ; }
   15           public   string  Content_Type {  get ;  set  ; }
   16  
  17           public   static  RequestHeader ConvertRequestHander( string   headerStr)
   18           {
   19               string  regActionName =  "  GET|POST  "  ;
   20               string  regURL =  "  (?<=GET|POST).*?(?=HTTP/1.1)  "  ;
   21               string  regHost =  @"  (?<=Host\:\s).*(?=\r\n)  "  ;
   22               string  regAccept =  @"  (?<=Accept\:\s).*(?=\r\n)  "  ;
   23               string  regConnection =  @"  (?<=Connection\:\s).*(?=\r\n)  "  ;
   24               string  regAcceptLanguage =  @"  (?<=Accept-Language\:\s).*(?=\r\n)  "  ;
   25               string  regUserAgent =  @"  (?<=User-Agent\:\s).*(?=\r\n)  "  ;
   26               string  regAcceptEncoding =  @"  (?<=Accept-Encoding\:\s).*(?=\r\n)  "  ;
   27  
  28               string  regForm =  @"  (?<=\r\n\r\n).*  "  ;
   29               string  regConntenLength =  @"  (?<=Connten-Length\:\s).*(?=\r\n)  "  ;
   30               string  regRefere =  @"  (?<=Refere\:\s).*(?=\r\n)  "  ;
   31               string  regContentType =  @"  (?<=Content-Type\:\s).*(?=\r\n)  "  ;
   32  
  33              RequestHeader hander =  new   RequestHeader();
   34              hander.ActionName =  Regex.Match(headerStr, regActionName).Value;
   35              hander.URL =  Regex.Match(headerStr, regURL).Value;
   36              hander.Host =  Regex.Match(headerStr, regHost).Value;
   37              hander.Accept =  Regex.Match(headerStr, regAccept).Value;
   38              hander.Connection =  Regex.Match(headerStr, regConnection).Value;
   39              hander.Accept_Language =  Regex.Match(headerStr, regAcceptLanguage).Value;
   40              hander.Accept_Encoding =  Regex.Match(headerStr, regAcceptEncoding).Value;
   41              hander.User_Agent =  Regex.Match(headerStr, regUserAgent).Value;
   42               string  tempStr =  Regex.Match(headerStr, regConntenLength).Value;
   43              hander.Content_Length = Convert.ToInt32(tempStr ==  ""  ?  "  0  "   : tempStr);
   44              hander.Referer =  Regex.Match(headerStr, regRefere).Value;
   45              hander.Content_Type =  Regex.Match(headerStr, regContentType).Value;
   46              hander.Form =  Regex.Match(headerStr, regForm).Value;
   47               return   hander;
   48           }
   49       }
   50  
  51       class   ResponseHeader
   52       {
   53           public   string  ResponseCode {  get ;  set  ; }
   54           public   string  Server {  get ;  set  ; }
   55           public   int  Content_Length {  get ;  set  ; }
   56           public   string  Connection {  get ;  set  ; }
   57           public   string  Content_Type {  get ;  set  ; }
   58  
  59           public   override   string   ToString()
   60           {
   61               string  result =  string  .Empty;
   62              result +=  "  HTTP/1.1   "  +  this .ResponseCode +  "  \r\n  "  ;
   63              result +=  "  Server:   " + this .Server+ "  \r\n  "  ;
   64              result +=  "  Content-Length:   "  +  this .Content_Length +  "  \r\n  "  ;
   65              result +=  "  Connection:   " + this .Connection+ "  \r\n  "  ;
   66              result +=  "  Content-Type:   "  +  this .Content_Type +  "  \r\n\r\n  "  ;
   67               return   result;
   68           }
   69  
  70           public   string   CreateErrorHtml()
   71           {
   72               string  html =  @"  <html><head><meta http-equiv=""Content-Type"" content=""text/html;charset=utf-8""></head><body>{0}</body></html>  "  ;
   73              html = html.Replace( "  {0}  " ,  "  <h2>My Web Server</h2><div>{0}</div>  "  );
   74              html =  string .Format(html,  this  .ResponseCode);
   75               return   html;
   76           }
   77       }
   78  
  79       public   class   ServerConfigEntity
   80       {
   81           public   string  IP {  get ;  set  ; }
   82           public   int  Port {  get ;  set  ; }
   83           public   int  MaxConnect {  get ;  set  ; }
   84           public   string  VirtualPath {  get ;  set  ; }
   85           public   string  DefaultPage {  get ;  set  ; }
   86       }
   87  
  88       public   class   HttpProtocolServer
   89       {
   90           private   SocketPoolController _pool;
   91           private  Dictionary< string ,  string >  _supportExtension;
   92           private   ServerConfigEntity config;
   93           private   bool   _runFlag;
   94  
  95           public   HttpProtocolServer(ServerConfigEntity config)
   96           {
   97               this .config =  config;
   98              _pool =  new  SocketPoolController( 32768  , config.MaxConnect);
   99              _supportExtension =  new  Dictionary< string ,  string > () 
  100               {
  101                  {  "  htm  " ,  "  text/html  "   },
  102                  {  "  html  " ,  "  text/html  "   },
  103                  {  "  xml  " ,  "  text/xml  "   },
  104                  {  "  txt  " ,  "  text/plain  "   },
  105                  {  "  css  " ,  "  text/css  "   },
  106                  {  "  png  " ,  "  image/png  "   },
  107                  {  "  gif  " ,  "  image/gif  "   },
  108                  {  "  jpg  " ,  "  image/jpg  "   },
  109                  {  "  jpeg  " ,  "  image/jpeg  "   },
  110                  {  "  zip  " ,  "  application/zip  "  },
  111                  { "  js  " , "  text/javascript  "  },
  112                  {  "  dll  " ,  "  text/plain  "   },
  113                  { "  aspx  " , "  text/html  "  }
  114               };
  115              _runFlag =  false  ;
  116           }
  117  
 118           public   void   RunServer()
  119           {
  120               if  (_runFlag)  return  ;
  121              _pool.OnReceive +=  new   SocketPoolController.RecevieHandler(HandleRequest);
  122               _pool.RunPool(config.IP, config.Port);
  123              _runFlag =  true  ;
  124           }
  125  
 126           public   void   StopServer()
  127           {
  128               _pool.StopPool();
  129              _pool =  null  ;
  130              _runFlag =  false  ;
  131           }
  132  
 133           private   void  HandleRequest( string  uid,  string   header)
  134           {
  135              RequestHeader request =  RequestHeader.ConvertRequestHander(header);
  136              ResponseHeader response =  new   ResponseHeader();
  137              response.Server =  "  My Test WebSite  "  ;
  138              response.Connection =  "  close  "  ;
  139  
 140               //  暂时只支持POST和GET的请求,其他的请求就视为未实现,发501响应 
 141               if  (request.ActionName !=  "  GET  "  && request.ActionName !=  "  POST  "  )
  142               {
  143                  response.ResponseCode =  "  501 Not Implemented  "  ;
  144                  response.Content_Type =  "  text/html  "  ;
  145                   SendErrorResponse(uid, response);
  146                   return  ;
  147               }
  148  
 149               //  对请求资源名称经行处理。主要是去除GET时带的参数,还有把斜杠换过来 
 150               string  fullURL = config.VirtualPath +  request.URL;
  151               string  fileName =(fullURL.Contains( '  ?  ' )? Regex.Match(fullURL,  @"  .*(?=\?)  " ).Value:fullURL).Replace( '  /  ' , '  \\  '  );
  152  
 153               //  如果请求的只是一个斜杠的,那证明请求的是默认页面 
 154               if  (fileName == fullURL +  "  \\  "  )
  155               {
  156                   //  如果配置中有默认页的,发200的响应 
 157                   string  defaultFile =  Path.Combine(config.VirtualPath, config.DefaultPage);
  158                   if   (File.Exists(defaultFile))
  159                   {
  160                      response.Content_Type =  "  text/html  "  ;
  161                      response.ResponseCode =  "  200 OK  "  ;
  162                       SendResponse(uid, File.ReadAllText(defaultFile), response);
  163                       return  ;
  164                   }
  165                   //  如果不存在的,当404处理了 
 166                   else 
 167                   {
  168                      response.ResponseCode =  "  404 Not Found  "  ;
  169                      response.Content_Type =  "  text/html  "  ;
  170                       SendErrorResponse(uid, response);
  171                       return  ;
  172                   }
  173               }
  174  
 175               //  如果请求的资源不存在的,那就发送404 
 176              FileInfo fileInfo =  new   FileInfo(fileName);
  177               if  (! fileInfo.Exists)
  178               {
  179                  response.ResponseCode =  "  404 Not Found  "  ;
  180                  response.Content_Type =  "  text/html  "  ;
  181                   SendErrorResponse(uid, response);
  182                   return  ;
  183               }
  184  
 185               //  如果请求的资源不在支持的范围内,也当作404了,感觉不是404的,貌似是403的 
 186               string  extension = fileInfo.Extension.TrimStart( '  .  '  );
  187               if  ( string .IsNullOrEmpty(extension) || ! _supportExtension.ContainsKey(extension))
  188               {
  189                  response.ResponseCode =  "  404 Not Found  "  ;
  190                  response.Content_Type =  "  text/html  "  ;
  191                   SendErrorResponse(uid, response);
  192                   return  ;
  193               }
  194  
 195               //  既然也不是请求起始页的,也没发生上面列的错误的,就正常响应 
 196              response.Content_Type =  _supportExtension[extension];
  197              response.ResponseCode =  "  200 OK  "  ;
  198              FileStream fs = null  ;
  199               try 
 200               {
  201                  fs =  File.OpenRead(fileInfo.FullName);
  202                   byte [] datas =  new   byte  [fileInfo.Length];
  203                  fs.Read(datas,  0  , datas.Length);
  204                   SendResponse(uid, datas, response);
  205               }
  206               finally 
 207               {
  208                   fs.Close();
  209                   fs.Dispose();
  210                  fs =  null  ;
  211               }
  212               return  ;
  213           }
  214  
 215           private   void  SendErrorResponse( string   uid,ResponseHeader header)
  216           {
  217               string  errorPageContent =  header.CreateErrorHtml();
  218              header.Content_Length =  errorPageContent.Length;
  219               SendResponse(uid, errorPageContent, header);
  220           }
  221  
 222           private   void  SendResponse( string  uid, string   content,ResponseHeader header)
  223           {
  224              header.Content_Length =  content.Length;
  225               _pool.SendMessage(uid, header.ToString());
  226               _pool.SendMessage(uid, content);
  227           }
  228  
 229           private   void  SendResponse( string  uid,  byte  [] content, ResponseHeader header)
  230           {
  231              header.Content_Length =  content.Length;
  232               _pool.SendMessage(uid, header.ToString());
  233               _pool.SendMessage(uid, content);
  234           }
  235      }

 

 

分类:  C# ,  Socket编程

标签:  Socket ,  Web服务器 ,  Http

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于自己写的Web服务器的详细内容...

  阅读:34次