一步步搭建自己的web服务器

时间:2022-10-04 07:51:04

IIS或者其他Web服务器究竟做了哪些工作,让浏览器请求一个URL地址后显示一个漂亮的网页?要想弄清这个疑问,我想我们可以自己写一个简单的web服务器。

思路:

  1. 创建socket监听浏览器请求。
  2. 连接成功,接受浏览器的请求数据。
  3. 响应浏览器的请求。(我们只响应静态文件) 。

好的,思路很清晰。下面就跟着我动手一步步用代码实现。

1、创建socket监听浏览器请求。

  当浏览器请求域名,DNS将域名解析为IP地址。带着缺省的端口80访问我们的服务器。所以Socket的监听,也需要绑定IP和端口。

代码:

// 窗体加载
private void Form1_Load(object sender, EventArgs e)
{
// 关闭线程操作检测
Control.CheckForIllegalCrossThreadCalls = false;
Log(
"服务器启动中 >>>");
IPAddress ip
= IPAddress.Parse(txtIP.Text);
IPEndPoint p
= new IPEndPoint(ip, Convert.ToInt32(txtPort.Text));
socketWatch.Bind(p);
socketWatch.Listen(
10);
// 为了不阻塞主线程,开启后台线程,监听浏览器请求。
Thread t = new Thread(Listen);
t.IsBackground
= true;
t.Start();
Log(
"服务器启动成功。");
}

Listen 进程只需要无限循环接受浏览器发送过来的请求。

private void Listen()

{

   while (true)

   {

     Socket sender=socketWatch.Accept();

     Log(sender.RemoteEndPoint.ToString() + "连接成功");

       }

}

辅助方法,显示提示信息。(不重要)

// 显示提示信息
private void Log(string msg)
{
txtMsg.AppendText(msg
+ "\r\n");
}

执行结果

打开我们的winform程序。

打开浏览器访问: http://192.168.95.1:3099/  地址

就能看到以下结果:

 一步步搭建自己的web服务器

 

2、连接成功,接受浏览器的请求数据。

分析:

浏览器的请求到达服务器后,我们要接待他,就要问他是过来干嘛的。

要和浏览器交流,那么服务器和浏览器就应该说一种语言。这个语言就是http协议。

