模拟Web服务器

     模拟Web服务器,实现基本功能来了解Web的原理,对于学习Asp.Net当然是铺垫作用,也能更好上手Asp.Net知识点。
     首先浏览器发送请求报文给Web服务器,
     紧接着Web服务器接收浏览器发送过来的请求,并将接收到的请求内容进行数据处理,然后准备响应报文,包括响应头、响应行以及响应体,发送响应报文给浏览器。
     最后浏览器将接收到的响应报文进行解析,展示给用户,就是我们看到的网页内容了。
     不过模拟Web服务器,只是模拟Web服务器的部分功能。
     第一步——监听浏览器是否进行连接?如果连接,接收浏览器发送的请求报文数据内容:
//运行服务器
private void btnStart_Click(object sender, EventArgs e)
{
//创建连接的ip地址
IPAddress ip = IPAddress.Parse(txtIp.Text);
//创建连接的ip地址和端口号
IPEndPoint port = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text));
//创建一个基本的socket
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
//绑定ip和端口号
socket.Bind(port);
//监听的最大等待个数
socket.Listen(10);
ShowMsg("开始运行了...");

//接收浏览器,将监听封装成一个方法
Thread th = new Thread(Listen);
//将监听的这个方法放到线程中,设为后台线程。
th.IsBackground = true;
th.Start(socket);
}
catch (Exception ex)
{
ShowMsg(ex.Message);
}
}

//监听浏览器的连接
void Listen(object o)
{
Socket socket = o as Socket;
while (true)
{
//创建负责通信用的socket
Socket connSocket = socket.Accept();
ShowMsg(connSocket.RemoteEndPoint.ToString() + "连接成功了");

//接收消息
byte[] buffer = new byte[1024 * 1024];
//实际接收到的字节个数
int num = connSocket.Receive(buffer);
string str = Encoding.UTF8.GetString(buffer, 0, num);
ShowMsg(str);
}
}
     第二步——将显示消息交由DataConnection类来实现:
//监听浏览器的连接
void Listen(object o)
{
Socket socket = o as Socket;
while (true)
{
//创建负责通信用的socket
Socket connSocket = socket.Accept();
ShowMsg(connSocket.RemoteEndPoint.ToString() + "连接成功了");

//接收消息,写到一个DataConnection类中,new一个对象
//通过DataConnection接收http请求和发送http响应
DataConnection dc = new DataConnection(connSocket, ShowMsg);
}
}
DataConnection类实现代码:
//定义一个委托,实现展示消息方法在类与类之间进行传递
delegate void DelSetText(string msg);

