zoukankan      html  css  js  c++  java
  • 使用beetle简单地实现高效的http基础服务

    之前的章节里已经讲述了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协议由两大部分组成,那就可以根据这制订相应的协议对象

    • 消息头
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    //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;
            }
        }
    }
    • 消息体
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    //消息体数据块
    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来处理,所以消息设计由多个数据块组件.

    • 消息适器
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    //消息适配器用于对象写入流转换
    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实现相关方法即可.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    /// <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服务并在浏览中访问

    组件提供基础的服务对象,只需要在继承指写对应的协议分析器即可,用起来也非常简单方便.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    class 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%左右的优化空间.

    下载代码

    可靠、高性能的Socket TCP通讯组件
    开源数据库访问组件
    c#组件设计交流群:47164588 
    c# socket :285021077
  • 相关阅读:
    C/C++中0xcccccccc...
    函数指针定义
    Visual C++的DLL
    RGB
    链接指示:extern "C"
    for_each用法
    漫画 | 夜深了,程序员的电脑却没关,发生了这样的故事…
    漫画 | 小公司卧薪尝胆三年,意外拿到美团offer
    Java 可变参数
    使用程序往Neo4j导入CSV报错
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2760815.html
Copyright © 2011-2022 走看看