前言

最近在学习网络原理,突然萌发出自己实现一个网络服务器的想法,并且由于第三代小白机器人的开发需要,我把之前使用python、PHP写的那部分代码都迁移到了C#(别问我为什么这么喜欢C#),之前使用PHP就是用来处理网络请求的,现在迁移到C#了,而Linux系统上并没有IIS服务器,自然不能使用ASP.Net,所以这个时候自己实现一个功能简单的网络服务器就恰到好处地解决这些问题了。

基本原理

Web Server在一个B/S架构系统中起到的作用不仅多而且相当重要,Web开发者大部分时候并不需要了解它的详细工作机制。虽然不同的Web Server可能功能并不完全一样,但是以下三个功能几乎是所有Web Server必须具备的:

  • 接收来自浏览器端的HTTP请求
  • 将请求转发给指定Web站点程序(后者由Web开发者编写,负责处理请求)
  • 向浏览器发送请求处理结果

下图显示Web Server在整个Web架构系统中所处的重要位置:

如上图,Web Server起到了一个“承上启下”的作用(虽然并没有“上下”之分),它负责连接用户和Web站点。

每个网站就像一个个“插件”,只要网站开发过程中遵循了Web Server提出的规则,那么该网站就可以“插”在Web Server上,我们便可以通过浏览器访问网站。

太长不看版原理

浏览器想要拿到哪个文件(html、css、js、image)就和服务器发请求信息说我要这个文件,然后服务器检查请求合不合法,如果合法就把文件数据传回给浏览器,这样浏览器就可以把网站显示出来了。(一个网站一般会包含n多个文件)

话不多说,直接上代码

在C#中有两种方法可以简单实现Web服务器,分别是直接使用Socket和使用封装好的HttpListener。

因为后者比较方便一些,所以我选择使用后者。

这是最简单的实现一个网络服务器,可以处理浏览器发过来的请求,然后将指定的字符串内容返回。

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) //发送回复
{
try
{
output.Write(responseByte, 0, responseByte.Length);
}
catch (Exception ex)
{
Qdb.Error(ex.Message, QDebugErrorType.Error, "onWebResponse");
response.StatusCode = (int)HttpStatusCode.InternalServerError;
}
}
}

这样就可以提供基本的网页访问了,经过测试,使用Bootstrap,Pure等前端框架的网页都可以完美访问,性能方面一般般。(在QFramework的封装中我做了一点性能优化,有一点提升)我觉得要在性能方面做提升还是要在多线程处理这方面做优化,由于篇幅关系,就不把多线程版本的代码贴出来了。

接下来我们还要实现服务器的PHP支持。

首先定义两个字段。

/// <summary>
/// 是否开启PHP功能
/// </summary>
public bool PHP_CGI_Enabled = true; /// <summary>
/// PHP执行文件路径
/// </summary>
public string PHP_CGI_Path = "php-cgi";

接下来在网页服务的核心代码里做PHP支持的处理。

//PHP处理
string phpCgiOutput = "";
Action phpProc = new Action(() =>
{
try
{
string argStr = ""; if (request.HttpMethod == "GET")
{
if (rawUrl.IndexOf('?') > -1)
argStr = rawUrl.Substring(rawUrl.IndexOf('?'));
}
else if (request.HttpMethod == "POST")
{
using (StreamReader reader = new StreamReader(request.InputStream))
{
argStr = reader.ReadToEnd();
}
} Process p = new Process();
p.StartInfo.CreateNoWindow = false; //不显示窗口
p.StartInfo.RedirectStandardOutput = true; //重定向输出
p.StartInfo.RedirectStandardInput = false; //重定向输入
p.StartInfo.UseShellExecute = false; //是否指定操作系统外壳进程启动程序
p.StartInfo.FileName = PHP_CGI_Path;
p.StartInfo.Arguments = string.Format("-q -f {0} {1}", fileName, argStr);
p.Start(); StreamReader sr = p.StandardOutput;
while (!sr.EndOfStream)
{
phpCgiOutput += sr.ReadLine() + Environment.NewLine;
} responseByte = sr.CurrentEncoding.GetBytes(phpCgiOutput);
}
catch (Exception ex)
{
Qdb.Error(ex.Message, QDebugErrorType.Error, "onWebResponse->phpProc");
response.StatusCode = (int)HttpStatusCode.InternalServerError;
}
}); if (fileExt == "php" && PHP_CGI_Enabled)
{
phpProc();
}
else
{
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;
}
}
}

这样就实现了基于PHP-CGI的PHP支持了,经过测试,基本的php页面都可以支持,但是需要使用curl和xml这类扩展的暂时还没办法。需要做更多的工作。

接下来我会给服务器做一个GUI界面,供大家测试。

同时也会把QFramework框架发布,有兴趣的可以使用基于QFramework的服务器封装。


博客原文地址:http://blog.deali.cn/?p=875

我的微信公众号:DealiAxy

