基于C#动手实现网络服务器Web Server

2019-12-30 18:15:43刘景俊

这个简单的代码已经可以实现用于小白机器人的网络请求处理了,因为大致只用到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://www.easck.com//开始异步接收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) //发送回复
  {
    try
    {
      output.Write(responseByte, 0, responseByte.Length);
    }
    catch (Exception ex)
    {
      Qdb.Error(ex.Message, QDebugErrorType.Error, "onWebResponse");
      response.StatusCode = (int)HttpStatusCode.InternalServerError;
    }
  }
}