DELPHI高性能大容量SOCKET并发(九):稳定性问题解决
来源:互联网 发布:疯狂追星 知乎 编辑:程序博客网 时间:2024/05/01 02:36
IOCP接收缓存导致的内存错乱
在用IOCP控件写了一个ERP服务器后,服务器会发生运行3天后,出现莫名的内存错误,用FastMM检测,是本没有内存错误的地方,而且内存错误出现的地方也不固定。这是一个不可重现的Bug,后续通过打日志把错误范围缩小后发现,每次出现内存错误之前都是由于有链接断开释放,因此就加了日志逐步定位到是TSocketHandle释放引起的,具体原因是:在IOCP中,每个Socket连接需要投递一个接收请求,并给出数据存放内存,原来是销毁TSocketHandle的同时,销毁投递接收请求的缓存,这样有可能对象销毁后,IOCP返回一个异步接收消息,会导致写入到已销毁的接收缓存,造成内存被重写,导致内存错误。
解决办法,是用锁和对象分离相同的机制,把接收缓存和对象分离,在释放对象的时候不释放接收缓存,等待超过30分钟后,重新使用这个锁和接受缓存,这样做即可以解决内存错乱问题,也起到了锁和接收缓存的池化处理。
具体代码处理:
投递请求缓存和对象分开,采用是锁和对象分离相同的机制。
{* 客户端对象和锁 *} TClientSocket = record Lock: TCriticalSection; SocketHandle: TSocketHandle; IocpRecv: TIocpRecord; //投递请求结构体 IdleDT: TDateTime; end; PClientSocket = ^TClientSocket;在释放TSocketHandle的时候,只释放对象,投递请求缓存不释放,和锁一起保留,加入到空闲列表中。procedure TSocketHandles.Delete(const AIndex: Integer);var ClientSocket: PClientSocket;begin ClientSocket := FList[AIndex]; ClientSocket.Lock.Enter; try ClientSocket.SocketHandle.Free; ClientSocket.SocketHandle := nil; finally ClientSocket.Lock.Leave; end; FList.Delete(AIndex); ClientSocket.IdleDT := Now; FIdleList.Add(ClientSocket);end;在加入对象的时候,检测空闲列表是否有超过30分钟没使用的,如果有则重复利用。function TSocketHandles.Add(ASocketHandle: TSocketHandle): Integer;var ClientSocket, IdleClientSocket: PClientSocket; i: Integer;begin ClientSocket := nil; for i := FIdleList.Count - 1 downto 0 do begin IdleClientSocket := FIdleList.Items[i]; if Abs(MinutesBetween(Now, IdleClientSocket.IdleDT)) > 30 then begin ClientSocket := IdleClientSocket; FIdleList.Delete(i); Break; end; end; if not Assigned(ClientSocket) then begin New(ClientSocket); ClientSocket.Lock := TCriticalSection.Create; ClientSocket.IocpRecv.WsaBuf.buf := GetMemory(MAX_IOCPBUFSIZE); ClientSocket.IocpRecv.WsaBuf.len := MAX_IOCPBUFSIZE; end; ClientSocket.SocketHandle := ASocketHandle; ClientSocket.IdleDT := Now; ASocketHandle.FLock := ClientSocket.Lock; ASocketHandle.FIocpRecv := @ClientSocket.IocpRecv; Result := FList.Add(ClientSocket);end;
CheckDisconnectedClient方法加锁及判断是否正在执行
原来检测释放断开连接的方法如下:
procedure TIocpServer.CheckDisconnectedClient;var i: Integer;begin FSocketHandles.Lock; try for i := FSocketHandles.Count - 1 downto 0 do begin if not FSocketHandles.Items[i].SocketHandle.Connected then begin FSocketHandles.Delete(i); end; end; finally FSocketHandles.UnLock; end;end;这个方法存在以下问题:1、对整个FSocketHandles加锁,FSocketHandles.Delete在释放的时候又加了一次锁,如果Delete加锁等待,则导致整个FSocketHandles被锁住,这时再加连接就会等待,造成IOCP无法接收连接,从而存在问题。
2、如果某个TScoketHandle执行很长时间,它的Connected属性为False,则FSocketHandles.Delete会锁住,造成和1相同的问题。
解决办法:
1、不对整个FSocketHandles加锁,我每次查找一个Connected为False的连接,避免一次加两个锁。
2、TSocketHandle增加一个属性标识是否正在执行中,在检测断开连接的时候如果正在执行中则跳过。
具体代码如下:
procedure TIocpServer.CheckDisconnectedClient;var iCount: Integer; ClientSocket: PClientSocket; function GetDisconnectSocket: PClientSocket; var i: Integer; begin Result := nil; FSocketHandles.Lock; try for i := FSocketHandles.Count - 1 downto 0 do begin if (not FSocketHandles.Items[i].SocketHandle.Connected) and (not FSocketHandles.Items[i].SocketHandle.Executing) then begin Result := FSocketHandles.Items[i]; Break; end; end; finally FSocketHandles.UnLock; end; end;begin ClientSocket := GetDisconnectSocket; iCount := 0; while (ClientSocket <> nil) and (iCount < 1024 * 1024) do begin ClientSocket.Lock.Enter; try if Assigned(ClientSocket.SocketHandle) then FreeSocketHandle(ClientSocket.SocketHandle); finally ClientSocket.Lock.Leave; end; ClientSocket := GetDisconnectSocket; Inc(iCount); end;end;主要是使用GetDisconnectSocket来返回一个已经断开的连接。TSocketHandle.Executing的赋值只需要在下面方法中赋值即可,因为他执行的进入口和返回口。procedure TSocketHandle.ProcessIOComplete(AIocpRecord: PIocpRecord; const ACount: Cardinal);begin FExecuting := True; try case AIocpRecord.IocpOperate of ioNone: Exit; ioRead: //收到数据 begin FActiveTime := Now; ReceiveData(AIocpRecord.WsaBuf.buf, ACount); if FConnected then PreRecv; //投递请求 end; ioWrite: //发送数据完成,需要释放AIocpRecord的指针 begin FActiveTime := Now; FSendOverlapped.Release(AIocpRecord); end; ioStream: begin FActiveTime := Now; FSendOverlapped.Release(AIocpRecord); WriteStream; //继续发送流 end; end; finally FExecuting := False; end;end;
解决这两个稳定性问题后,IOCP支持的ERP服务器已经能支持7*24小时运行。一般当服务器出现稳定性问题后,日志就开始发挥作用,但是太多无用的日志不利于定位到问题点,太少的日志又无法定位,一般写日志的原则是在调用顺序关键点上加日志、继承关键点上加日志、数据流输入输出关键点上加日志;这样出现问题后,可以快速把问题定位到一段代码上,能帮助缩短解决问题的周期。如果出现一个不可重现BUG,能控制在3天解决,出现一个内存错乱BUG,能控制在半个月解决,哪你的日志辅助调试就是有效的。
V1版下载地址:http://download.csdn.net/detail/sqldebug_fan/4510076,需要资源10分,有稳定性问题,可以作为研究稳定性用;
V2版下载地址:http://download.csdn.net/detail/sqldebug_fan/5560185,不需要资源分,解决了稳定性问题和提高性能;免责声明:此代码只是为了演示IOCP编程,仅用于学习和研究,切勿用于商业用途。水平有限,错误在所难免,欢迎指正和指导。邮箱地址:fansheng_hx@163.com- DELPHI高性能大容量SOCKET并发(九):稳定性问题解决
- C#高性能大容量SOCKET并发(九):断点续传
- DELPHI高性能大容量SOCKET并发(八):断点续传
- DELPHI高性能大容量SOCKET并发(十):IOCP完成端口性能优化
- DELPHI高性能大容量SOCKET并发(一):IOCP完成端口例子介绍
- DELPHI高性能大容量SOCKET并发(二):IOCP完成端口控件封装
- DELPHI高性能大容量SOCKET并发(三):接收、发送、缓存
- DELPHI高性能大容量SOCKET并发(五):锁和对象分离
- DELPHI高性能大容量SOCKET并发(六):协议字符集
- DELPHI高性能大容量SOCKET并发(七):通讯协议
- DELPHI高性能大容量SOCKET并发(四):粘包、分包、解包
- DELPHI高性能大容量SOCKET并发(四):粘包、分包、解包
- Netty高性能大容量Socket并发
- DELPHI高性能大容量SOCKET并发:IOCP完成端口例子介绍
- Netty高性能大容量Socket并发 二
- Netty高性能大容量Socket并发 一
- Netty高性能大容量Socket并发(一):Netty性能测试
- 高性能大容量SOCKET并发(一):IOCP完成端口例子介绍
- Linux 内核配置机制(make menuconfig、Kconfig、makefile)讲解
- 变量~HLSL
- 玩转Google开源C++单元测试框架Google Test系列(gtest)之三 - 事件机制
- POJ The Fewest Coins
- 玩转Google开源C++单元测试框架Google Test系列(gtest)之四 - 参数化
- DELPHI高性能大容量SOCKET并发(九):稳定性问题解决
- Android 资源(resource)学习小结
- make menuconfig 执行流程分析
- 玩转Google开源C++单元测试框架Google Test系列(gtest)之五 - 死亡测试
- Linux source code Makefile分析
- DELPHI高性能大容量SOCKET并发(十):IOCP完成端口性能优化
- 如何带领好团队
- android中的OOM问题 解决原则
- 关于Hibernate的sql查询返回值的问题