博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
自己动手,开发轻量级,高性能http服务器。
阅读量:5031 次
发布时间:2019-06-12

本文共 13439 字,大约阅读时间需要 44 分钟。

前言 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.1Accept: */*Content-Type: application/x-www-from-urlencodedHost: www.163.comContent-Length: 7Connection: Keep-Alive body

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

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
_listBuffer = new List
(); 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 HttpAllText111         {112             get113             {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 IsReadEnd123         {124             get125             {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
GetBodyParamBuffer()139 {140 List
result = new List
();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对应的byte152 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 HttpBodyText191 {192 get193 {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

你可能感兴趣的文章
生成指定位数随机数的方法
查看>>
java的垃圾回收
查看>>
Essential C++学习笔记
查看>>
python+selenium进行简单验证码获取
查看>>
where,having与 group by连用的区别
查看>>
【MySQL】MySQL锁和隔离级别浅析二 之 INSERT
查看>>
Oracle T4-2 使用ILOM CLI升级Firmware
查看>>
4.14上午
查看>>
数据分析 -- 白话一下什么是决策树模型(转载)
查看>>
Java SPI机制原理和使用场景
查看>>
web前端java script学习2017.7.18
查看>>
删除TXPlatform
查看>>
LaTex:图片排版
查看>>
并发访问超时的问题可能性(引用)
查看>>
中小团队基于Docker的Devops实践
查看>>
利用python打开摄像头并保存
查看>>
System函数的使用说明
查看>>
Selenium-测试对象操作之:获取浏览器滚动条滚动距离
查看>>
Linux下MySQL数据库安装与配置
查看>>
Extjs String转Json
查看>>