自己动手实现网络服务器(Web Server)——基于C#
前言
最近在学习网络原理,突然萌发出自己实现一个网络服务器的想法,并且由于第三代小白机器人的开发需要,我把之前使用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#的更多相关文章
- Tomcat是怎么工作的(2) -- 动手实现山寨版的简单Web Server
本文先讲解一下Java web server都是怎么工作的.web server也叫HTTP server——顾名思义它是用HTTP协议和客户端交互的.客户端一般就是各种各样的浏览器了.相信所有朋友都 ...
- Jexus Web Server 完全傻瓜化图文配置教程(基于Ubuntu 12.04.3 64位)[内含Hyper-v 2012虚拟机镜像下载地址]
1. 前言 近日有感许多新朋友想尝试使用Jexus,不过绝大多数都困惑徘徊在Linux如何安装啊,如何编译Mono啊,如何配置Jexus啊...等等基础问题,于是昨日向宇内流云兄提议,不如搞几个配置好 ...
- C#中自己动手创建一个Web Server(非Socket实现)
目录 介绍 Web Server在Web架构系统中的作用 Web Server与Web网站程序的交互 HTTPListener与Socket两种方式的差异 附带Demo源码概述 Demo效果截图 总结 ...
- web server服务器
使用最多的 web server服务器软件有两个:微软的信息服务器(iis),和Apache. 通俗的讲,Web服务器传送(serves)页面使浏览器可以浏览,然而应用程序服务器提供的是客户端应用程序 ...
- C# .net基于Http实现web server(web服务)
原文:C# .net基于Http实现web server(web服务) 什么是 web server? 百度百科是这么解释的: Web Server中文名称叫网页服务器或web服务器.WEB服务器也 ...
- Atitit.Gui控件and面板----web server区----- web服务器监控面板and控制台条目
Atitit.Gui控件and面板----web server区----- web服务器监控面板and控制台条目 1. Resin4.0.22 1 2. 查看http连接数::Summary>& ...
- 阿里云服务器Windows Server 2008/2012部署Office Web Server 2013
以前成功将Office Web Server 2013部署在了本地服务器上,此次是将Office Web Server 2013部署在阿里云服务器Windows Server 2008和2012上,中 ...
- node - web 服务器 、server 服务器
node 作为服务端 - 基本使用 1. web 服务器 web.html <!DOCTYPE html> <html> <head> <meta chars ...
- 动手打造轻量web服务器(二)路由
tomcat启动慢?自己动手打造轻量web服务器(一) 上篇讲了怎么做一个最简单的web服务器,这篇就是在上篇加上URL路由功能(什么是路由?) 首先,根据http获得请求行 val scanner ...
随机推荐
- 软件工程(GZSD2015)第二次作业进度
贵州师范大学软件工程第二次作业 徐 镇 王铭霞 张 英 涂江枫 张 燕 安 坤 周 娟 杨明颢 杨家堂 罗文豪 娄秀盛 周 娟 李盼 岳庆 张颖 李丽思 邓婷 唐洁 郑倩 尚清丽 陈小丽 毛茸 宋光能 ...
- 团队作业4——第一次项目冲刺(ALpha版本)第三天
一.Daily Scrum Meeting照片 二.燃尽图 三.项目进展 1.界面 完成了前端的HTML页面设计 2.功能 完成了后端数据处理的全部基本功能: 数据结构设计及数据交互操作 博客页面数据 ...
- Java学习10——package和import
package和import语句 为了便于管理大型软件系统中数目众多的类,解决类的命名冲突问题,Java引入包(package)机制,提供类的多重类命名空间,使用时,import引入相应package ...
- java201521123118《java程序设计》第3周总结
1. 本周学习 总结初学面向对象,会学习到很多碎片化的概念与知识.尝试学会使用思维导图将这些碎片化的概念.知识组织起来.请使用纸笔或者下面的工具画出本周学习到的知识点.截图或者拍照上传. 2. 书面作 ...
- 基于图形检测API(shape detection API)的人脸检测
原文:https://paul.kinlan.me/face-detection/ 在 Google 开发者峰会中,谷歌成员 Miguel Casas-Sanchez 跟我说:"嘿 Paul ...
- Thrift教程初级篇——thrift安装环境变量配置第一个实例
前言: 因为项目需要跨语言,c++客户端,web服务端,远程调用等需求,所以用到了RPC框架Thrift,刚开始有点虚,第一次接触RPC框架,后来没想到Thrift开发方便上手快,而且性能和稳定性也不 ...
- YYHS-Floor it
题目描述 输入 输出 样例输入 5 97 样例输出 11 提示 题解 先不管p,通过列举前面几项,不难发现当i为偶数时,a[i]=a[i-1]+a[i-2],当i为奇数时,a[i]=a[i ...
- 创建maven项目pom.xml第一行报错
之前也创建过几次maven项目,也是第一行报错,之前直接是右键项目强制更新maven好像就解决了,这次遇见这个问题使用这个方法好像不起作用了,给的一堆英文报错又看不懂,幸好在网上看见路人甲大神提示,根 ...
- MongoDB分片原理篇
MongoDB分片 为什么需要Sharded cluster? MongoDB目前3大核心优势:『灵活模式』+ 『高可用性』 + 『可扩展性』,通过json文档来实现灵活模式,通过复制集来保证高可用, ...
- QT4.8.5 连接数据库(读写数据)
#include "mainwindow.h" #include <QApplication> #include <QLabel> #include < ...