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

时间:2021-12-09 21:27:05

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;