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,能控制在半个月解决,哪你的日志辅助调试就是有效的。
下载地址:http://download.csdn.net/detail/sqldebug_fan/4510076
免责声明:此代码只是为了演示IOCP编程,仅用于学习和研究,切勿用于商业用途。水平有限,错误在所难免,欢迎指正和指导。邮箱地址:fansheng_hx@163.com
在物料清单采购中,用到excel上传文件解析功能,不过使用poi来解析,发现如果某个单元格为空,则使用poi的官网示例则会被忽略,导致某些非必填的单元格为空,而解析出来则认为不符合格式。找了半天,也没发现poi正确解析的示例和一些资料,只能自己查查excel的格式,然后再解析了。官网地址示例:http://poi.apache.org/spreadsheet/how-to.html#xssf_sax_api
那么我们就看看excel2007的格式了。
1. excel2007是使用xml格式来存储的,把一个excel文件后缀改为.zip,打开之后就直接可以看到一个excel文件对应的xml格式的文件了。
这里面有几部分
<!--[if !supportLists]-->1. 1 <!--[endif]-->对于docProps目录下 这里core是文件的创建时间和修改时间,标题,主题和作者,app是文档的其他属性,文档类型,版本,是否只读,是否共享,安全属性等文档属性信息。
Core.xml <dc:creator></dc:creator> <cp:lastModifiedBy></cp:lastModifiedBy> <dcterms:created xsi:type="dcterms:W3CDTF">2006-09-13T11:21:51Z</dcterms:created> <dcterms:modified xsi:type="dcterms:W3CDTF">2013-06-05T09:28:23Z</dcterms:modified> App.xml <Application>Microsoft Excel</Application> <DocSecurity>0</DocSecurity> <ScaleCrop>false</ScaleCrop> <Company></Company> <LinksUpToDate>false</LinksUpToDate> <SharedDoc>false</SharedDoc> <HyperlinksChanged>false</HyperlinksChanged> <AppVersion>12.0000</AppVersion> ……
2.在xl目录下是文档的具体内容信息
先看workbook.xml
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"> <fileVersion appName="xl" lastEdited="4" lowestEdited="4" rupBuild="4507" /> <workbookPr filterPrivacy="1" defaultThemeVersion="124226" /> <bookViews> <workbookView xWindow="0" yWindow="90" windowWidth="19200" windowHeight="11640" /> </bookViews> <sheets> <sheet name="Sheet1" sheetId="1" r:id="rId1" /> <sheet name="Sheet2" sheetId="2" r:id="rId2" /> <sheet name="Sheet3" sheetId="3" r:id="rId3" /> </sheets> <calcPr calcId="125725" /></workbook>
workbook.xml文件包含一对<sheets>标签,其中的每个<sheet>元素都代表Excel 2007文件中的一个,工作表的名称就是其name属性的值,这里有三个sheet。
xl/_rels/workbook.xml.rels定义每个sheetid对应的sheet内容文件sheet1.xml,共享的单元格内容文件sharedstring.xml,样式文件style.xml是当前单元格的样式字体,颜色等样式的xml配置。
Theme存放的是当前的设置导航栏的默认样式。这两个看看大概也就能明白。
关键我们看看下面每个sheet的内容格式,
打开一个sheet1.xml看看
<sheetData> <row r="1" spans="1:7" ht="33.75" customHeight="1"> row标签是表示每一行的数据,r表示第几行,其他几个都是这几行的样式 <c r="A1" s="9" t="s">c标签表示每个单元格的内容,这里A1 第一行的第一列,r表示位置,s表示这个单元格的样式, s=9对应style.xml的的index为9的样式即为这个单元格的样式,t=s表示这个单元格有值,里面的v标签即为值的id,id对应到sharedstring.xm里的id对应的值 <v>2</v> </c> <c r="B1" s="10" /> 没有t属性,表示这个单元格没有值设置 <c r="C1" s="10" /> <c r="D1" s="10" /> <c r="E1" s="10" /> <c r="F1" s="10" /> </row> <row r="2" spans="1:7" ht="27.75" customHeight="1"> 第二行 <c r="A2" s="3" t="s"> 第二行第二列 <v>1</v> </c> <c r="B2" s="4" t="s"> <v>5</v> </c> <c r="C2" s="3" t="s"> <v>0</v>
我们找到对应的第一行第一列的值索引为2对应到sharedStrings.xml里面的index的值,这里si从0开始,第三个即为index为2的值,刚好跟我们的excel的A1值符合<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="71" uniqueCount="13"> <si> <t>物料编号</t> <phoneticPr fontId="1" type="noConversion" /> </si> <si> <t>序号</t> <phoneticPr fontId="1" type="noConversion" /> </si> <si> <t>注意:请不要修改表中蓝色区域文字;带有*号字段是必填项;每张物料清单最多只能导入20条物料信息</t> <phoneticPr fontId="1" type="noConversion" /> </si>
而A1的s=9对应的样式style.xml我们也看看,找到cellXfs里面的第9个,不过这里又引用fontid字体样式,borderid样式,numfmtId格式等
<cellXfs count="11"> ...... <xf numFmtId="0" fontId="0" fillId="3" borderId="1" xfId="0" applyFill="1" applyBorder="1"> <alignment vertical="center" /> </xf> <xf numFmtId="0" fontId="2" fillId="0" borderId="2" xfId="0"
组件是针对同一张表中的字段进行映射,作用是将字段多的一张表分成多个实体类来表示。
如:name与user
user表中有first_name及last_name。在实体类中,自定义一个Name类来表示first_name与last_name;
Name实体类:
package cn.framelife.mvc.entity; public class Name { private String firstName; private String lastName; public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } }
User实体类:
package cn.framelife.mvc.entity; import java.io.Serializable; public class User implements Serializable { private Integer id; private Name name; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Name getName() { return name; } public void setName(Name name) { this.name = name; } }
User.hbm.xml:
<hibernate-mapping> <class name="cn.framelife.hibernate.entity.User" table="user" catalog="hibernate"> <id name="id" type="java.lang.Integer"> <column name="id" /> <generator class="native" /> </id> <component name="name" class="cn.framelife.hibernate.entity.Name"> <property name="firstName" column="first_name"></property> <property name="lastName" column="last_name"></property> </component> </class> </hibernate-mapping>
增加操作:
tx = session.beginTransaction(); Name name = new Name(); name.setFirstName("111"); name.setLastName("2222"); User user = new User(); user.setName(name); session.save(user); tx.commit();