最近在学习网络原理,忽然萌发出本身实现一个网络服务器的想法,而且因为第三代小白机器人的开发须要,我把以前使用python、PHP写的那部分代码都迁移到了C#(别问我为何这么喜欢C#),以前使用PHP就是用来处理网络请求的,如今迁移到C#了,而Linux系统上并无IIS服务器,天然不能使用ASP.Net,因此这个时候本身实现一个功能简单的网络服务器就恰到好处地解决这些问题了。javascript
Web Server在一个B/S架构系统中起到的做用不只多并且至关重要,Web开发者大部分时候并不须要了解它的详细工做机制。虽然不一样的Web Server可能功能并不彻底同样,可是如下三个功能几乎是全部Web Server必须具有的:php
下图显示Web Server在整个Web架构系统中所处的重要位置:css
如上图,Web Server起到了一个“承上启下”的做用(虽然并无“上下”之分),它负责链接用户和Web站点。html
每一个网站就像一个个“插件”,只要网站开发过程当中遵循了Web Server提出的规则,那么该网站就能够“插”在Web Server上,咱们即可以经过浏览器访问网站。前端
浏览器想要拿到哪一个文件(html、css、js、image)就和服务器发请求信息说我要这个文件,而后服务器检查请求合不合法,若是合法就把文件数据传回给浏览器,这样浏览器就能够把网站显示出来了。(一个网站通常会包含n多个文件)java
在C#中有两种方法能够简单实现Web服务器,分别是直接使用Socket和使用封装好的HttpListener。python
由于后者比较方便一些,因此我选择使用后者。web
这是最简单的实现一个网络服务器,能够处理浏览器发过来的请求,而后将指定的字符串内容返回。json
class Program { static void Main(string[] args) { string port = "8080"; HttpListener httpListener = new HttpListener(); httpListener.Prefixes.Add(string.Format("http://+:{0}/", port)); httpListener.Start(); httpListener.BeginGetContext(new AsyncCallback(GetContext), httpListener); //开始异步接收request请求 Console.WriteLine("监听端口:" + port); Console.Read(); } static void GetContext(IAsyncResult ar) { HttpListener httpListener = ar.AsyncState as HttpListener; HttpListenerContext context = httpListener.EndGetContext(ar); //接收到的请求context(一个环境封装体) httpListener.BeginGetContext(new AsyncCallback(GetContext), httpListener); //开始 第二次 异步接收request请求 HttpListenerRequest request = context.Request; //接收的request数据 HttpListenerResponse response = context.Response; //用来向客户端发送回复 response.ContentType = "html"; response.ContentEncoding = Encoding.UTF8; using (Stream output = response.OutputStream) //发送回复 { byte[] buffer = Encoding.UTF8.GetBytes("要返回的内容"); output.Write(buffer, 0, buffer.Length); } } }
这个简单的代码已经能够实现用于小白机器人的网络请求处理了,由于大体只用到GET和POST两种HTTP方法,只须要在GetContext方法里判断GET、POST方法,而后分别给出响应就能够了。浏览器
可是咱们的目的是开发一个真正的网络服务器,固然不能只知足于这样一个专用的服务器,咱们要的是能够提供网页服务的服务器。
那就继续吧。
根据个人研究,提供网页访问服务的服务器作起来确实有一点麻烦,由于须要处理的东西不少。须要根据浏览器请求的不一样文件给出不一样响应,处理Cookies,还要处理编码,还有各类出错的处理。
首先咱们要肯定一下咱们的服务器要提供哪些文件的访问服务。
这里我用一个字典结构来保存。
/// <summary> /// MIME类型 /// </summary> public Dictionary<string, string> MIME_Type = new Dictionary<string, string>() { { "htm", "text/html" }, { "html", "text/html" }, { "php", "text/html" }, { "xml", "text/xml" }, { "json", "application/json" }, { "txt", "text/plain" }, { "js", "application/x-javascript" }, { "css", "text/css" }, { "bmp", "image/bmp" }, { "ico", "image/ico" }, { "png", "image/png" }, { "gif", "image/gif" }, { "jpg", "image/jpeg" }, { "jpeg", "image/jpeg" }, { "webp", "image/webp" }, { "zip", "application/zip"}, { "*", "*/*" } };
剧透一下:其中有PHP类型是咱们后面要使用CGI接入的方式使咱们的服务器支持PHP。
我在QFramework中封装了一个QHttpWebServer模块,这是其中的启动代码。
/// <summary> /// 启动本地网页服务器 /// </summary> /// <param name="webroot">网站根目录</param> /// <returns></returns> public bool Start(string webroot) { //触发事件 if (OnServerStart != null) OnServerStart(httpListener); WebRoot = webroot; try { //监听端口 httpListener.Prefixes.Add("http://+:" + port.ToString() + "/"); httpListener.Start(); httpListener.BeginGetContext(new AsyncCallback(onWebResponse), httpListener); //开始异步接收request请求 } catch (Exception ex) { Qdb.Error(ex.Message, QDebugErrorType.Error, "Start"); return false; } return true; }
如今把网页服务器的核心处理代码贴出来。
这个代码只是作了基本的处理,对于网站的主页只作了html后缀的识别。
后来我在QFramework中封装的模块作了更多的细节处理。
/// <summary> /// 网页服务器相应处理 /// </summary> /// <param name="ar"></param> private void onWebResponse(IAsyncResult ar) { byte[] responseByte = null; //响应数据 HttpListener httpListener = ar.AsyncState as HttpListener; HttpListenerContext context = httpListener.EndGetContext(ar); //接收到的请求context(一个环境封装体) httpListener.BeginGetContext(new AsyncCallback(onWebResponse), httpListener); //开始 第二次 异步接收request请求 //触发事件 if (OnGetRawContext != null) OnGetRawContext(context); HttpListenerRequest request = context.Request; //接收的request数据 HttpListenerResponse response = context.Response; //用来向客户端发送回复 //触发事件 if (OnGetRequest != null) OnGetRequest(request, response); if (rawUrl == "" || rawUrl == "/") //单纯输入域名或主机IP地址 fileName = WebRoot + @"\index.html"; else if (rawUrl.IndexOf('.') == -1) //不带扩展名,理解为文件夹 fileName = WebRoot + @"\" + rawUrl.SubString(1) + @"\index.html"; else { int fileNameEnd = rawUrl.IndexOf('?'); if (fileNameEnd > -1) fileName = rawUrl.Substring(1, fileNameEnd - 1); fileName = WebRoot + @"\" + rawUrl.Substring(1); } //处理请求文件名的后缀 string fileExt = Path.GetExtension(fileName).Substring(1); if (!File.Exists(fileName)) { responseByte = Encoding.UTF8.GetBytes("404 Not Found!"); response.StatusCode = (int)HttpStatusCode.NotFound; } else { try { responseByte = File.ReadAllBytes(fileName); response.StatusCode = (int)HttpStatusCode.OK; } catch (Exception ex) { Qdb.Error(ex.Message, QDebugErrorType.Error, "onWebResponse"); response.StatusCode = (int)HttpStatusCode.InternalServerError; } } if (MIME_Type.ContainsKey(fileExt)) response.ContentType = MIME_Type[fileExt]; else response.ContentType = MIME_Type["*"]; response.Cookies = request.Cookies; //处理Cookies response.ContentEncoding = Encoding.UTF8; using (Stream output = response.OutputStream)