/// <summary>
/// 1、接收浏览器发送的请求
/// 2、向浏览器发送响应
/// </summary>
class DataConnection
{
private Socket connSocket;
//要指向窗体中给文本框赋值的方法(ShowMsg)
private DelSetText del;
//构造函数中把通信用的connsocket,还有委托方法传过来
//获取浏览器发送过来的http请求
public DataConnection(Socket connSocket, DelSetText del)
{
this.connSocket = connSocket;
this.del = del;

//2、执行接收消息的方法
string msg = RecMsg();
//把消息——http请求报文显示在文本框中
del(msg);
}

//接收消息
string RecMsg()
{
//接收消息
byte[] buffer = new byte[1024 * 1024];
//实际接收到的字节个数
int num = connSocket.Receive(buffer);
string str = Encoding.UTF8.GetString(buffer, 0, num);
return str;
}
     第三步——DataConnection类分析请求报文并根据请求路径返回具体页面:
//定义一个委托,实现展示消息方法在类与类之间进行传递
delegate void DelSetText(string msg);

/// <summary>
/// 1、接收浏览器发送的请求
/// 2、向浏览器发送响应
/// </summary>
class DataConnection
{
private Socket connSocket;
//要指向窗体中给文本框赋值的方法(ShowMsg)
private DelSetText del;
//构造函数中把通信用的connsocket,还有委托方法传过来
//获取浏览器发送过来的http请求
public DataConnection(Socket connSocket, DelSetText del)
{
this.connSocket = connSocket;
this.del = del;

//2、执行接收消息的方法
string msg = RecMsg();
//把消息——http请求报文显示在文本框中
del(msg);

//3、分析http请求报文,写到一个Request类中
Request req = new Request(msg);
//GET /1.html HTTP/1.1
//req.Url相对路径 /1.html

//4、根据请求的路径,判断是静态页面还是动态页面
Judge(req.Url);
}
Request类中实现代码:
/// <summary>
/// 解析请求报文中的url
/// </summary>
class Request
{
/// <summary>
/// 解析msg中的url
/// </summary>
/// <param name="msg">请求报文</param>
public Request(string msg)
{
try
{
//以行分割请求报文
string[] arrMsg = msg.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
//请求报文中的请求行
string firstLine = arrMsg[0];
//以空格分割请求报文中的请求行
string[] arr = firstLine.Split(' ');
//获得请求行中的路径
url = arr[1];
}
catch (Exception ex)
{
url = "";
}
}
private string url;
/// <summary>
/// 请求行中的路径
/// </summary>
public string Url
{
get { return url; }
set { url = value; }
}
}
     第四步——DataConnection类中将生成响应头交由Response类实现:
Response类实现代码:
/// <summary>
/// 准备响应头
/// </summary>
class Response
{
//用来存放状态码和状态描述
private Dictionary<int, string> dic = new Dictionary<int, string>();
//状态码
private int statusCode = 200;
//内容的长度
private int contentLength;
//内容的类型
private string contentType;
//调用构造方法,获得状态码、内容的长度和内容的类型。
public Response(int statusCode, int contentLength, string url)
{
//填充字典,状态码和状态码的描述
FillDic();
//状态码
this.statusCode = statusCode;
//内容的长度
this.contentLength = contentLength;
//调用方法来获得内容的类型
GetContentType(url);
}

//填充字典,状态码和状态码描述对应的键值对
void FillDic()
{
dic.Add(200, "OK");
dic.Add(404, "Not Found");
}

//根据后缀给contentType内容类型赋值
void GetContentType(string url)
{
//.htm
string ext = Path.GetExtension(url);
switch (ext)
{
case ".htm":
case ".html":
contentType = "text/html";
break;
case ".css":
contentType = "text/css";
break;
case ".js":
contentType = "text/javascript";
break;
case ".jpg":
contentType = "image/jpeg";
break;
default:
contentType = "text/html";
break;
}
}

//生成响应头
public byte[] GetResponseHeaders()
{
//HTTP/1.1 200 OK
//Content-Length: 427380
//Content-Type: image/jpeg;charset=utf-8
StringBuilder sb = new StringBuilder();
sb.AppendLine("HTTP/1.1 " + statusCode + " " + dic[statusCode]);
sb.AppendLine("Content-Length: " + contentLength);
sb.AppendLine("Content-Type: " + contentType + ";charset=utf-8\r\n");
return Encoding.UTF8.GetBytes(sb.ToString());
}
}
     第五步——DataConnection类中接收响应头并且处理静态页面:
//根据请求的路径,判断是静态页面还是动态页面
void Judge(string url)
{
//.htm
string ext = Path.GetExtension(url).TrimStart('.');
switch (ext.ToLower())
{
case "htm":
case "html":
case "jpg":
case "js":
case "gif":
case "png":
//处理静态页面 并作出响应
ProcessStaticPage(url);
break;
case "aspx": //jsp php
//处理动态页面
ProcessDyPage(url);
break;
default:
break;
}
}

/// <summary>
/// 根据路径找到静态页面,并作出响应
/// 1 获得响应头
/// </summary>
/// <param name="url"></param>
void ProcessStaticPage(string url)
{
// /1.htm
//1、根据相对路径生成绝对路径
//1.1、获取exe所在文件夹的路径
string exePath = AppDomain.CurrentDomain.BaseDirectory;
//1.2、请求文件的绝对路径
string path = exePath + "Web\\" + url;
//2、判断文件是否存在,读取静态页面
if (File.Exists(path))
{
using (FileStream fs = new FileStream(path, FileMode.Open))
{
//2.1、读取文件
byte[] buffer = new byte[fs.Length];
fs.Read(buffer, 0, buffer.Length);
//2.2、生成响应头
Response response = new Response(200, buffer.Length, url);
//注意:send没有立即发送,会等待一个非常短的时间,等待是否还有send,如果有,一起发送过去。
//2.3、发送响应头
connSocket.Send(response.GetResponseHeaders());
//2.4、发送响应体
connSocket.Send(buffer);
//2.5、关闭短连接
connSocket.Close();
del("关闭连接");
}
}
else
{
//404文件不存在
}
}
     第六步——DataConnection类中处理动态页面:
//处理动态页面
void ProcessDyPage(string url)
{
NewsList list = new NewsList();
//响应体
byte[] buffer = list.ProcessRequest();
//响应头
Response res = new Response(200, buffer.Length, url);
//发送响应头
connSocket.Send(res.GetResponseHeaders());
//发送响应体
connSocket.Send(buffer);
//关闭短连接
connSocket.Close();
del("关闭连接了");
}
此方法,只要请求的的页面是以.aspx结尾的,不管什么名字,都可访问得到。
因为是根据后缀名来找到页面。

     第七步——DataConnection类中处理动态页面完善:

//处理动态页面
void ProcessDyPage(string url)
{
//文件名(类名)
string fileName = Path.GetFileNameWithoutExtension(url);

#region 第一种处理动态页面方式
定义一个接口
//IHttpHandler handler = null;
判断
//if (fileName.ToLower() == "newslist")
//{
// //NewsList list = new NewsList();
// //去实现一个接口
// handler = new NewsList();
//}
//else if (fileName.ToLower() == "newsdetails")
//{
// //NewsDetails details = new NewsDetails();
// //去实现一个接口
// handler = new NewsDetails();
//}
//if (handler != null)
//{
// //响应体
// byte[] buffer = handler.ProcessRequest();
// //响应头
// Response res = new Response(200, buffer.Length, url);
// //发送响应头
// connSocket.Send(res.GetResponseHeaders());
// //发送响应体
// connSocket.Send(buffer);
// //关闭短连接
// connSocket.Close();
// del("关闭连接了");
//}
//else
//{
// //404 找不到页面
//}
#endregion

#region 第二种处理动态页面的方式
//通过反射创建负责请求类的对象,并且放到接口中
//根据字符串(类的全名称:命名空间.类名)创建对应类的对象
//命名空间
string nameSpace =
System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Namespace;
//类的全名称
string fullName = nameSpace + "." + fileName;
//通过反射创建类的对象
IHttpHandler handler = System.
Reflection.Assembly.GetExecutingAssembly().CreateInstance(fullName, true) as IHttpHandler;

if (handler != null)
{
//生成响应体
byte[] buffer = handler.ProcessRequest();
//响应头
Response res = new Response(200, buffer.Length, url);
//发送响应头
connSocket.Send(res.GetResponseHeaders());
//发送响应体
connSocket.Send(buffer);
//关闭短连接
connSocket.Close();
del("关闭连接了");
}
else
{
//404文件不存在
}
#endregion
}
准备一个接口IHttpHandler:
/// <summary>
/// 让实现了接口的类,能够处理浏览器的请求
/// </summary>
interface IHttpHandler
{
//处理请求并做出响应
byte[] ProcessRequest();
}

     附上代码整个程序

     备注:写于2013年9月25日

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值