关于idtcpserver做的服务器程序几个问题(300分)

  • 主题发起人 主题发起人 jimmypeng
  • 开始时间 开始时间
J

jimmypeng

Unregistered / Unconfirmed
GUEST, unregistred user!
我用indy控件的idtcpserver组件开发了一个服务器程序.实现功能:1.用来接收客户端发送过来的数据,并将数据存入sql数据库. 2.当客户端请求接收数据时,服务器便从sql数据库中将该客户端的所有数据发送给客户端.都是采用短连接方式.在正常的情况下,该服务程序能正常运行,当连接的客户端多时或连接频繁时,便有时会出现如下三种任一错误:
1.list index out of bounds
2.invalid class typecast
3.access vilation at address 00403a38 in module "ntxsvr.exe", read of address FFFFFFDC
这3种错误不是同时出现,而是有时是这个,有时是那个.最后导致服务器不能工作.这是经过半年时间的运作发现的.请大哥们帮帮忙.非常感谢!
 
哇,都运行了半年了啊?
连微软的2003服务器也需要每个月定期重启一次呢,你这半年都让他运行着啊?
 
该程序已有使用了半年时间,就是在这个运行过程中,时不时会出现上面的3个问题,程序就必须重起,才能正常过作.有时可以连续工作一个星期,有时连续工作15天.
 
list index out of bounds
估计是连接指针列表操作的问题,注意及时清除无效的
 
2.传递的类型错误,出现失效的类型转换;
做类型转换时,要先判断类型是否为该类型 一般用 is 操作符。
3.内存操作引起的错误,很可能是对象已释放但再次调用该对象。
调用不确定的对象调用前先判断是否有效,Object <> nil 或 Assigned(Object),
具体要看你的代码?
 
我将我的接收数据的源码贴出来:
//**--监听客户端请求
procedure TNtxServerFrm.IdTCPServerExecute(AThread: TIdPeerThread);
var
sReadCmd,sCommand:String;
sSoftRegCode,sSoftRegPwd:String;
ResultValue:Boolean;
AQryCompany:TADOQuery;
ACmdCompany:TADOCommand; //更新资料
iRkey:Integer;
begin
try
//开始接收客户端请求的命令
sReadCmd:=Trim(AThread.Connection.ReadLn);
sCommand:=Copy(sReadCmd,1,6);
sSoftRegCode:=Copy(sReadCmd,8,16);
sSoftRegPwd:=Copy(sReadCmd,25,15);

AQryCompany:=TADOQuery.Create(AThread.Connection);
AQryCompany.Connection:=ADOCN;
AQryCompany.SQL.Text:='Select Company.Rkey,Company.isStatus, ' +
'Company.CanSendSmsQty + Company.DayPreSentSmsQty as CanSendSmsQty ' +
'From Company ' + 'Where SoftRegCode=' + QuotedStr(sSoftRegCode) +
' and SoftRegPwd=' + QuotedStr(sSoftRegPwd);

AQryCompany.Active:=True;
if AQryCompany.RecordCount<>1 then
begin
AQryCompany.Close;
AQryCompany.Free;
AQryCompany:=nil;
AThread.Connection.WriteLn('[NORG]'); //传送没有注册命令
end
else
begin
iRkey:=AQryCompany.FieldByName('Rkey').AsInteger;
AQryCompany.Close;
AQryCompany.Free;
AQryCompany:=nil;

if sCommand='--RM--' then //接收信息
begin
AThread.connection.WriteLn('[RMSC]');
ResultValue:=FReceiveSms(Athread,iRkey); //送线程和企业表中RKEY
End
else if ............其他请求....
except
on E:Exception do
LBLog.Items.Add(DateTimeToStr(now)+Athread.Connection.Socket.Binding.PeerIP + ' Y.接收客户端请求错误: '+E.Message);
end;

try
if AThread.Terminated=False then
begin
AThread.Terminate;
AThread.FreeOnTerminate:=True;
if Athread.Connection.Socket.Binding.PeerIP<>IdTCPClient.Host then
LBlog2.Items.Add(DateTimeToStr(now) + ' ' + Athread.Connection.Socket.Binding.PeerIP + ' 成功中断');
end;
except
on E:Exception do
LBLog.Items.Add(DateTimeToStr(now) + ' ' + Athread.Connection.Socket.Binding.PeerIP + ' Z.接收客户端请求错误: '+E.Message);
end;
end;

