自己寫一個使用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 }
對本服務器的更改情況,將在下一篇博文《自己寫Web服務器(續)》列出