之前的章节里已经讲述了Beetle对不同应用协议的扩展和处理,在这章会讲解Beetle实现一个比较通用的应用协议HTTP扩展.组件对于HTTP协 议的扩展也是一件非常简单的事情,同时也能得到组件出色的性能和稳定性所支持.不过在实现之前必须对HTTP协议组成部分有个大概的了解.HTTP协议主 要由两大部分组件分别是消息头和消息体,消息头是必须的有于描述获取相关内容和附带的一些属性如:GET /images/logo.gif HTTP/1.1,通过回车换行符来标记消息头结束.对于消息休则是可选的如果存在消息体必须在消息头里标识Content-Length.对于HTTP 更详细的内容可以查看http://zh.wikipedia.org/zh/%E8%B6%85%E6%96%87%E6%9C%AC%E4%BC%A0%E8%BE%93%E5%8D%8F%E8%AE%AE
接下就详细讲述Beetle实现HTTP服务的过程,具体内容如下:
- 制订对应的HTTP对象消息结构
- 制订HTTP协议分析器
- 实现一个HTTP服务并在浏览中访问
- 性能测试
- 总结
制订HTTP对象消息
既然HTTP协议由两大部分组成,那就可以根据这制订相应的协议对象
- 消息头
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849//http头描述
public
class
HttpHeader : HttpMessage
{
public
HttpHeader()
{
Headers =
new
Hashtable(8);
}
//消息头常量定义,详细参考http://en.wikipedia.org/wiki/List_of_HTTP_header_fields
#region headers
public
const
string
HTTP_ContentLength =
"Content-Length"
;
public
const
string
HTTP_Request_Accept =
"Accept"
;
public
const
string
HTTP_Request_AcceptCharset =
"Accept-Charset"
;
public
const
string
HTTP_Requst_AcceptEncoding =
"Accept-Encoding"
;
#endregion
//获取http头键值表
public
Hashtable Headers
{
get
;
set
;
}
//获取设置方法信息如GET /images/logo.gif HTTP/1.1或返回信息
public
string
MethodInfo {
get
;
set
; }
//获取或设置消息休长度
public
int
ContentLength
{
get
{
object
value = Headers[HTTP_ContentLength];
if
(value !=
null
)
return
int
.Parse(value.ToString().Trim());
return
0;
}
set
{
Headers[HTTP_ContentLength] = value.ToString();
}
}
public
string
this
[
string
key]
{
get
{
return
(
string
)Headers[key];
}
set
{
Headers[key] = value;
}
}
}
- 消息体
12345678910111213141516171819202122232425262728293031//消息体数据块
public
class
HttpDataSegment : HttpMessage
{
public
HttpDataSegment()
{
Data = HttpPackage.BufferPools.Pop();
Data.SetInfo(0, 0);
}
//当前块是否尾部
public
bool
Eof
{
get
;
set
;
}
//获取数据块内容
public
ByteArraySegment Data
{
get
;
set
;
}
//释放数据块对应的buffer
protected
override
void
OnDispose()
{
base
.OnDispose();
if
(Data !=
null
)
{
HttpPackage.BufferPools.Push(Data);
Data =
null
;
}
}
}
由于消息体有可能比较大,如果是几百MB的情况也不太可能用一个Buffer来处理,所以消息设计由多个数据块组件.
- 消息适器
123456789101112131415161718192021222324252627282930313233343536373839404142434445//消息适配器用于对象写入流转换
public
class
HttpAdater : IMessage
{
public
HttpMessage Message
{
get
;
set
;
}
//从流加载数据,由于直接有协议分析器来处理了所以不需要实现相关方法
public
void
Load(BufferReader reader)
{
throw
new
NotImplementedException();
}
//把http协议对象写入网络流
public
void
Save(BufferWriter writer)
{
if
(Message
is
HttpHeader)
{
OnSaveHeader(writer, Message
as
HttpHeader);
}
else
if
(Message
is
HttpDataSegment)
{
OnSaveDataSegment(writer, Message
as
HttpDataSegment);
}
else
{
}
Message.Dispose();
}
//写入消息头信息
private
void
OnSaveHeader(BufferWriter writer, HttpHeader header)
{
writer.WriteString(header.MethodInfo +
"\r\n"
);
foreach
(
object
key
in
header.Headers.Keys)
{
writer.WriteString(
string
.Format(
"{0}: {1}\r\n"
, key, header.Headers[key]));
}
writer.WriteString(
"\r\n"
);
}
//写入消息体信息
private
void
OnSaveDataSegment(BufferWriter writer, HttpDataSegment segment)
{
writer.Write(segment.Data.Array, segment.Data.Offset, segment.Data.Count);
}
}
制订HTTP协议分析器
组件对协议的支持并不需要修改组件核心代码,都是通过扩展的方式实现.只需要继承Package实现相关方法即可.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657/// <summary>
/// 网络数据导入方法
/// </summary>
/// <param name="data">接收数据流</param>
/// <param name="start">开始索引</param>
/// <param name="count">总长度</param>
public
override
void
Import(
byte
[] data,
int
start,
int
count)
{
int
index = 0;
if
(mHeader ==
null
)
{
//从池中获取头加载内存块
if
(mHeaderData ==
null
)
{
mHeaderData = BufferPools.Pop();
//初始化块内容
mHeaderData.SetInfo(0, 0);
}
//查询http头结束标记
index = ByteIndexOf(data, EofData, start, count);
if
(index >= 0)
{
//把http头数据流复制到当前分析缓冲区中
Buffer.BlockCopy(data, start, mHeaderData.Array, mHeaderData.Offset, index + 1);
mHeaderData.Count += index + 1;
start = index + 1;
count = count - index + 1;
//分析头信息
OnCreateHeader();
MessageArgs.Message = mHeader;
//触发消息接收事件
OnReceiveMessage(MessageArgs);
}
}
//如果存在接收内容
if
(ContentLength > 0)
{
//新建一个数据块消息
HttpDataSegment hds =
new
HttpDataSegment();
//把数据流复制到相应的数据块中
Buffer.BlockCopy(data, start, hds.Data.Array, 0, count);
hds.Data.SetInfo(0, count);
ContentLength -= count;
//如果获取数据流完整后设置为结束块
if
(ContentLength == 0)
hds.Eof =
true
;
MessageArgs.Message = hds;
//触发消息接收事件
OnReceiveMessage(MessageArgs);
}
//清除当前接收请求内容
if
(mHeader !=
null
&& ContentLength == 0)
{
DisposeHeaderData();
mHeader =
null
;
}
}
通过实现Import方法来处理协议数据分析,对于http头的拆分可以通过下载完整代码查看.
实现一个HTTP服务并在浏览中访问
组件提供基础的服务对象,只需要在继承指写对应的协议分析器即可,用起来也非常简单方便.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889class
Program:ServerBase<Beetle.Packages.HttpPackage>
{
static
void
Main(
string
[] args)
{
//初如化组件
TcpUtils.Setup(
"beetle"
);
Program server =
new
Program();
server.Listens = 1000;
//在所有IP的8088端口上监听服务
server.Open(8088);
Console.WriteLine(
"Beetle Http Server start@8088"
);
System.Threading.Thread.Sleep(-1);
}
protected
override
void
OnConnected(
object
sender, ChannelEventArgs e)
{
base
.OnConnected(sender, e);
e.Channel.EnabledSendCompeletedEvent =
true
;
e.Channel.SendMessageCompleted = (o, i) => {
HttpPackage.HttpAdater adapter = (HttpPackage.HttpAdater)i.Messages[i.Messages.Count - 1];
//消息发送完成后判断是否关闭对应的连接
if
(adapter.Message
is
HttpPackage.HttpHeader)
{
if
(((HttpPackage.HttpHeader)adapter.Message).ContentLength == 0 && e.Channel !=
null
)
e.Channel.Dispose();
}
else
if
(adapter.Message
is
HttpPackage.HttpDataSegment)
{
if
(((HttpPackage.HttpDataSegment)adapter.Message).Eof && e.Channel !=
null
)
e.Channel.Dispose();
}
};
}
//错误处理事件,可以获取协议分析和逻辑处理环节中出现的异常
protected
override
void
OnError(
object
sender, ChannelErrorEventArgs e)
{
base
.OnError(sender, e);
Console.WriteLine(
"{0} error:{1}"
, e.Channel.EndPoint, e.Exception.Message);
Console.WriteLine(e.Exception.StackTrace);
}
//连接释放过程
protected
override
void
OnDisposed(
object
sender, ChannelDisposedEventArgs e)
{
base
.OnDisposed(sender, e);
}
//消息接收处理事件
protected
override
void
OnMessageReceive(PacketRecieveMessagerArgs e)
{
base
.OnMessageReceive(e);
if
(e.Message
is
HttpPackage.HttpHeader)
{
OnRequestHeader(e.Channel, (HttpPackage.HttpHeader)e.Message);
}
else
if
(e.Message
is
HttpPackage.HttpDataSegment)
{
OnRequestSegment(e.Channel, (HttpPackage.HttpDataSegment)e.Message);
}
}
//得到请求头信息处理过程
private
void
OnRequestHeader(TcpChannel channel, Beetle.Packages.HttpPackage.HttpHeader header)
{
//Console.WriteLine(header.MethodInfo);
//foreach (object key in header.Headers.Keys)
//{
// Console.WriteLine("{0}:\t{1}", key, header[(string)key]);
//}
HttpPackage.HttpHeader response =
new
HttpPackage.HttpHeader();
HttpPackage.HttpDataSegment responsedata =
new
HttpPackage.HttpDataSegment();
responsedata.Data.Encoding(Resource1.BEETLE_HTTP_TEST, channel.Coding);
responsedata.Eof =
true
;
response.ContentLength = responsedata.Data.Count;
response.MethodInfo =
"HTTP/1.1 200 OK"
;
response[
"Cache-Control"
] =
"private"
;
response[
"Connection"
] =
"Close"
;
response[
"Content-Type"
] =
"text/html; charset=utf-8"
;
response[
"Server"
] =
"Beetle Http Server"
;
//发送应答头
channel.Send(response);
//发送应答数据
channel.Send(responsedata);
}
private
void
OnRequestSegment(TcpChannel channel, Beetle.Packages.HttpPackage.HttpDataSegment segment)
{
}
}
以上一个HTTP服务已经实现了,由于测试用所以当接收请求后并没有分析对应的请求信息,直接测试内容.通过浏览器查询如下:
性能测试
为作一个服务型的应用需要关注的是其性能和稳定性,下面通过AB工具对这个服务进行压力测试,并和IIS7获取一个静态页进行对比,测试内容是分别请求100W次
Beetle结果
IIS结果
总结
Beetle 可以很灵活地实现不同的应用协议支持,而你在扩展的过程只需要关心协议和逻辑上的工作,对于性能和稳定性Beetle可以给你做保障.由于Beetle是 纯c#实现,所以也可以说明.net的socket 处理能力还是很不错的,由于Beetle并不是针对http这种短连接应用设计,所以在这应用上并没有真正发挥出.net socket在这方面的能力.总的来说应该还有10%-20%左右的优化空间.