自己动手,开发轻量级,高性能http服务器。

前言 http协议是互联网上使用最广泛的通讯协议了。web通讯也是基于http协议;对应c#开发者来说,asp.net core是最新的开发web应用平台。由于最近要开发一套人脸识别系统,对通讯效率的要求很高。虽然.net core对http处理很优化了,但是我决定开发一个轻量级http服务器;不求功能多强大,只求能满足需求,性能优越。本文以c#开发windows下http服务器为例。

  经过多年的完善、优化,我积累了一个非常高效的网络库(参见我的文章:高性能通讯库)。以此库为基础,开发一套轻量级的http服务器难度并不大。我花了两天的时间完成http服务器开发,并做了测试。同时与asp.net core处理效率做了对比,结果出乎意料。我的服务器性能是asp.net core的10倍。对于此结果,一开始我也是不相信,经过多次反复测试,事实却是如此。此结果并不能说明我写的服务器优于asp.net core,只是说明一个道理:合适的就是最好,高大上的东西并不是最好的。

1 HTTP协议特点

HTTP协议是基于TCP/IP之上的文本交换协议。对于开发者而言,也属于socket通讯处理范畴。只是http协议是请求应答模式,一次请求处理完成,则立即断开。http这种特点对sokcet通讯提出几个要求:

a) 能迅速接受TCP连接请求。TCP是面向连接的,在建立连接时,需要三次握手。这就要求socket处理accept事件要迅速,要能短时间处理大量连接请求。

b) 服务端必须采用异步通讯模式。对windows而言,底层通讯就要采取IOCP,这样才能应付成千上万的socket请求。

c) 快速的处理读取数据。tcp是流传输协议,而http传输的是文本协议;客户端向服务端发送的数据,服务端可能需要读取多次,服务端需要快速判断数据是否读取完毕。

以上几点只是处理http必须要考虑的问题,如果需要进一步优化,必须根据自身的业务特点来处理。

 2 快速接受客户端的连接请求

采用异步Accept接受客户端请求。这样的好处是:可以同时投递多个连接请求。当有大量客户端请求时,能快速建立连接。

异步连接请求代码如下:

   public bool StartAccept()
        {
            SocketAsyncEventArgs acceptEventArgs = new SocketAsyncEventArgs();
            acceptEventArgs.Completed += AcceptEventArg_Completed;

            bool willRaiseEvent = listenSocket.AcceptAsync(acceptEventArgs);
            Interlocked.Increment(ref _acceptAsyncCount);

            if (!willRaiseEvent)
            {
                Interlocked.Decrement(ref _acceptAsyncCount);
                _acceptEvent.Set();
                acceptEventArgs.Completed -= AcceptEventArg_Completed;
                ProcessAccept(acceptEventArgs);
            }
            return true;
        }

可以设置同时投递的个数,比如此值为10。当异步连接投递个数小于10时,立马再次增加投递。有一个线程专门负责投递。

_acceptAsyncCount记录当前正在投递的个数,MaxAcceptInPool表示同时投递的个数;一旦_acceptAsyncCount小于MaxAcceptInPool,立即增加一次投递。

 private void DealNewAccept()
        {
            try
            {
                if (_acceptAsyncCount <= MaxAcceptInPool)
                {
                    StartAccept();
                }
            }
            catch (Exception ex)
            {
                _log.LogException(0, "DealNewAccept 异常", ex);
            }
        }

3 快速分析从客户端收到的数据

比如客户端发送1M数据到服务端,服务端收到1M数据,需要读取的次数是不确定的。怎么样才能知道数据是否读取完?

这个细节处理不好,会严重影响服务器的性能。毕竟服务器要对大量这样的数据进行分析。

http包头举例