自己动手实现网络服务器(Web Server)——基于C#的更多相关文章

  1. Tomcat是怎么工作的(2) -- 动手实现山寨版的简单Web Server

    本文先讲解一下Java web server都是怎么工作的.web server也叫HTTP server——顾名思义它是用HTTP协议和客户端交互的.客户端一般就是各种各样的浏览器了.相信所有朋友都 ...

  2. Jexus Web Server 完全傻瓜化图文配置教程(基于Ubuntu 12.04.3 64位)[内含Hyper-v 2012虚拟机镜像下载地址]

    1. 前言 近日有感许多新朋友想尝试使用Jexus,不过绝大多数都困惑徘徊在Linux如何安装啊,如何编译Mono啊,如何配置Jexus啊...等等基础问题,于是昨日向宇内流云兄提议,不如搞几个配置好 ...

  3. C#中自己动手创建一个Web Server(非Socket实现)

    目录 介绍 Web Server在Web架构系统中的作用 Web Server与Web网站程序的交互 HTTPListener与Socket两种方式的差异 附带Demo源码概述 Demo效果截图 总结 ...

  4. web server服务器

    使用最多的 web server服务器软件有两个:微软的信息服务器(iis),和Apache. 通俗的讲,Web服务器传送(serves)页面使浏览器可以浏览,然而应用程序服务器提供的是客户端应用程序 ...

  5. C# .net基于Http实现web server(web服务)

    原文:C# .net基于Http实现web server(web服务) 什么是 web server?  百度百科是这么解释的: Web Server中文名称叫网页服务器或web服务器.WEB服务器也 ...

  6. Atitit.Gui控件and面板----web server区----- web服务器监控面板and控制台条目

    Atitit.Gui控件and面板----web server区----- web服务器监控面板and控制台条目 1. Resin4.0.22 1 2. 查看http连接数::Summary>& ...

  7. 阿里云服务器Windows Server 2008/2012部署Office Web Server 2013

    以前成功将Office Web Server 2013部署在了本地服务器上,此次是将Office Web Server 2013部署在阿里云服务器Windows Server 2008和2012上,中 ...

  8. node - web 服务器 、server 服务器

    node 作为服务端 - 基本使用 1. web 服务器 web.html <!DOCTYPE html> <html> <head> <meta chars ...

  9. 动手打造轻量web服务器(二)路由

    tomcat启动慢?自己动手打造轻量web服务器(一) 上篇讲了怎么做一个最简单的web服务器,这篇就是在上篇加上URL路由功能(什么是路由?) 首先,根据http获得请求行 val scanner ...

随机推荐

  1. 201521123032 《Java程序设计》第7周学习总结

    1. 本周学习总结 以你喜欢的方式(思维导图或其他)归纳总结集合相关内容. 2. 书面作业 ArrayList代码分析 1.1 解释ArrayList的contains源代码 在contains方法中 ...

  2. 201521123017 《Java程序设计》第4周学习总结

    1. 本周学习总结 2. 书面作业 Q1.注释的应用 使用类的注释与方法的注释为前面编写的类与方法进行注释,并在Eclipse中查看.(截图)   Q2.面向对象设计(大作业1,非常重要) 2.1 将 ...

  3. 201521123075 《Java程序设计》第3周学习总结

    1. 本周学习总结 2. 书面作业 1.代码阅读 public class Test1 { private int i = 1;//这行不能修改 private static int j = 2; p ...

  4. 201521123026 《java程序设计》 第九周学习总结

    1. 本章学习总结 以你喜欢的方式(思维导图或其他)归纳总结异常相关内容. 2. 书面作业 Q1.常用异常 题目5-1 1.1 截图你的提交结果(出现学号) PTA提交结果: 运行结果截图: 1.2 ...

  5. 201521123014 《Java程序设计》第14周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多数据库相关内容. 数据库是为了实现一定目的按某种规则组织起来的"数据"的"集合".常见的数 ...

  6. 使用electron将应用程序加入到系统托盘

    博主电脑

  7. Android 跳转系统选择本地视频的功能

    今天在项目开发的过程中产品要求添加选择本地视频的功能,于是就翻阅和查找各种资料,进行功能的开发,但是在开发过程中发现,各种不同的品牌的手机跳转至系统选择本地视频的功能结果不太一样,所以我就对一些主流的 ...

  8. linux crontab设置定期执行脚本

    Linux下crontab命令的用法 任务调度的crond常驻命令crond 是linux用来定期执行程序的命令.当安装完成操作系统之后,默认便会启动此任务调度命令.crond命令每分锺会定期检查是否 ...

  9. Spring配置文件的命名空间URI

    Spring配置文件介绍 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi=" ...

  10. vue2.0动态绑定图片src属性值初始化时报错

    在vue2.0中,经常会使用类似这样的语法 v-bind:src = " imgUrl "(缩写 :src = " imgUrl "),看一个案例 <te ...