(对HTTP协议还不太了解的园友可以参考:http协议知识整理

根据HTTP协议,浏览器请求服务器的格式都是规定好的,

所以我们只需要根据格式进行拆分,获取我们感兴趣的数据。

这件事情,已经不属于form1.cs这个类的能力范围了。所以我们找一个管家HttpManager来帮我们处理。至于怎么处理form1不用管,他只需要告诉管家就可以了。

代码:

在form1.cs的Listen中创建管家

HttpManager manager = new HttpManager(sender, Log);

管家接收基础的数据后,再交给专门处理请求数据的对象HttpRequest。

class HttpManager
{
private Socket socket;
private Action<string> Log;
public HttpManager(Socket socket, Action<string> act)
{
this.socket = socket;
this.Log = act;
// 接收消息
string msg = ReceiveMsg();
Log(msg);

try
{
// 去找请求对象,拿到我们需要的数据。
HttpRequest request = new HttpRequest(msg);
}
catch (Exception ex)
{

Log(ex.Message);
}
}

// 接受浏览器请求报文
private string ReceiveMsg()
{
byte[] date = new byte[1024 * 1024];
int count = socket.Receive(date);
return Encoding.UTF8.GetString(date, 0, count);
}
}

 

核心的HttpResponse:

class HttpRequest
{
// 请求原始地址 /xxx/xxx.xxxx
public string RawUrl { get; set; }
// 请求类型 Post/Get
public string Method;
public HttpRequest(string msg)
{
string[] lines = msg.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
string[] arrays = lines[0].Split(' ');
Method
= arrays[0];
RawUrl
= arrays[1];
}
}

 

3、响应浏览器的请求。

 

  响应过程其实也非常简单,IO读取浏览器需要请求的数据,拼接响应报文返回给浏览器。

在HttpManager类接收完数据后,调用响应对象。来帮我们处理响应。

// 构造响应报文,响应请求。
HttpResponse response = new HttpResponse(request.RawUrl, socket, Log);

核心HttpResponse代码

一步步搭建自己的web服务器一步步搭建自己的web服务器
  1 class HttpResponse
2 {
3 public int StatusCode { get; set; }
4 public Dictionary<int, string> Dic = new Dictionary<int, string>();
5 public string ContentType { get; set; }
6 public int ContentLength { get; set; }
7 public byte[] Data { get; set; }
8 public Socket Socket { get; set; }
9 public Action<string> Log { get; set; }
10 public HttpResponse(string rawUrl, Socket socket, Action<string> log)
11 {
12 this.Socket = socket;
13 FillDic();
14 this.Log = log;
15 // 判断是否为静态资源
16 if (Judge(rawUrl))
17 {
18 // IO 读取资源
19 Data = ReadFile(rawUrl);
20 if (Data == null)
21 {
22 // 404 没有这个文件
23 StatusCode = 404;
24 string msg = "没有找到这个文件。";
25 ContentType = "text/html";
26 Data = Encoding.Default.GetBytes(msg);
27 ContentLength = Data.Length;
28 ContentType = "text/html";
29 SendData();
30 }
31 else
32 {
33 ContentLength = Data.Length;
34 StatusCode = 200;
35 ContentType = GetContentType(rawUrl);
36 // 响应请求
37 SendData();
38 }
39 }
40 else
41 {
42 // 不处理
43 string msg = "只处理静态文件。";
44 Data = Encoding.Default.GetBytes(msg);
45 ContentLength = Data.Length;
46 StatusCode = 500;
47 ContentType = "text/html";
48 // 响应请求
49 SendData();
50 }
51
52
53 }
54
55
56 // 判断文件是否为静态资源
57 private bool Judge(string rawUrl)
58 {
59 bool isStatic = false;
60 string ext = Path.GetExtension(rawUrl);
61 switch (ext)
62 {
63 case ".html":
64 case ".htm":
65 case ".jpg":
66 case ".png":
67 case ".gif":
68 case ".js":
69 case ".css":
70 case ".ico": isStatic = true; break;
71 }
72 return isStatic;
73 }
74
75 // 响应
76 private void SendData()
77 {
78 StringBuilder sb = new System.Text.StringBuilder();
79 sb.Append("HTTP/1.1 " + StatusCode + " " + Dic[StatusCode] + "\r\n");
80 sb.Append("Server: ksn/1.0\r\n");
81 sb.Append("Content-Type: " + ContentType + "\r\n");
82 sb.Append("Content-Length: " + ContentLength + "\r\n");
83 sb.Append("\r\n");
84 Log("响应报文: \r\n" + sb.ToString());
85
86 byte[] head = Encoding.UTF8.GetBytes(sb.ToString());
87 List<byte> res = new List<byte>();
88 res.AddRange(head);
89 if (this.Data != null)
90 {
91 res.AddRange(this.Data);
92 }
93 Socket.Send(res.ToArray());
94
95 }
96
97
98 private string GetContentType(string rawUrl)
99 {
100 string ct = "text/html";
101 string ext = Path.GetExtension(rawUrl);
102 switch (ext)
103 {
104 case ".js": ct = "text/javascript";
105 break;
106 case ".css": ct = "text/css";
107 break;
108 case ".jpg": ct = "image/jpeg";
109 break;
110 case ".png": ct = "image/png";
111 break;
112 case ".gif": ct = "image/gif";
113 break;
114 case ".ico": ct = "image/x-ico";
115 break;
116 }
117 return ct;
118 }
119
120 // 初始化状态码
121 private void FillDic()
122 {
123 Dic.Add(200, "Ok");
124 Dic.Add(404, "Not Found");
125 Dic.Add(500, "Internal Error");
126 }
127
128 // 读取请求的资源
129 private byte[] ReadFile(string rawUrl)
130 {
131 string path = AppDomain.CurrentDomain.BaseDirectory;
132 path = path + "web" + rawUrl;
133 if (File.Exists(path))
134 {
135 using (FileStream fs = new FileStream(path, FileMode.Open))
136 {
137 byte[] data = new byte[fs.Length];
138 fs.Read(data, 0, data.Length);
139 return data;
140 }
141 }
142 else
143 {
144 return null;
145 }
146
147
148 }
149 }
View Code

 

  这个类的代码比较多,但是不难,都是按照顺序一步步往下走的。其实web服务器大致就做了这些事情。只不过封装的东西更多,功能更完善。

资源:

项目源码下载地址