有了以下的知识:
想必开发一个小型服务器以不是问题了,功能补复杂,能够响应客户端浏览器的请求,并根据请求文件的类型返回响应的信息,如能处理静态页面、图片、样式、脚本、动态页面等。
回顾
由于客户端和服务端的通信是通过Socket通信,且它们通信的“语言”是基于Http1.1协议。根据这个线索,我们完全可以自己开发服务器软件,暂且叫他Melodies Server,当然这是一个很简单的样例,和真正的服务器还是有差距的,好,我们进入正题,首先需要了解以下几个知识点:
- 客户端和服务端是由Socket进行通信,在服务器端需要有监听请求的套接字,他绑定在某个端口号上,如果发现有请求过来,socket.Accept()产生一个套接字和客户端进行通信。
- 客户端发送的请求(报文)交给服务器软件分析,判断是否为静态页面、图片还是动态aspx文件,若是静态文件能直接返回。
- 处理动态页面稍稍麻烦,需要反射创建页面类(原因详见ASP.NET那点不为人知的事(二))
开启服务
public
partial
class
WebServerForm : Form
{
public
WebServerForm()
{
InitializeComponent();
TextBox.CheckForIllegalCrossThreadCalls =
false
;
}
private
Socket socketWatch;
//负责监听浏览器连接请求的套接字
private
Thread threadWatch;
//负责循环调用Socket.Accept 监听线程
private
void
btnStartServer_Click(
object
sender, EventArgs e)
{
socketWatch=
new
Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
IPAddress address = IPAddress.Parse(txtIPAddress.Text.Trim());
IPEndPoint endPoint=
new
IPEndPoint(address,
int
.Parse(txtPort.Text.Trim()));
socketWatch.Bind(endPoint);
socketWatch.Listen(10);
threadWatch =
new
Thread(WatchConnect);
threadWatch.IsBackground =
true
;
threadWatch.Start();
}
private
bool
isWatch =
true
;
//Dictionary<>
void
WatchConnect()
{
while
(isWatch)
{
Socket socketConnection=socketWatch.Accept();
ShowMsg(
"浏览器:"
+socketConnection.RemoteEndPoint.ToString()+
",连接成功*********************"
);
ConnectionClient connectionClient =
new
ConnectionClient(socketConnection, ShowMsg);
}
}
void
ShowMsg(
string
msg)
{
txtLog.AppendText(msg+
"\r\n"
);
}
}
|
分析报文,处理请求
- 在异步线程创建的与客户端通信的Socket,它的主要职责就是分析保文:
/// <summary>
/// 与客户端连接通信类(包含一个与客户端通信的套接字和通信线程)
/// </summary>
public
class
ConnectionClient
{
private
Socket socketMsg;
//与客户端通信套接字
private
Thread threadMsg;
//通信线程
private
DGShowMsg dgShowMsg;
//负责向主窗体文本框显示消息的委托
public
ConnectionClient(Socket socket,DGShowMsg dgShowMsg)
{
this
.socketMsg = socket;
this
.dgShowMsg = dgShowMsg;
//负责启动一个接受客户端浏览器请求报文的线程
threadMsg =
new
Thread(ReceiveMsg);
threadMsg.IsBackground =
true
;
threadMsg.Start();
}
private
bool
isRec =
true
;
void
ReceiveMsg()
{
while
(isRec)
{
byte
[] arrMsg=
new
byte
[1024*1024*3];
//接受对应客户端发送过来的请求报文
int
length = socketMsg.Receive(arrMsg);
string
strMsg = System.Text.Encoding.UTF8.GetString(arrMsg, 0, length);
dgShowMsg(strMsg);
//处理报文
string
[] arrStr = strMsg.Replace(
"\r\n"
,
"韘"
).Split(
'韘'
);
string
[] firstRow=arrStr[0].Split(
' '
);
string
requestFile = firstRow[1];
ExcuteRequest(requestFile);
//todo:长连接多少时间
}
}
private
void
ExcuteRequest(
string
requestFile)
{
//获得被请求页面的后缀名
string
fileExtension = System.IO.Path.GetExtension(requestFile);
if
(!
string
.IsNullOrEmpty(fileExtension))
{
switch
(fileExtension.ToLower())
{
case
".html"
:
case
".htm"
:
case
".css"
:
case
".js"
:
ExcuteStaticPage(requestFile,fileExtension);
break
;
case
".jpg"
:
ExcuteImg(requestFile,fileExtension);
break
;
case
".aspx"
:
ExcuteDymPage(requestFile,fileExtension);
break
;
}
}
}
|
- 针对不同的请求执行不同的操作,其中静态页面、css、js、图片处理操作一样,都是属性静态文件,直接返回字节流:
/// <summary>
/// 处理静态页面,直接输出
/// </summary>
private
void
ExcuteStaticPage(
string
requestPath,
string
fileExtension)
{
StringBuilder sb=
new
StringBuilder();
//获得请求文件的文件夹的物理路径
string
dataDir = AppDomain.CurrentDomain.BaseDirectory;
if
(dataDir.EndsWith(
@"\bin\Debug\"
) || dataDir.EndsWith(
@"\bin\Release\"
))
{
dataDir = System.IO.Directory.GetParent(dataDir).Parent.Parent.FullName;
}
string
phyPath = dataDir + requestPath;
//读取静态页面内容
string
fileContent = System.IO.File.ReadAllText(phyPath);
//获得响应体字节数组
byte
[] fileArr = System.Text.Encoding.UTF8.GetBytes(fileContent);
//获得响应报文头:
string
responseHeader = GetResponseHeader(fileArr.Length, fileExtension);
byte
[] arrHead = System.Text.Encoding.UTF8.GetBytes(responseHeader);
//发送响应报文头回浏览器
socketMsg.Send(arrHead);
//发送响应报文体回浏览器
//todo:sleep 1分钟会怎样
socketMsg.Send(fileArr);
}
/// <summary>
/// 处理图片
/// </summary>
/// <param name="requestPath"></param>
/// <param name="extentionName"></param>
private
void
ExcuteImg(
string
requestPath,
string
extentionName)
{
//获得请求文件的文件夹的物理路径
string
dataDir = AppDomain.CurrentDomain.BaseDirectory;
if
(dataDir.EndsWith(
@"\bin\Debug\"
) || dataDir.EndsWith(
@"\bin\Release"
))
{
dataDir = System.IO.Directory.GetParent(dataDir).Parent.Parent.FullName;
}
//获得请求文件的物理路径(绝对路径)
string
phyPath = dataDir + requestPath;
int
imgLength;
byte
[] fileArr;
//读取图片内容
using
(FileStream fs =
new
FileStream(phyPath, FileMode.Open))
{
fileArr =
new
byte
[fs.Length];
imgLength = fs.Read(fileArr, 0, fileArr.Length);
//获得响应报文头
string
responseHeader = GetResponseHeader(imgLength, extentionName);
byte
[] arrHeader = System.Text.Encoding.UTF8.GetBytes(responseHeader);
socketMsg.Send(arrHeader);
socketMsg.Send(fileArr, imgLength, SocketFlags.None);
}
}
/// <summary>
/// 得到响应头信息
/// </summary>
/// <param name="contentLength"></param>
/// <param name="fileExtentionName"></param>
/// <returns></returns>
private
string
GetResponseHeader(
int
contentLength,
string
fileExtentionName)
{
StringBuilder sbHeader=
new
StringBuilder();
sbHeader.Append(
"HTTP/1.1 200 OK\r\n"
);
sbHeader.Append(
"Content-Length: "
+contentLength+
"\r\n"
);
sbHeader.Append(
"Content-Type:"
+ GetResponseHeadContentType(fileExtentionName) +
";charset=utf-8\r\n\r\n"
);
return
sbHeader.ToString();
}
/// <summary>
/// 根据后缀名获取响应保文中的内容类型
/// </summary>
/// <param name="fileExtentionName"></param>
/// <returns></returns>
private
string
GetResponseHeadContentType(
string
fileExtentionName)
{
switch
(fileExtentionName.ToLower())
{
case
".html"
:
case
".htm"
:
case
".aspx"
:
return
"text/html"
;
break
;
case
".css"
:
return
"text/plain"
;
break
;
case
".js"
:
return
"text/javascript"
;
break
;
case
".jpg"
:
return
"image/JPEG"
;
case
".gif"
:
return
"image/GIF"
;
break
;
default
:
return
"text/html"
;
break
;
}
}
|
- 同样,针对动态页面反射创建其页面类,注意记得让其实现IHttpHandler接口
- 创建一个页面类View
public
class
View:IHttpHandler
{
public
string
ProcessRequest()
{
string
dataDir = AppDomain.CurrentDomain.BaseDirectory;
//获得模板物理路径
if
(dataDir.EndsWith(
@"\bin\Debug\"
) || dataDir.EndsWith(
@"\bin\Release"
))
{
dataDir = System.IO.Directory.GetParent(dataDir).Parent.Parent.FullName;
}
string
phyPath = dataDir +
"/model.htm"
;
string
modelContent=System.IO.File.ReadAllText(phyPath);
modelContent = modelContent.Replace(
"@Title"
,
"动态页面"
).Replace(
"@Content"
,
"反射创建页面类"
);
return
modelContent;
}
}
|
- 反射View,调用其ProcessRequest方法执行服务端代码
/// <summary>
/// 反射创建动态页面对象
/// </summary>
/// <param name="requestFile"></param>
/// <param name="extentionName"></param>
private
void
ExcuteDymPage(
string
requestFile,
string
extentionName)
{
string
pageClassName = System.IO.Path.GetFileNameWithoutExtension(requestFile);
string
assemblyName = Assembly.GetExecutingAssembly().GetName().Name;
//获得页面类全名称
pageClassName = assemblyName +
"."
+ pageClassName;
//通过反射创建页面类对象
object
pageObj = Assembly.GetExecutingAssembly().CreateInstance(pageClassName);
IHttpHandler page = pageObj
as
IHttpHandler;
byte
[] fileArr=
null
;
if
(page!=
null
)
{
string
strHtml=page.ProcessRequest();
fileArr= System.Text.Encoding.UTF8.GetBytes(strHtml);
}
//获得响应报文头
string
responseHeader = GetResponseHeader(fileArr.Length, extentionName);
byte
[] arrHeader = System.Text.Encoding.UTF8.GetBytes(responseHeader);
socketMsg.Send(arrHeader);
socketMsg.Send(fileArr);
}
|
总结
至此,一个小型的服务器软件就构建好了,赶紧测试一下呵呵。
标签:
ASP.NET本质论
本文转自木宛城主博客园博客,原文链接:http://www.cnblogs.com/OceanEyes/archive/2013/03/23/aspnet_melodies_server.html,如需转载请自行联系原作者