<>
程序的主干
这里主要是创建socket对象,监听浏览器的连接,通过socket接收浏览器发送过来的请求报文,将请求报文传递给HttpContent类进行处理
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace MyIISServer
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.txtIp.Text = "192.168.253.3";
this.txtPort.Text = "50000";
Control.CheckForIllegalCrossThreadCalls = false;
}
private void btnStartServer_Click(object sender, EventArgs e)
{
try
{
//当点击开始监听的时候,在服务器端创建一个负责监听IP地址跟端口号的Socket
Socket skConn = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ip = IPAddress.Any;
IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text.Trim()));
//监听
skConn.Bind(point);
skConn.Listen(10);
startState.Text = "服务已经启动";
// 使用线程池接受用户的连接
ThreadPool.QueueUserWorkItem(new WaitCallback((skConnObj) =>
{
//socket1表示的是当前监听客户端连接的socket
Socket socket1 = skConnObj as Socket;
while (true)
{
//等待客户端连接;Accept()这个方法能接收客户端的连接,并为新连接创建一个负责通信的Socket
Socket skCommu = socket1.Accept();
//获取当前浏览器(客户端)发送过来的请求报文(因为这里只接收浏览器的一次发送请求(因为HTTP是无状态的,请求是分次数的,如果还有请求又会重新来过,所以这里一次只会接收一次请求),所以不需要循环)
byte[] buffers = new byte[1020 * 102 * 2];
//接收从浏览器发送过来的数据;Receive()方法是:从绑定的 System.Net.Sockets.Socket 套接字接收数据,将数据存入接收缓冲区。(即:buffers这个二进制数组中存储的就是浏览器发送过来的请求报文)
int length = skCommu.Receive(buffers);
//将buffers这个二进制数组转换成字符串 (即:将请求报文转换成字符串)
string httpRequestMsg = Encoding.UTF8.GetString(buffers, 0, length);
txtLog.AppendText(httpRequestMsg);
//调用PR方法对请求进行处理
ProcessRequest(httpRequestMsg, skCommu);
}
}), skConn);//注意参数的值skConn ;匿名方法的形参是:skConnObj 实参是skConn 即skConnObj的值是skConn
}
catch
{
}
}
//PR方法(用来处理浏览器发送过来请求报文,对请求报文进行封装。既然要对请求报文进行封装,就需要在调用这个PR方法的时候将请求报文作为参数传递过来)
private void ProcessRequest(string httpRequestMsg, Socket socket)
{
//1.把浏览器的请求报文进行封装
//把用户请求的报文传递到HttpContext对象中,由该对象进行解析
HttpContext context = new HttpContext(httpRequestMsg);
//2.处理请求(这里我把处理请求的方法封装到一个名字叫HttpApplication的类中)
HttpApplication application = new HttpApplication();
application.ProcessRequest(context);
//3.返回响应
socket.Send(context.Response.ResponseHeader);
socket.Send(context.Response.ResponseBody);
socket.Shutdown(SocketShutdown.Both);
//服务器关闭连接
socket.Close();
socket.Dispose();
}
}
}
HttpContext类
这里是主要实现的是通过new一个HttpReuqestd对象,将浏览器的请求报文字符串通过HttpReuqestd类构造函数的形式传递到HttpReuqestd类中,然后HttpReuqestd类里面对浏览器的请求报文进行封装。封装好后,在通过new一个HttpResponsed对象,通过HttpResponsed类的构造函数,将封装好的这个HttpReuqestd对象传递到HttpResponsed类中,在HttpResponsed类中,我们根据请求报文,定义两个对象,一个对象是响应报文头ResponseHeader,一个对象是响应报文体ResponseBoyd。(注意,我们仅仅是在HttpResponsed类中构建响应报文头,即:根据请求报文,来构建一个响应报文头。我们在这里并没有给响应报文体ResponseBoyd赋值,仅仅定义了一个ResponseBoyd对象在这里而已。给它赋值是在HttpApplication类里面干的事情)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyIISServer
{
/// <summary>
/// 封装Request对象和Response对象
/// </summary>
public class HttpContext
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="httpRequestMsgString">浏览器的请求报文字符串</param>
public HttpContext(string httpRequestMsg)
{
//对用户传递过来的请求报文进行解析,解析后分别保存到Request对象和Response对象中
//在这里new一个HttpRequest类对象,然后再HttpRequest类的构造函数中,将解析后的请求报文数据赋值给HttpRequest类的对应属性,然后给这个对象赋值给HttpContext类的Request对象
this.Request = new HttpRequest(httpRequestMsg);
//创建一个用来响应的HttpResponsed对象;既然服务器要对浏览器的请求做响应,所以你肯定要给请求报文给我啊,因为在前面我们已经将请求报文封装到了Request对象里面了,所以这里要传递一个HttpRequest类的对象过来。
this.Response = new HttpResponse(this.Request);
}
public HttpRequest Request { get; set; }
public HttpResponse Response { get; set; }
}
}
HttpRequest类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyIISServer
{
/// <summary>
/// 封装用户的请求信息
/// </summary>
public class HttpRequest
{
//GET /HtmlPage1.html HTTP/1.1
//Accept text/html, application/xhtml+xml, */*
//Accept-Language zh-CN
//User-Agent Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
//Accept-Encoding gzip, deflate
//Host localhost:53241
//DNT 1
//Connection Keep-Alive
/// <summary>
/// 用户请求的方法Post/Get
/// </summary>
public string Method { get; set; }
/// <summary>
/// 用户请求资源的路径
/// </summary>
public string RequestUrl { get; set; }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="httpRequestMsg"></param>
public HttpRequest(string httpRequestMsg)
{
string[] requestMsgArray = httpRequestMsg.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
//在构造函数中初始化
this.Method = requestMsgArray[0].Split(new string[] { " ", " ", " " }, StringSplitOptions.RemoveEmptyEntries)[0].ToString();
//在构造函数中初始化
this.RequestUrl = requestMsgArray[0].Split(new string[] { " ", " ", " " }, StringSplitOptions.RemoveEmptyEntries)[1].ToString();
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyIISServer
{
/// <summary>
/// 封装服务器对浏览器(用户)的响应信息
/// </summary>
public class HttpResponse
{
private HttpRequest httpRequest;
//服务器要给浏览器响应,就需要有报文头和报文体
//响应头
//其实我们也可以将这个属性设置为一个类对象。
public byte[] ResponseHeader
{
get
{
//构建响应报文头
StringBuilder builder = new StringBuilder();
//HTTP/1.1 200 OK
//Content-Type text/html
//Last-Modified Tue, 11 Aug 2015 08:21:40 GMT
//Accept-Ranges bytes
//ETag "6bc282bfed4d01:0"
//Server Microsoft-IIS/8.0
//X-SourceFiles =?UTF-8?B?SDpcYXNwLm5ldFxTb2x1dGlvbjFcV2ViQXBwbGljYXRpb24xXEh0bWxQYWdlMS5odG1s?=
//X-Powered-By ASP.NET
//Date Tue, 11 Aug 2015 08:58:10 GMT
//Content-Length 421
//(浏览器请求的资源不存在就是404,等等需要做很详细的判断;这里我们假设请求资源肯定存在,所以返回200)
builder.AppendLine("HTTP/1.1 200 OK");
builder.AppendLine("Content-Type: " + GetUserContentType());
builder.AppendLine("Content-Length: " + this.ResponseBody.Length);
//因为响应报文头与报文体之间有一个空行,所以这里需要有个换行builder.AppendLine()操作
builder.AppendLine();
//返回二进制的响应报文头
return Encoding.UTF8.GetBytes(builder.ToString());
}
//set { }
}
/// <summary>
/// 响应体(即:用来存储服务器对浏览器响应的具体内容)
/// </summary>
public byte[] ResponseBody { get; set; }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="httpRequest">封装了请求报文的HttpRequest类对象</param>
public HttpResponse(HttpRequest httpRequest)
{
this.httpRequest = httpRequest;
}
private
/// <summary>
/// 根据浏览器请求的URL地址的后缀名来设置Content-Type值
/// </summary>
/// <param name="httpRequest">封装了Http请求报文的HttpRequest对象</param>
/// <returns></returns>
string GetUserContentType()
{
string contentType = string.Empty;
//这里要根据浏览器请求报文里的请求地址的扩展名来判断浏览器请求的是图片还是js文件还是css文件
string filePostfix = Path.GetExtension(this.httpRequest.RequestUrl);
switch (filePostfix)
{
case ".aspx":
case ".html":
case ".htm":
contentType = "text/html; charset=utf-8";
break;
case ".png":
contentType = "image/png";
break;
case ".jpg":
contentType = "image/jpg";
break;
case ".Jpeg":
contentType = "image/Jpge";
break;
case ".css":
contentType = "text/css";
break;
case ".js":
contentType = "application/x-javascript";
break;
default:
contentType = "text/plain";
break;
}
return contentType;
}
}
}
HttpApplication.cs类
这里主要是处理用户的请求 (通过传递一个HttpContent对象来过,里调用这个对象的Request属性的httpRequestUrl属性,获取浏览器请求的url地址的后缀名,根据后缀名来判断浏览器请求的是动态资源还是静态资源,如果是静态资源就获取这个url对应的文件所在服务器上磁盘的实际路径,(即:实际物理路径)然后读取这个路径,然后将读取后得到的二进制数组赋值给HttpContent对象的Response属性的ResponseBody属性)。如果是动态资源,就获取这个httpRequestUrl的无后缀的文件名,这个文件名的值实际上就是后台处理类的类名。然后我们通过获取当前正在执行的程序集,通过反射获取这个程序集里对应的这个类的类型,并创建它的实例。这样我们就得到了浏览器请求的动态资源的后台类对象,然后我们可以将这个对象转换成IHttpHandler接口对象(因为所有的后台类都实现了IHttpHandler接口)而IHttpHandler接口下有一个ProcessRequest()方法,这个方法是我们程序员为了处理用户的请求,根据需求来自己写的。 我们可以用这个对象调用这个ProcessRequest方法来处理用户的请求。
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace MyIISServer
{
public class HttpApplication
{
//这个PR方法就是对请求进行处理
public void ProcessRequest(HttpContext context)
{
//这里如何对浏览器的请求进行处理?
string ext = Path.GetExtension(context.Request.RequestUrl);
if (ext == ".aspx")
{
//1.如果浏览器请求的是动态资源
//假如浏览器请求的是http://192.168.253.3:50000/Default.aspx (注:我服务器里面没有Default.aspx页面,只有一个对应的处理Default.aspx页面的Default.cs类)
//1.1首先建立一个处理http://192.168.253.3:50000/Default.aspx请求的Default.cs类
//1.2获取浏览器请求资源的的文件名(不带扩展名)即:获取该文件对应的后台类的名称
string className = Path.GetFileNameWithoutExtension(context.Request.RequestUrl);
//1.3.1获取当前类的命名空间
string ns= MethodBase.GetCurrentMethod().DeclaringType.Namespace;
//利用反射动态创建对象
//1.3获取当前正在执行的程序集(即:获取Default类所在的程序集)
//CreateInstance()方法的意思是:从当前正在执行的程序集中查找指定的类型,并使用系统激活器,创建它的实例
//注:CreateInstance()方法的参数是一个类的完全限定名;即:命名空间.类名,所以我们需要在1.3.1中获取当前类的命名空间
IHttpHandler objPage = (IHttpHandler)Assembly.GetExecutingAssembly().CreateInstance(ns + "." + className);
objPage.ProcessRequest(context);
}
else
{
//2.如果浏览器请求的是静态资源:
//2.1根据用户请求的静态资源的路径和文件名与当前程序执行的exe的路径拼接出用户请求的资源的完整磁盘路径
//2.1.1 获取当前执行的exe的路径(即:获取项目下MyIISServer.exe这个文件的路径)
//Assembly表示一个程序集 GetExecutingAssembly方法表示获取包含当前执行的代码的程序集。Location属性:获取包含清单的已加载文件的路径或 UNC 位置。
//通过调试得知:p1的值为:H:\asp.net\Solution1\MyIISServer\bin\Debug
string p1 = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
//用户请求的静态资源的实际文件路径
//通过调试得知fileName的值为:H:\asp.net\Solution1\MyIISServer\bin\Debug\web\HtmlPage1.html
string fileName = Path.Combine(Path.Combine(p1, "web", context.Request.RequestUrl.TrimStart('/')));
if (File.Exists(fileName))
{
//通过IO操作读取该文件,将读取到的数据设置到 context.Response.ResponseBody中
context.Response.ResponseBody = File.ReadAllBytes(fileName);
}
else
{
//如果资源文件不存在就返回一个0个字节的二进制空数组
context.Response.ResponseBody = new byte[0];
}
}
}
}
}
假设用户请求静态资源文件:http://192.168.253.3:50000/HtmlPage1.html的时候
一下是HtmlPage1.html页面
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title></title>
<script src="jquery-1.11.2.js"></script>
<link href="stylesheet1.css" rel="stylesheet" />
</head>
<body>
<div id="div1">我是静态的HTML网页</div>
<img src="123.jpg" />
</body>
</html>
假设当用户请求动态资源文件: http://192.168.253.3:50000/Default.aspx的时候:
以下是Default.cs处理请求类 (这个类一般是由程序员自己写的)
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace MyIISServer
{
//假如说一个网站下面有很多个动态的.aspx网页,为了保证我这些动态页面的后台类都有某些必要的方法(即:处理用户请求的方法),所以我们在一个接口中定义一个这样的方法,然后让这些后台类都实现这个接口,这样就保证了这些后台类都具有同样的一个处理用户请求的方法
public class Default:IHttpHandler
{
public void ProcessRequest(HttpContext content)
{
StringBuilder buider = new StringBuilder();
buider.Append("<html><head><title>动态网页</title></head>");
buider.Append("<body>");
for (int i = 0; i < 5; i++)
{
buider.Append("<h1>" + DateTime.Now.ToString() + "</h1>");
Thread.Sleep(1000);
}
buider.Append("</body>");
buider.Append("</html>");
content.Response.ResponseBody = Encoding.UTF8.GetBytes(buider.ToString());
}
}
}
IHttpHandler.cs接口
这个接口主要是用来给浏览器请求的动态资源的后台类继承实现的。这样就保证了所有的后台类都有一个处理用户请求的ProcessRequest()方法
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyIISServer
{
/// <summary>
/// 这个IHttpHandler接口中有一个PR方法,这个PR方法就是用来处理用户请求的
/// </summary>
public interface IHttpHandler
{
void ProcessRequest(HttpContext content);
}
}