Delphi组件indy 10中IdTCPServer修正及SSL使用心…

2008-04-09 04:20:54来源:互联网 阅读 ()

新老客户大回馈,云服务器低至5折

indy 10终于随着Delphi2005发布了,不过indy套件在我的印象中总是复杂并且BUG不断,说实话,不是看在他一整套组件的面子上,我还是喜欢VCL原生的Socket组件,简洁,清晰。Indy9发展到了indy10几乎完全不兼容,可叹啊。言归正传。在使用IdTCPServer组件的时候发现了他的漏洞,他的OnConnec,OnExecute,OnDisconnect等事件是在其他线程中执行的,通常情况下这没有问题,但是在特殊的情况下会造成问题,如果其他部分的程序写得有问题就会出现漏洞。

我发现的漏洞是这样的,我在OnDisconnect事件中释放一个ListView的一个对应的Item,也就是,一个客户端离开的时候,界面上的ListView对应的项目删除。正常情况没有任何问题,但是,如果不断开连接直接关闭程序就会死掉,事实上我在程序中Form的CloseQuery事件中作了处理,在这个事件中我关闭连接,但是,没有效果,只有在程序中手动的点鼠标关闭才不会死掉,问题出在哪里?

问题出在这里,ListView是一个Windows的标准组件,释放他的一个Item是通过消息完成的,也就是说,我在OnDisconnect中在一个非主线程的线程中向主线程的窗口发送消息并且等待返回。而此时主线程在干嘛呢?因为是主线程触发的DisConnect,所以他在等待这个端口的服务线程挂起,这样就发生死锁,问题在于,主线程的等待服务线程挂起的处理代码不当,它理论上应该在等待同时处理消息。原代码如下所示

procedure TIdSchedulerOfThread.TerminateYarn(AYarn: TIdYarn);
var
LYarn: TIdYarnOfThread;
begin
LYarn := TIdYarnOfThread(AYarn);
if LYarn.Thread.Suspended then begin //判断是否挂起了,挂起了才释放线程
// If suspended, was created but never started
// ie waiting on connection accept
LYarn.Thread.Free;
FreeAndNil(LYarn);
end else begin
// Is already running and will free itself
LYarn.Thread.Stop; //没完没了的调用Stop过程,却不处理任何消息和同步事件

// Dont free the yarn. The thread frees it (IdThread.pas)
end;
end;

它的上一级调用者,没完没了的判断服务线程数量,然后没完没了地调用上面这个函数,调用者原代码如下

procedure TIdTCPServer.TerminateAllThreads;
var
i: Integer;
begin
// TODO: reimplement support for TerminateWaitTimeout

//BGO: find out why TerminateAllThreads is sometimes called multiple times
//Kudzu: Its because of notifications. It calls shutdown when the Scheduler is
// set to nil and then again on destroy.
if Contexts <> nil then begin
with Contexts.LockList do try
for i := 0 to Count - 1 do begin
// Dont call disconnect with true. Otheriwse it frees the IOHandler and the thread
// is still running which often causes AVs and other.
TIdContext(Items[i]).Connection.Disconnect(False);
end;
finally Contexts.UnLockList; end;
end;

// Scheduler may be nil during destroy which calls TerminateAllThreads
// This happens with explicit schedulers
if Scheduler <> nil then begin
Scheduler.TerminateAllYarns;
end;
end;

说实话,我很不理解indy线程对象又是stop又是start的复杂模型意义何在,而且非常容易出问题,简单的线程模型更加可靠和实用。

修改的方法很简单,但是考虑到兼容Linux和其他平台的问题,还必须进行隔离分解层次,所以稍微复杂了一点点。就是在idThread类中增加一个公开的方法ProcessMessages,然后在TerminateYarn中调用。代码如下

procedure TIdThread.ProcessMessages;
begin
{$IFDEF MSWINDOWS}
if GetCurrentThreadID = MainThreadID then
begin
CheckSynchronize;
Application.ProcessMessages;
end;
{$ENDIF}
{$IFDEF LINUX}
if GetCurrentThreadID = MainThreadID then
begin
CheckSynchronize(1000);
end;
{$ENDIF}
end;

procedure TIdSchedulerOfThread.TerminateYarn(AYarn: TIdYarn);
var
LYarn: TIdYarnOfThread;
begin
LYarn := TIdYarnOfThread(AYarn);
if LYarn.Thread.Suspended then begin
// If suspended, was created but never started
// ie waiting on connection accept
LYarn.Thread.Free;
FreeAndNil(LYarn);
end else begin
// Is already running and will free itself
LYarn.Thread.Stop;
LYarn.Thread.ProcessMessages; //此处增加了处理。
// Dont free the yarn. The thread frees it (IdThread.pas)
end;
end;

TidThread所在单元增加uses

{$IFDEF MSWINDOWS}
Windows, Forms,
{$ENDIF}
至此程序正常。

虽然理论上,在其他线程中访问VCL组件应当使用线程同步方法,但是在这个例子中由于工作线程包装太深已经无法使用线程同步方法了,其实在这个例子中即使使用了线程同步方法来访问VCL也无济于事,因为在等待线程结束的程序中根本没有检查主线程的未结消息和线程同步事件(Delphi7以后版本线程同步方法使用事件event作为同步方法,Delphi7之前使用消息同步)一样会死锁。

其实VCL并没有强制主线程访问,他的说明是这样的,它要求在任何时候应当只有一个线程访问VCL的相关代码(不同的线程同时访问不相关的VCL代码段是没有问题的),所以,不使用线程同步方法访问VCL也是可以的,但是要使用其他方法保证访问的唯一,比如可以使用临界区。

下面是使用SSL的心得。

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:建立自己的csdn知识管理库(1)

下一篇:希望我不要下定决心放弃borland与delphi