4 、结语 基于多线程处理的系统代价是比较大的,需要经常调用加/解锁方法lock()或Monitor.Enter(),需要经常创建处理线程等。从实际运行效果看,笔者的实现方案有较好的稳定性:2005年4月到5月间,在一个普通PC机器上连续运行30多天不出一点故障。同时,笔者采用了时序区间判重等算法,有效地提高了系统处理与响应速度。测试表明,在普通的PC机器(P4 2.0)上,可以做到0.5秒处理一个数据包,如果优化代码和服务器,还有较大的性能提升空间。 上面的代码是笔者实现的省级公路交通流量数据服务中心(DSC)项目中的接收服务器框架部分,整个系统还包括:数据转发交通部的转发服务器、数据远程查询客户端、综合报表数据处理系统、数据在线发布系统、系统运行监控系统等。 实际的接收服务器类及其辅助类超过3K行,整个系统则超过了60K。因为是早期实现的程序,难免有代码粗糙、方法欠妥的感觉,只有留待下个版本完善扩充了。由于与甲方有保密合同和版权保护等,不可能公开全部源代码,删减也有不当之处,读者发现时请不吝指正。下面是带详细注释的代码下载URL。 附注:笔者补充了有关数据包界限、间断、重叠等内容,请参考指正。 国庆假日的最后一天,用近9个小时写完了C# 实现的多线程异步Socket数据包接收器框架(包括删减代码的时间)。饭后散步回来再看,好家伙,有300多个Page Views了,超过笔者在codeproject上首日前几个小时的PV速度了。呵呵,如果发表在笔者原博客网上,估计就是自己反反复复修改记录的数十个PV了!终究是彼网牛人高手太多。 散步时仔细想想该文,发觉有三个Socket通信中关键与著名的问题没有讲到或没有讲清楚:
先补充异步BeginReceive()回调函数EndReceiveData()中的数据包分合函数ResolveBuffer()。
Code
/// <summary> /// 1) 报文界限字符为<>,其它为合法字符, /// 2) 按报文头、界限标志抽取报文,可能合并包文 /// 3) 如果一次收完数据,此时 DatagramBuffer 为空 /// 4) 否则转存到包文缓冲区 session.DatagramBuffer /// </summary> private void ResolveBuffer(TSession session,int receivedSize) { // 上次留下的报文缓冲区非空(注意:必然含有开始字符 <,空时不含 <) bool hasBeginChar= (session.DatagramBufferLength> 0); int packPos= 0;// ReceiveBuffer 缓冲区中包的开始位置 int packLen= 0;// 已经解析的接收缓冲区大小 byte dataByte= 0;// 缓冲区字节 int subIndex= 0;// 缓冲区下标 while (subIndex< receivedSize) { // 接收缓冲区数据,要与报文缓冲区 session.DatagramBuffer 同时考虑 dataByte = session.ReceiveBuffer[subIndex]; if (dataByte== TDatagram.BeginChar)// 是数据包的开始字符<,则前面的包文均要放弃 { // <前面有非空串(包括报文缓冲区),则前面是错包文,防止 AAA<A,1,A> 两个报文一次读现象 if (packLen> 0) { Interlocked.Increment(ref _datagramCount);// 前面有非空字符 Interlocked.Increment(ref _errorDatagramCount);// 一个错误包 this.OnDatagramError(); } session.ClearDatagramBuffer(); // 清空会话缓冲区,开始一个新包 packPos = subIndex;// 新包起点,即<所在位置 packLen =1; // 新包的长度(即<) hasBeginChar =true; // 新包有开始字符 } else if (dataByte == TDatagram.EndChar)// 数据包的结束字符 > { if (hasBeginChar)// 两个缓冲区中有开始字符< { ++packLen; // 长度包括结束字符> // >前面的为正确格式的包,则分析该包,并准备加入包队列 AnalyzeOneDatagram(session, packPos, packLen); packPos = subIndex+ 1;// 新包起点。注意:subIndex 在循环最后处 + 1 packLen =0; // 新包长度 } else // >前面没有开始字符,则认为结束字符>为一般字符,待后续的错误包处理 { ++packLen; // hasBeginChar = false; } } else // 非界限字符<>,就是是一般字符,长度 + 1,待解析包处理 { ++packLen; } ++subIndex;// 增加下标号 } // end while if (packLen> 0)// 剩下的待处理串,分两种情况 { // 剩下包文,已经包含首字符且不超长,转存到包文缓冲区中,待下次处理 if (hasBeginChar&& packLen + session.DatagramBufferLength<= _maxDatagramSize) { session.CopyToDatagramBuffer(packPos, packLen); } else // 不含首字符,或超长 { Interlocked.Increment(ref _datagramCount); Interlocked.Increment(ref _errorDatagramCount); this.OnDatagramError(); session.ClearDatagramBuffer(); // 丢弃全部数据 } } } 分析包文AnalyzeOneDatagram()函数代码补充如下:
Code
/// <summary> /// 具有<>格式的数据包加入到队列中 /// </summary> private void AnalyzeOneDatagram(TSession session,int packPos,int packLen) { if (packLen+ session.DatagramBufferLength> _maxDatagramSize)// 超过长度限制 { Interlocked.Increment(ref _datagramCount); Interlocked.Increment(ref _errorDatagramCount); this.OnDatagramError(); } else // 一个首尾字符相符的包,此时需要判断其类型 { Interlocked.Increment(ref _datagramCount); TDatagram datagram =new TDatagram(); if (!datagram.CheckDatagramKind())// 包格式错误(只能是短期BG、或长期SG包) { Interlocked.Increment(ref _datagramCount); Interlocked.Increment(ref _errorDatagramCount); this.OnDatagramError(); datagram = null; // 丢弃当前包 } else // 实时包、定期包,先解析数据,判断正误,并发回确认包 { datagram.ResolveDatagram(); if (true)// 正确的包才入包队列 { Interlocked.Increment(ref _datagramQueueCount); lock (_datagramQueue) { _datagramQueue.Enqueue(datagram); // 数据包入队列 } } else { Interlocked.Increment(ref _errorDatagramCount); this.OnDatagramError(); } } } session.ClearDatagramBuffer(); // 清包文缓冲区 } TSession的拷贝转存数据包文的方法CopyToDatagramBuffer()代码如下:
Code
/// <summary> /// 拷贝接收缓冲区的数据到数据缓冲区(即多次读一个包文) /// </summary> public void CopyToDatagramBuffer(int startPos,int packLen) { int datagramLen= 0; if (DatagramBuffer!= null) datagramLen= DatagramBuffer.Length; // 调整长度(DataBuffer 为 null 不会出错) Array.Resize(ref DatagramBuffer, datagramLen+ packLen); // 拷贝到数据就缓冲区 Array.Copy(ReceiveBuffer, startPos, DatagramBuffer, datagramLen, packLen); } 代码中注释比较详细了,下面指出其思路:
设计时考虑的另一个重要问题就是处理速度。如果自动观测站达到100个,此时5*60=300秒钟就有100个包,即每3秒种一个包,不存在处理速度慢问题。但是,真正耗时的是判断包是否重复!特别地,当设备故障时存在混乱上传数据包现象,此时将存在大量的重复包。笔者采用了所谓的区间判重算法,较好地解决了判重速度问题,使得系统具有很好的可伸缩性(分析算法的论文被EI核心版收录,呵呵,意外收获)。事实上,前年的交通部接收服务器还不具备该项功能,可能是太费时间了。 还有,就是在.NET Framework的托管CLR下,系统本身的响应速度如何?当时的确没有把握,认为只要稳定性和速度满足要求就行了。三年半运行情况表明,系统有良好的处理速度、很好的稳定性、满足了部省要求。 |
C# 实现的多线程异步Socket数据包接收器框架(4)
最新推荐文章于 2021-06-21 14:01:42 发布