//**--客户端接收信息
function TNtxServerFrm.FReceiveSms(AThread:TIdPeerThread;iRkey:Integer):Boolean;
type //请求记录变量
MyRecord=Packed Record
CurRec:Integer;
TotRec:Integer;
MobileNo:String[15];
Content:String[180];
MobiSendTime:TdateTime;
ReceTime:TdateTime;
isYuE:String[1];
end;
type //接收记录变量
MyRecRecord=Packed Record
Flag:String[1];
AutoReturn:String[1];
MobileNo:String[15];
Content:String[180];
end;
var
MySendingRecord:MyRecord;
MyReceivingRecord:MyRecRecord;
AQryReceSms:TADOQuery;
AQryCompany:TADOQuery;
ACmdReceSms:TADOCommand;
ACmdSendSms:TADOCommand;
ACmdCompany:TADOCommand;
iCurRec,iTotRec:Integer;
begin
//建立查询控件
AQryReceSms:=TADOQuery.Create(AThread.Connection);
AQryReceSms.Connection:=ADOCN;
AQryCompany:=TADOQuery.Create(AThread.Connection);
AQryCompany.Connection:=ADOCN;
ACmdReceSms:=TADOCommand.Create(AThread.Connection);
ACmdReceSms.Connection:=ADOCN;
ACmdSendSms:=TADOCommand.Create(AThread.Connection);
ACmdSendSms.Connection:=ADOCN;
ACmdCompany:=TADOCommand.Create(AThread.Connection);
ACmdCompany.Connection:=ADOCN;

//查询企业
AQryCompany.Close;
AQryCompany.SQL.Text:='SELECT Rkey,DayPresentSmsQty, ' +
'CanSendSmsQty+DayPresentSmsQty as CanSendSmsQty ' +
'From Company Where Rkey= ' + IntToStr(iRkey);

//查询未接收的信息
AQryReceSms.Close;
AQryReceSms.SQL.Text:='Select ReceSms.* ' +
'From ReceSms ' +
'Where ReceSms.Company_Rkey = ' + IntToStr(iRkey) +
' and (Flag is null or Flag<>''3'' ) ';
AQryReceSms.Active:=True;

iCurRec:=1;
iTotRec:=AQryReceSms.RecordCount;

try
if AQryReceSms.RecordCount>0 then
begin
AQryReceSms.First;
while AQryReceSms.Eof=False do
begin
MySendingRecord.CurRec:=iCurRec; //当前记录
MySendingRecord.TotRec:=iTotRec; //总记录
MySendingRecord.MobileNo:=AQryReceSms.FieldByName('MobileNo').AsString;
MySendingRecord.Content:=AQryReceSms.FieldByName('Content').AsString;
MySendingRecord.MobiSendTime:=AQryReceSms.FieldByName
('MobiSendTime').AsDateTime;
MySendingRecord.ReceTime:=Now; //客户端接收时间

MySendingRecord.isYuE:='0';

AThread.Connection.OpenWriteBuffer(-1);
AThread.connection.WriteBuffer(MySendingRecord,SizeOf
(MySendingRecord),True);
AThread.Connection.CloseWriteBuffer;

//将接收信息 插入 到 ReceSmsSuc 表中
ACmdReceSms.CommandText:='Insert into ReceSmsSuc (MobileNo,Content,ClientReceTime,MobiSendTime,FispNo,Company_Rkey,Flag) ' +
'Values (:ParMobileNo,:ParContent,GetDate(),:ParMobiSendTime,:ParFispNo,:ParCompany_Rkey,'+'''3'''+ ') ' ;

With ACmdReceSms do
begin
Parameters.ParamByName('ParMobileNo').Value:=AQryReceSms.FieldByName('MobileNo').AsString;
Parameters.ParamByName('ParContent').Value:=AQryReceSms.FieldByName('Content').AsString;
Parameters.ParamByName('ParMobiSendTime').Value:=AQryReceSms.FieldByName('MobiSendTime').AsDateTime;
Parameters.ParamByName('ParFispNo').Value:=AQryReceSms.FieldByName('FispNo').AsString;
Parameters.ParamByName('ParCompany_Rkey').Value:=AQryReceSms.FieldByName('Company_Rkey').AsInteger;
end;
ACmdReceSms.Execute;

//删除该笔已接收的资料
ACmdReceSms.CommandText:='Delete From ReceSms '+
'Where Rkey = ' + AQryReceSms.FieldByName('Rkey').AsString;
ACmdReceSms.Execute;

//接收Client请求命令
AThread.Connection.ReadBuffer(MyReceivingRecord,SizeOf(MyReceivingRecord));

AQryReceSms.Next;
iCurRec:=iCurRec + 1; //初步累计
end;

MySendingRecord.CurRec:=0; //当前记录
MySendingRecord.TotRec:=iTotRec; //总记录