POST / HTTP/1.1
Accept: */*
Content-Type: application/x-www-from-urlencoded
Host: www.163.com
Content-Length: 7
Connection: Keep-Alivebody

分析读取数据,常规、直观的处理方式如下:

1) 将收到的多个buffer合并成一个buffer。如果读取10次才完成,则需要合并9次。

2) 将buffer数据转成文本。

3) 找到文本中的http包头结束标识("\r\n\r\n") 。

4) 找到Content-Length,根据此值判断是否接收完成。

采用上述处理方法,将严重影响处理性能。必须另辟蹊径,采用更优化的处理方法。

优化后的处理思路

1)多缓冲处理

基本思路是:收到所有的buffer之前,不进行buffer合并。将缓冲存放在List<byte[]> listBuffer中。通过遍历listBuffer来查找http包头结束标识,来判断是否接收完成。

类BufferManage负责管理buffer。

 public class BufferManage
    {
        List<byte[]> _listBuffer = new List<byte[]>();

        public void AddBuffer(byte[] buffer)
        {
            _listBuffer.Add(buffer);
        }

        public bool FindBuffer(byte[] destBuffer, out int index)
        {
            index = -1;
            int flagIndex = 0;

            int count = 0;
            foreach (byte[] buffer in _listBuffer)
            {
                foreach (byte ch in buffer)
                {
                    count++;
                    if (ch == destBuffer[flagIndex])
                    {
                        flagIndex++;
                    }
                    else
                    {
                        flagIndex = 0;
                    }

                    if (flagIndex >= destBuffer.Length)
                    {
                        index = count;
                        return true;
                    }
                }
            }

            return false;
        }

        public int TotalByteLength
        {
            get
            {
                int count = 0;
                foreach (byte[] item in _listBuffer)
                {
                    count += item.Length;
                }
                return count;
            }
        }

        public byte[] GetAllByte()
        {
            if (_listBuffer.Count == 0)
                return new byte[0];
            if (_listBuffer.Count == 1)
                return _listBuffer[0];

            int byteLen = 0;
            _listBuffer.ForEach(o => byteLen += o.Length);
            byte[] result = new byte[byteLen];

            int index = 0;
            foreach (byte[] item in _listBuffer)
            {
                Buffer.BlockCopy(item, 0, result, index, item.Length);
                index += item.Length;
            }
            return result;
        }

        public byte[] GetSubBuffer(int start, int countTotal)
        {
            if (countTotal == 0)
                return new byte[0];

            byte[] result = new byte[countTotal];
            int countCopyed = 0;

            int indexOfBufferPool = 0;
            foreach (byte[] buffer in _listBuffer)
            {
                //找到起始复制点
                int indexOfItem = 0;
                if (indexOfBufferPool < start)
                {
                    int left = start - indexOfBufferPool;
                    if (buffer.Length <= left)
                    {
                        indexOfBufferPool += buffer.Length;
                        continue;
                    }
                    else
                    {
                        indexOfItem = left;
                        indexOfBufferPool = start;
                    }
                }

                //复制数据
                int dataLeft = buffer.Length - indexOfItem;
                int dataNeed = countTotal - countCopyed;
                if (dataNeed >= dataLeft)
                {
                    Buffer.BlockCopy(buffer, indexOfItem, result, countCopyed, dataLeft);
                    countCopyed += dataLeft;
                }
                else
                {
                    Buffer.BlockCopy(buffer, indexOfItem, result, countCopyed, dataNeed);
                    countCopyed += dataNeed;
                }
                if (countCopyed >= countTotal)
                {
                    Debug.Assert(countCopyed == countTotal);
                    return result;
                }
            }
            throw new Exception("没有足够的数据!");
            // return result;
        }
    }

类HttpReadParse借助BufferManage类,实现对http文本的解析。

  1   public class HttpReadParse
  2     {
  3
  4         BufferManage _bufferManage = new BufferManage();
  5
  6         public void AddBuffer(byte[] buffer)
  7         {
  8             _bufferManage.AddBuffer(buffer);
  9         }
 10
 11         public int HeaderByteCount { get; private set; } = -1;
 12
 13         string _httpHeaderText = string.Empty;
 14         public string HttpHeaderText
 15         {
 16             get
 17             {
 18                 if (_httpHeaderText != string.Empty)
 19                     return _httpHeaderText;
 20
 21                 if (!IsHttpHeadOver)
 22                     return _httpHeaderText;
 23
 24                 byte[] buffer = _bufferManage.GetSubBuffer(0, HeaderByteCount);
 25                 _httpHeaderText = Encoding.UTF8.GetString(buffer);
 26                 return _httpHeaderText;
 27             }
 28         }
 29
 30         string _httpHeaderFirstLine = string.Empty;
 31         public string HttpHeaderFirstLine
 32         {
 33             get
 34             {
 35                 if (_httpHeaderFirstLine != string.Empty)
 36                     return _httpHeaderFirstLine;
 37
 38                 if (HttpHeaderText == string.Empty)
 39                     return string.Empty;
 40                 int index = HttpHeaderText.IndexOf(HttpConst.Flag_Return);
 41                 if (index < 0)
 42                     return string.Empty;
 43
 44                 _httpHeaderFirstLine = HttpHeaderText.Substring(0, index);
 45                 return _httpHeaderFirstLine;
 46             }
 47         }
 48
 49         public string HttpRequestUrl
 50         {
 51             get
 52             {
 53                 if (HttpHeaderFirstLine == string.Empty)
 54                     return string.Empty;
 55
 56                 string[] items = HttpHeaderFirstLine.Split(‘ ‘);
 57                 if (items.Length < 2)
 58                     return string.Empty;
 59
 60                 return items[1];
 61             }
 62         }
 63
 64         public bool IsHttpHeadOver
 65         {
 66             get
 67             {
 68                 if (HeaderByteCount > 0)
 69                     return true;
 70
 71                 byte[] headOverFlag = HttpConst.Flag_DoubleReturnByte;
 72
 73                 if (_bufferManage.FindBuffer(headOverFlag, out int count))
 74                 {
 75                     HeaderByteCount = count;
 76                     return true;
 77                 }
 78                 return false;
 79             }
 80         }
 81
 82         int _httpContentLen = -1;
 83         public int HttpContentLen
 84         {
 85             get
 86             {
 87                 if (_httpContentLen >= 0)
 88                     return _httpContentLen;
 89
 90                 if (HttpHeaderText == string.Empty)
 91                     return -1;
 92
 93                 int start = HttpHeaderText.IndexOf(HttpConst.Flag_HttpContentLenth);
 94                 if (start < 0) //http请求没有包体
 95                     return 0;
 96
 97                 start += HttpConst.Flag_HttpContentLenth.Length;
 98
 99                 int end = HttpHeaderText.IndexOf(HttpConst.Flag_Return, start);
100                 if (end < 0)
101                     return -1;
102
103                 string intValue = HttpHeaderText.Substring(start, end - start).Trim();
104                 if (int.TryParse(intValue, out _httpContentLen))
105                     return _httpContentLen;
106                 return -1;
107             }
108         }
109
110         public string HttpAllText
111         {
112             get
113             {
114                 byte[] textBytes = _bufferManage.GetAllByte();
115                 string text = Encoding.UTF8.GetString(textBytes);
116                 return text;
117             }
118         }
119
120         public int TotalByteLength => _bufferManage.TotalByteLength;
121
122         public bool IsReadEnd
123         {
124             get
125             {
126                 if (!IsHttpHeadOver)
127                     return false;
128
129                 if (HttpContentLen == -1)
130                     return false;
131
132                 int shouldLenth = HeaderByteCount + HttpContentLen;
133                 bool result = TotalByteLength >= shouldLenth;
134                 return result;
135             }
136         }
137
138         public List<HttpByteValueKey> GetBodyParamBuffer()
139         {
140             List<HttpByteValueKey> result = new List<HttpByteValueKey>();
141
142             if (HttpContentLen < 0)
143                 return result;
144             Debug.Assert(IsReadEnd);
145
146             if (HttpContentLen == 0)
147                 return result;
148
149             byte[] bodyBytes = _bufferManage.GetSubBuffer(HeaderByteCount, HttpContentLen);
150
151             //获取key value对应的byte
152             int start = 0;
153             int current = 0;
154             HttpByteValueKey item = null;
155             foreach (byte b in bodyBytes)
156             {
157                 if (item == null)
158                     item = new HttpByteValueKey();
159
160                 current++;
161                 if (b == ‘=‘)
162                 {
163                     byte[] buffer = new byte[current - start - 1];
164                     Buffer.BlockCopy(bodyBytes, start, buffer, 0, buffer.Length);
165                     item.Key = buffer;
166                     start = current;
167                 }
168                 else if (b == ‘&‘)
169                 {
170                     byte[] buffer = new byte[current - start - 1];
171                     Buffer.BlockCopy(bodyBytes, start, buffer, 0, buffer.Length);
172                     item.Value = buffer;
173                     start = current;
174                     result.Add(item);
175                     item = null;
176                 }
177             }
178
179             if (item != null && item.Key != null)
180             {
181                 byte[] buffer = new byte[bodyBytes.Length - start];
182                 Buffer.BlockCopy(bodyBytes, start, buffer, 0, buffer.Length);
183                 item.Value = buffer;
184                 result.Add(item);
185             }
186
187             return result;
188         }
189
190         public string HttpBodyText
191         {
192             get
193             {
194                 if (HttpContentLen < 0)
195                     return string.Empty;
196                 Debug.Assert(IsReadEnd);
197
198                 if (HttpContentLen == 0)
199                     return string.Empty;
200
201                 byte[] bodyBytes = _bufferManage.GetSubBuffer(HeaderByteCount, HttpContentLen);
202                 string bodyString = Encoding.UTF8.GetString(bodyBytes);
203                 return bodyString;
204             }
205         }
206
207     }

4 性能测试

采用模拟客户端持续发送http请求测试,每个http请求包含两个图片。一次http请求大概发送70K数据。服务端解析数据后,立即发送应答。

注:所有测试都在本机,客户端无法模拟大量http请求,只能做简单压力测试。

1)本人所写的服务器,测试结果如下

每秒可发送300次请求,每秒发送数据25M,服务器cpu占有率为4%。

2)asp.net core 服务器性能测试

每秒发送30次请求,服务器cpu占有率为12%。

测试对比:本人开发的服务端处理速度为asp.net core的10倍,cpu占用为对方的三分之一。asp.net core处理慢,有可能实现了更多的功能;只是这些隐藏的功能,对我们也没用。

后记: 如果没有开发经验,没有清晰的处理思路,开发一个高效的http服务器还有很困难的。本人也一直以来都是采用asp.net core作为http服务器。因为工作中需要高效的http服务器,就尝试写一个。不可否认,asp.net core各方面肯定优化的很好;但是,asp.net core 提供的某些功能是多余的。如果化繁为简,根据业务特点开发,性能未必不能更优。

原文地址:https://www.cnblogs.com/yuanchenhui/p/httpserver.html

时间: 2024-10-11 12:02:35

自己动手,开发轻量级,高性能http服务器。的相关文章

自己动手开发简易的Web服务器

使用python实现一个简易版的web服务器,旨在了解web服务器的工作原理,及了解HTTP协议.没有涉及多线程处理,并发之类的内容,以后再专门另外研究.首先上代码,稍后再作讲解. # coding=utf-8 import socket class httpd(object): def __init__(self,host,port): self.host = host self.port = port def parse_info(self,data): global _ENV _ENV =

谈谈如何使用Netty开发实现高性能的RPC服务器

RPC(Remote Procedure Call Protocol)远程过程调用协议,它是一种通过网络,从远程计算机程序上请求服务,而不必了解底层网络技术的协议.说的再直白一点,就是客户端在不必知道调用细节的前提之下,调用远程计算机上运行的某个对象,使用起来就像调用本地的对象一样.目前典型的RPC实现框架有:Thrift(facebook开源).Dubbo(alibaba开源)等等.RPC框架针对网络协议.网络I/O模型的封装是透明的,对于调用的客户端而言,它就认为自己在调用本地的一个对象.至

Netty开发实现高性能的RPC服务器

Netty开发实现高性能的RPC服务器 RPC(Remote Procedure Call Protocol)远程过程调用协议,它是一种通过网络,从远程计算机程序上请求服务,而不必了解底层网络技术的协议.说的再直白一点,就是客户端在不必知道调用细节的前提之下,调用远程计算机上运行的某个对象,使用起来就像调用本地的对象一样.目前典型的RPC实现框架有:Thrift(facebook开源).Dubbo(alibaba开源)等等.RPC框架针对网络协议.网络I/O模型的封装是透明的,对于调用的客户端而

Nginx:轻量级高性能的Web服务器

Nginx ("engine x") 是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP服务器.Nginx是由Igor Sysoev为俄罗斯访问量第二的Rambler.ru站点开发的,第一个公开版本0.1.0发布于2004年10月4日.其将源代码以类BSD许可证的形式发布,因它的稳定性.丰富的功能集.示例配置文件和低系统资源的消耗而闻名.2011年6月1日,nginx 1.0.4发布.Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/

&lt;摘录&gt;详谈高性能TCP服务器的开发

对于开发一款高性能服务器程序,广大服务器开发人员在一直为之奋斗和努力.其中一个影响服务器的重要瓶颈就是服务器的网络处理模块.如果一款服务器程序不能及时的处理用户的数据.则服务器的上层业务逻辑再高效也是徒劳.所以一个服务器程序的网络处理能力直接影响到整个服务器的性能, 本文主要介绍在windows平台下开发高性能的网络处理模块以及自己在设计开发服务器网络模块遇到的一些问题和开发心得.本篇主要介绍TCP服务器的设计, 下一篇将主要介绍UDP服务器的设计. 众所周知, 对于服务器来说windows下网

高性能Web服务器Nginx

高性能Web服务器Nginx介绍 Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个BSD-like 协议下发行.其特点是占有内存少,并发能力强,事实上nginx的并发能力确实在同类型的网页服务器中表现较好,可以运行在UNIX.GUN/LINUX.BSD.MAC OS X以及Microsoft Windows等操作系统中,中国大陆使用nginx网站用户有:百度.京东.新浪.网易.腾讯.淘宝等. Nginx的功能 Nginx的模块从功能上分为

高性能Web服务器Nginx的配置与部署研究(1)Nginx简介及入门示例

概述 从这篇博文起,将带领读者们一起领略Nginx的强大. Nginx 是做什么用的?我相信很多朋友都已经使用过,如果你没有,那么你一定知道以下这些名称之一:Apache,Lighttpd,Tomcat,Jetty. 它们占据了目前Web服务器的几乎全部江山,其中 Apache 是知名度最高的,同时也是最为重量级的.Lighttpd.Tomcat 和 Jetty 相对轻量级,其中 Jetty.Tomcat 多用于作为Java服务器容器. Nginx 是一个基于 BSD-like 协议.开源.高性

高性能Web服务器Nginx的配置与部署研究系列(1)-- 入门 hello work

简介: Nginx 是一个基于 BSD-like 协议.开源.高性能.轻量级的HTTP服务器.反向代理服务器以及电子邮件(SMTP.POP3.IMAP)服务器.Nginx 是由一个俄罗斯的名叫“Igor Sysoev”的软件工程师开发的,最初用于 Rambler.ru 网站(该网站在俄罗斯国内访问量排名第二) 例子:hello work 1:配置清单: worker_processes  4; events { worker_connections  1024; } http { server

《Nginx高性能Web服务器》系列分享专栏

<Nginx高性能Web服务器>系列分享专栏 [作者:Poechant] Nginx是目前最流行的基于BSD-like协议.轻量级.高性能的HTTP服务器.反向代理服务器和电子邮件(SMTP/POP3/IMAP)服务器.CSDN的Nginx专栏引领大家Step by Step地一起领略当今最强大高性能的Web服务器. <Nginx高性能Web服务器>已整理成PDF文档,点击可直接下载至本地查阅https://www.webfalse.com/read/203778.html 文章