1. 程式人生 > >開發屬於自己的Web服務器

開發屬於自己的Web服務器

accep star 地址 address lai 版本 pla dom shu

本文基於.net core 的控制臺程序作為服務端

main函數:

 1 class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Console.WriteLine("The server is starting......");
 6 
 7             new Server().StartServer();
 8 
 9             Console.ReadLine();
10         }
11     }

其中核心代碼在Server這個類上面:

 1 public class Server
 2     {
 3         private Socket socketWatch = null;
 4         private Thread threadWatch = null;
 5         private string ipAddress = "127.0.0.1";
 6         private string port = "11111";
 7 
 8         public Server()
 9         {
10             socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
11 socketWatch.Bind(new IPEndPoint(IPAddress.Parse(ipAddress), int.Parse(port))); 12 socketWatch.Listen(100); 13 // 創建Thread->後臺執行 14 threadWatch = new Thread(ListenClientConnect); 15 threadWatch.IsBackground = true; 16 } 17 18 public
void StartServer() 19 { 20 threadWatch.Start(socketWatch); 21 } 22 23 private void ListenClientConnect(object objSocket) 24 { 25 Socket socketListen = objSocket as Socket; 26 27 while (true) 28 { 29 Socket proxSocket = socketListen.Accept(); 30 byte[] data = new byte[1024 * 1024 * 2]; 31 int length = proxSocket.Receive(data, 0, data.Length, SocketFlags.None); 32 // Step1:接收HTTP請求 33 string requestText = Encoding.Default.GetString(data, 0, length); 34 HttpContext context = new HttpContext(requestText); 35 // Step2:處理HTTP請求 36 HttpApplication application = new HttpApplication(); 37 application.ProcessRequest(context); 38 // Step3:響應HTTP請求 39 Console.WriteLine(string.Format("{0} {1} from {2}", context.Request.HttpMethod, context.Request.Url, proxSocket.RemoteEndPoint.ToString())); 40 proxSocket.Send(context.Response.GetResponseHeader()); 41 proxSocket.Send(context.Response.Body); 42 // Step4:即時關閉Socket連接 43 proxSocket.Shutdown(SocketShutdown.Both); 44 proxSocket.Close(); 45 } 46 } 47 }

上面代碼中,主要是基於Socket和線程。在構造函數中初始化了服務器端Socket,還初始化了Thread,並且設置為後臺線程。ListenClientConnect函數主要做的事情是接受瀏覽器請求,並且轉化為HttpContext和HttpApplication,最後輸出響應並且關閉socket。

這裏面有幾個比較重要的類,主要如下:

 1 public class HttpContext
 2     {
 3         public HttpRequest Request { get; set; }
 4         public HttpResponse Response { get; set; }
 5 
 6         public HttpContext(string requestText)
 7         {
 8             Request = new HttpRequest(requestText);
 9             Response = new HttpResponse();
10         }
11     }

HttpContext模擬asp的HttpContext,裏面有兩個看起來很熟悉的類,HttpRequest和HttpResponse

 1 public class HttpRequest
 2     {
 3         public HttpRequest(string requestText)
 4         {
 5             string[] lines = requestText.Replace("\r\n", "\r").Split(\r);
 6             string[] requestLines = lines[0].Split( );
 7             // 獲取HTTP請求方式、請求的URL地址、HTTP協議版本
 8             if(requestLines.Length >= 2)
 9             {
10                 HttpMethod = requestLines[0];
11                 Url = requestLines[1];
12                 HttpVersion = requestLines[2];
13             }
14         }
15         // 請求方式:GET or POST?
16         public string HttpMethod { get; set; }
17         // 請求URL
18         public string Url { get; set; }
19         // Http協議版本
20         public string HttpVersion { get; set; }
21         // 請求頭
22         public Dictionary<string, string> HeaderDictionary { get; set; }
23         // 請求體
24         public Dictionary<string, string> BodyDictionary { get; set; }
25     }
 1 public class HttpResponse
 2     {
 3         // 響應狀態碼
 4         public string StateCode { get; set; }
 5         // 響應狀態描述
 6         public string StateDescription { get; set; }
 7         // 響應內容類型
 8         public string ContentType { get; set; }
 9         //響應報文的正文內容
10         public byte[] Body { get; set; }
11 
12         // 生成響應頭信息
13         public byte[] GetResponseHeader()
14         {
15             string strRequestHeader = string.Format(@"HTTP/1.1 {0} {1}
16 Content-Type: {2}
17 Accept-Ranges: bytes
18 Server: Microsoft-IIS/7.5
19 X-Powered-By: ASP.NET
20 Date: {3} 
21 Content-Length: {4}
22 
23 ", StateCode, StateDescription, ContentType, string.Format("{0:R}", DateTime.Now), Body.Length);
24 
25             return Encoding.UTF8.GetBytes(strRequestHeader);
26         }
27     }

這兩個核心類是關於請求和響應的。

IHttpHandler是另外一個很熟悉的接口,一般處理程序中都會實例化它

1 public interface IHttpHandler
2     {
3         void ProcessRequest(HttpContext context);
4     }

我們處理請求的時候就是依靠實例化這個接口了,在我們的實例上面就是HttpApplication

 1 public class HttpApplication : IHttpHandler
 2     {
 3         // 對請求上下文進行處理
 4         public void ProcessRequest(HttpContext context)
 5         {
 6             // 1.獲取網站根路徑
 7             if(string.IsNullOrEmpty(context.Request.Url))
 8             {
 9                 return;
10             }
11             string bastPath = AppDomain.CurrentDomain.BaseDirectory;
12             string fileName = Path.Combine(bastPath, "LZZWebSite", context.Request.Url.TrimStart(/));
13             string fileExtension = Path.GetExtension(context.Request.Url);
14             // 2.處理動態文件請求
15             if (fileExtension.Equals(".aspx") || fileExtension.Equals(".ashx"))
16             {
17                 string className = Path.GetFileNameWithoutExtension(context.Request.Url);
18                 IHttpHandler handler = Assembly.GetExecutingAssembly().CreateInstance($"lzzWebServerDemo.Page.{className}", true) as IHttpHandler;
19                 handler.ProcessRequest(context);
20                 return;
21             }
22             // 3.處理靜態文件請求
23             if (!File.Exists(fileName))
24             {
25                 context.Response.StateCode = "404";
26                 context.Response.StateDescription = "Not Found";
27                 context.Response.ContentType = "text/html";
28                 string notExistHtml = Path.Combine(bastPath, @"LZZWebSite\notfound.html");
29                 context.Response.Body = File.ReadAllBytes(notExistHtml);
30             }
31             else
32             {
33                 context.Response.StateCode = "200";
34                 context.Response.StateDescription = "OK";
35                 context.Response.ContentType = GetContenType(Path.GetExtension(context.Request.Url));
36                 context.Response.Body = File.ReadAllBytes(fileName);
37             } 
38         }
39 
40         // 根據文件擴展名獲取內容類型
41         public string GetContenType(string fileExtension)
42         {
43             string type = "text/html; charset=UTF-8";
44             switch (fileExtension)
45             {
46                 case ".aspx":
47                 case ".html":
48                 case ".htm":
49                     type = "text/html; charset=UTF-8";
50                     break;
51                 case ".png":
52                     type = "image/png";
53                     break;
54                 case ".gif":
55                     type = "image/gif";
56                     break;
57                 case ".jpg":
58                 case ".jpeg":
59                     type = "image/jpeg";
60                     break;
61                 case ".css":
62                     type = "text/css";
63                     break;
64                 case ".js":
65                     type = "application/x-javascript";
66                     break;
67                 default:
68                     type = "text/plain; charset=gbk";
69                     break;
70             }
71             return type;
72         }

上面的業務比較清晰,如果是靜態資源,就直接響應返回。如果是動態資源,例如aspx、ashx的話就通過反射實例化對應的處理類。我們例子上是這樣模擬的:

 1 public class LzzPage: IHttpHandler
 2     {
 3         public void ProcessRequest(HttpContext context)
 4         {
 5             StringBuilder sbText = new StringBuilder();
 6             sbText.Append("<html>");
 7             sbText.Append("<head></head>");
 8             sbText.Append("<body>");
 9             sbText.Append("<h1>demo</h1>");
10             sbText.Append("lzzdemolzzdemo");
11             sbText.Append(string.Format("<h3>time:{0}</h3>", DateTime.Now.ToString()));
12             sbText.Append("</body>");
13             sbText.Append("</html>");
14             context.Response.Body = Encoding.UTF8.GetBytes(sbText.ToString());
15             context.Response.StateCode = "200";
16             context.Response.ContentType = "text/html";
17             context.Response.StateDescription = "OK";
18         }
19     }

最後來一張整個體統的結構圖

技術分享圖片

運行圖:

技術分享圖片

開發屬於自己的Web服務器