AThread.Connection.OpenWriteBuffer(-1);
AThread.connection.WriteBuffer(MySendingRecord,SizeOf(MySendingRecord),True);
AThread.Connection.CloseWriteBuffer;
end
else
begin
MySendingRecord.CurRec:=0; //当前记录
MySendingRecord.TotRec:=iTotRec; //总记录

AThread.Connection.OpenWriteBuffer(-1);
AThread.connection.WriteBuffer(MySendingRecord,SizeOf(MySendingRecord),True);
AThread.Connection.CloseWriteBuffer;
end;
except
on E:Exception do
LBLog.Items.Add(DateTimeToStr(now) + ' ' + Athread.Connection.Socket.Binding.PeerIP + ' 接收信息错误: ' + E.Message);
end;

try
AQryReceSms.Close;
AQryReceSms.Free;
AQryReceSms:=nil;
AQryCompany.Close;
AQryCompany.Free;
AQryCompany:=nil;
ACmdReceSms.Free;
ACmdReceSms:=nil;
ACmdSendSms.Free;
ACmdSendSms:=nil;
ACmdCompany.Free;
ACmdCompany:=nil;
except
on E:Exception do
LBLog.Items.Add(DateTimeToStr(now) + ' ' + Athread.Connection.Socket.Binding.PeerIP + ' 释放变量错误: ' + E.Message);
end;

Result:=True;
end;
//**--企业接收信息---End

//释放内存,每2分钟执行一次
procedure TNtxServerFrm.TimerClearMemTimer(Sender: TObject);
begin
try
SetProcessWorkingSetSize(GetCurrentProcess,$FFFFFF,$FFFFFF);
application.ProcessMessages;
sleep(800);
LbLog.Items.Add(DateTimeToStr(Now) + ' 释放内存成功...!!');
except
on E:Exception do
begin
LbLog.Items.Add('释放内存错误:' + E.Message);
end;
end;
end;
 
to:jfyes
我已将客户端接收数据的代码贴出来了,请帮看看.
 
如果是一客户端处理完毕另一再来,你的就没问题,同时并发过来,
就死掉,估计你的数据量也不大。
并发连接问题,也就是你的客户过多,连接频繁时,会有这样问题,
你都没有处理并发连接,要知在很多客户连接到来,什么样的问题都可
能存在,你这样的处理不死也怪噢。
如果要从根本上解决,你最好为每个连接请建立一个会话,放入会
队列中,然后再开一个线程处理这些会话,逻辑上也就清楚多了。
另外你的代码也该优化一下,在处理线程中创建那么多ADO不给数据库带
来压力(数据库处理逻辑最好用存储过程在数据库,效率高)。
那么多Create和Free稍有不甚很容易内存溢出。
 
to:jfyes
非常感谢你的帮助!

1.ado那一块我先优化一下,能用存储过程的就用储存过程.

2.只于并发处理,idtcpserver本身就能处理多线程,我在调用接收函数的时候,也是用多线程的方式.我今天有测试过,用大量的客户端同时进行接收信息的处理,大概跑十多分钟的时候,便出现内存溢出的问题.你说的用会话,我不是很明白应该怎么做.
 
像你这样的应用很好处理呀,每注册的一个用户(客户端)建立一个会话,
用列表保存这些会话信息, 处理起来方便多了;
你的意思是每个连接就是一个线程,就不必重组,也能协同工作。不必担心并发问题。
其实不然,像你这样,并发连接进来的线程间各自去操作数据库不会有问题吗?这样造
成的问题很难发现,不易维护。
==>>>&quot;大量的客户端同时进行接收信息的处理, 大概跑十多分钟的时候,
便出现内存溢出的问题&quot;
=====>>
这还不能说明是并发问题吗?很可能你的溢出就是这个问题。

我所说的会话放在队列中,也就是用一个线程访问队列,再去处理数据逻辑,
可以用临界区保证数据操作安全。
 
优化你的数据库,首先要保证数据库不出问题。

我的服务器通常是用SOCKET来写的,对于服务器来讲,可控才是最重要的。INDY的服务控件
要有好的效果,通常也要优化的,还不如自己写一个。
简单说一下我的经验,
1、内存池是必要的,保持良好的结构,可以防止内存碎片而导致的内存异常和其他问题。
另:string类型就是比较危险的。由于DELPHI本身的内存管理会造成一些问题,所以最好自己管理,
2、数据完整性的保证,最好有一个简单的协议来保证,不然同样会导致内存方面的错误。
3、线程池和数据同步
每一个连接对应一个线程的做法不可取,用相关的IO模型来处理效率是最好的。

呵呵,俺的服务器两个月重启一次
 
to:jfyes,iswear321 兄:

不知能否贴一段源码看看,谢谢!
 
这个好,比传统的三层模式好,程序移植也方便。

我也正为并发问题苦恼,用户连接多,就经常出现问题,看来有必要好好研究什么完成端口,最好请张无忌大侠编写一个功能简单的、比较典型的完成端口的例子,那时应该可以解决楼主的问题。
 
呵呵,这一段正在整理,准备发一批网络开发的心得,让大家少走一些我走过的弯路。
 
to:iswear321
顶...
希望现在就能看到iswear321兄的开发心得.
 
缓冲队列只贴出如何处队列,
队列对象你要自己构建,我这里关连太多,不好贴,只主要的。
相关其他控制,还须你根据具体情况修改

type
TTCPServer = class;

TServerWorkThread = class(TThread)
private
FOwner: TTCPServer;
protected
procedure Execute; override;
public
constructor Create(AOwner: TTCPServer);
end;

TTCPServer = class(TComponent)
private
FIdTCPServer: TIdTCPServer;
FQueue: TQueue; //队列对象
FMManager: TMemoryManager; //静态内存管理
procedure doRead(Sender: TObject);
protected
procedure WorkThreadExecute(Sender: TObject);
public
constructor Create(AOwner: TComponent);
end;

implementation

constructor TTCPServer.Create(AOwner: TComponent);
begin
inherited Create;
FQueue := TQueue.Create(MaxBufferCount);
FMManager := TMemoryManager.Create(RecvBufSize, MaxBufferCount);
FWorkThreadCount := 5;
end;

//读取数据包并放入队列
procedure TTCPServer.doRead(Sender: TObject);
var
Buffer: PChar;
TempBuffer: array[0..4095] of Char;
BufferLength: Integer;
begin
//读取数据包
RecvBuf(TempBuffer, BufferLength);
if (BufferLength <= 0) or (BufferLength > TCPMTU) then
Exit;
if not FMManager.GetMem(Pointer(Buffer)) then
begin
Error('GetMem faild!');
Exit;
end;
//将数据包写入内存缓冲
Move(TempBuffer, Buffer, BufferLength);

//放进缓冲队列
if not FQueue.InsertNode(Buffer) then
begin
if not FMManager.FreeMem(Buffer) then
Error('FreeMem error');
Error('InsertNode faild!');
Exit;
end;
end;

//在队列中读取数据
procedure TTCPServer.WorkThreadExecute(Sender: TObject);
var
Buffer: PChar;
begin
if FBufferQueue = nil then
Exit;
if not FQueue.GetFirstNode(Pointer(Buffer)) or (Buffer = nil) then
begin
Sleep(100);
Exit;
end;
try
//真正处理数据包
ServerExecute(Sender, @Buffer);
finally
//处理完释放内存
if not FMManager.FreeMem(Buffer) then
Error('FreeMem error');
end;
end;

constructor TServerWorkThread.Create(AOwner: TTCPServer);
begin
FOwner := AOwner;
inherited Create(False);
end;

procedure TServerWorkThread.Execute;
begin
inherited;
while not Terminated do
begin
try
FOwner.WorkThreadExecute(Self);
except
on E: Exception do
FOwner.Error('TWorkThread.Execute');
end;
end;
end;
 
我试着将一写查询改成用存储过程,如所有的存储过程共享一个adoconnection,连接数据库,便出现了如下错误:连接占线导致另一个命令. 如每一个线程单独用一个连接,便不会,但在sql server 2000 中便会有很多连接进程,这样便影响sql效率.请问该如何做?
 
不可共享一个adoconnection的,但可共用原生的ADO对象,可以成为多线程安全。你可搜索'原生ADO'。呵呵
 
非常感谢大家的帮助
我在系统里定义了一个全局的临界区变量,在IdTCPServerExecute事件里的开头进入临界区,结尾离开临界区.程序运行很长时间没出现上面的三个问题.
所以现分析问题应该出在: 系统频繁创建ADO数据访问控件和释放ADO控件而引起的.
如是这样,不知道用线程池的方式是否可以解决.我试着用INDY中的idThreadMgrPool线程控件,运行测试,发现问题依旧.不知道如要用线程池,是否必须手工写代码控制IdTcpServer的线程.非常烦恼的是,我不知道这样的代码怎么写,请问谁有做过用线程池控制IdTcpServer,如何可以实现.
 
idtcpserver做服务器程序
我也要做这个网络传输的程序,发送出去的数据没有问题,接收到的全都乱码,不知是何原因!jimmypeng, jfyes,可否给份代码做参考!我的邮箱是 yeqing821@263.net
非常感谢!
 
后退
顶部