最近一个通讯项目中因为偷懒使用了TIdTcpServer控件,噩梦就开始了
小弟的编程环境是Delphi7,使用的是它自带的TIdTcpServer,估计版本比较低。所以,当服务器启动后,如果在有客服端连接的情况下,关闭服务器(Active:=false)时,服务器抛出"Terminate Thread Timeout."异常。从而使得TIdTcpServer根本无法关闭。网上解决办法大致相同
第一种是说在Socket断开的事件里,将AThread的Data属性赋值为nil,俺测试的结果是一样要抛出异常。第二种是在主程序退出的事件里用try...except将IdTcpServer.active:=false包裹起来,这样至少在非调试模式下,程序可以正常退出。可能是俺代码的问题,俺发现这样有可能出现主界面被关闭了,但是进程仍然在运行,只有在任务管理器中强制结束的结果。第三种方法俺觉得最靠谱:在IdTcpServer的关闭函数中遍历所有的客户端连接,并一一关闭,代码基本上都很类似以下方式:
i:=IdTCPServer.Threads.LockList.Count;
IdTCPServer.Threads.UnlockList;
for j:=0 to i-1 do
begin
  TIdPeerThread(IdTCPServer.Threads.LockList.Items[j]).Connection.DisconnectSocket;
  IdTCPServer.Threads.UnlockList;
end;
IdTCPServer.Active := false;
但是结果并不如预期的那么完美,油盐不进的IdTcpServer在Active:=false时依然执着的抛出"Terminate Thread Timeout."异常。小弟在不断尝试后各种解决办法后,忽然发现"Terminate Thread Timeout."异常消失了,于是总结出屏蔽此异常的个人解决方式如下,因为解决的并不完美,所以请各位高手指点一下:第一,因为TIdTcpServer提供了OnException的事件,所以最好不要在TIdTcpServer的OnExecute或OnDisconnect中捕获客服端的断开连接异常,将TIdTcpServer的非用户定义异常统一在OnException中处理(其他的如strtoint或者数据库操作等异常您可以自己处理)。第二,最好不要在TIdTcpServer的提供的各个事件中使用IdTcpServer.Threads.LockList,可能有点矫枉过正,因为这些事件有可能在你主界面的某个使用到IdTcpServer.Threads.LockList的函数操作中被触发,如果你上一句使用了LockList,此时触发了IdTcpServer的事件,即使下一句用UnlockList,但是在事件里你再次使用LockList时,很可能被锁死。第三,遍历关闭客户端连接完成后,要将此连接的AThread从IdTcpServer.Threads.LockList中移除,否则在Active:=false时依然会抛出"Terminate Thread Timeout."异常。代码如下:
//打开服务器事件
procedure TFormMain.BtnStartClick(Sender: TObject);
begin
  IdTCPServer.DefaultPort := StrToInt(EdtPort.Text);
  IdTCPServer.Active := True;
end;
//关闭服务器事件
procedure TFormMain.BtnStopClick(Sender: TObject);
var
i,j:integer;
List:TList;
begin
try
  i:=IdTCPServer.Threads.LockList.Count;
  IdTCPServer.Threads.UnlockList;  for j:=0 to i-1 do
  begin
    TIdPeerThread(IdTCPServer.Threads.LockList.Items[j]).Connection.DisconnectSocket;
    IdTCPServer.Threads.UnlockList;
    //TIdPeerThread(IdTCPServer.Threads.LockList.Items[j]).Free;
    //注意:如果放开了上面那句注释,那么再次关闭服务器时就会出现锁死,所以俺屏蔽了……俺也不知道原因,郁闷
    //IdTCPServer.Threads.UnlockList;
  end;  IdTCPServer.Threads.LockList.Clear;//这一句似乎是关键,很多网上的解决办法并没有这一句,所以会抛出异常
  IdTCPServer.Threads.UnlockList;
  IdTCPServer.Active:=false;
end;//连接事件
procedure TFormMain.IdTCPServerConnect(AThread: TIdPeerThread);
begin
  AThread.FreeOnTerminate:=true;//只是以防万一,追求心理安慰的一句代码,可能没啥作用
end;procedure TFormMain.IdTCPServerExecute(AThread: TIdPeerThread);
begin
  //你自己的数据处理代码
  with AThread.Connection do
  begin
    ReadChar();
  end;
end;
//异常处理
procedure TFormMain.IdTCPServerException(AThread: TIdPeerThread;
  AException: Exception);
begin
  AThread.Data:=nil;//这句话也许不重要,同样只是以防万一,追求心理安慰的一句代码
  if AThread.Connection.Connected then
    AThread.Connection.DisconnectSocket;  //小弟曾经尝试在这里调用以下两句注释中的代码,结果好像在第二次关闭服务器时就会出现死锁,依然不知道原因,郁闷……
  // IdTCPServer.Threads.LockList.Remove(AThread);或者IdTCPServer.Threads.Remove(AThread);
  // IdTCPServer.Threads.UnlockList;
  try
    AThread.Destroy;
    //用Free或者Terminate应该可以,俺用Destroy只是想看AThread在强制释放时是否会出现某些异常,以便分析,结果似乎程序执行到这句时,就会跳出当前事件,从侧面证明了AThread会阻塞在它的释放函数上,但是因为它是在线程中执行,所以似乎不会影响结果
    AThread:=nil;
  except
  end;
end;//关闭指定的客户端
procedure TFormMain.CloseClientSocket(AThread: TIdPeerThread);
begin
  IdTCPServer.Threads.LockList.Remove(AThread);
  //原理和关闭服务事件里一样,即从IdTCPServer管理的当前列表中删除这个AThread,让IdTCPServer不再关心此客户线程
  IdTCPServer.Threads.UnlockList;
  AThread.Connection.DisconnectSocket;
  //如果你按照小弟的代码在IdTCPServerException事件里释放了AThread,那么下句注释中的代码一旦执行,就会锁死
  //AThread.Free;
end;
还有一点比较奇怪,俺的IdTCPServer控件的TerminateWaitTime属性如果设为0,则必定会抛出异常,俺设了5000毫秒
以上代码,小弟在程序运行时便不会抛出Thread Terminate Timeout异常了(PS,俺的操作系统是XP ps2,应该关系不大)原因分析:IdTCPServer在Active:=false时会尝试去结束其下所有的客户线程,客户线程在结束时又会尝试关闭其socket连接,但是如果客户端的Socket并未关闭,则客户线程可能会一直等待直到TerminateWaitTime所设的时间,随后就会抛出Thread Terminate Timeout异常。因为在这之前小弟所做其他尝试中曾经出现过很奇怪的问题,即服务器遍历了所有客户端连接并关闭后,客户端不一定会知道自己的连接被关闭了,于是俺很手贱的用客户端又发了条数据,结果很诡异了,服务器明明关闭了所有客户端,却依然触发了IdTCPServerExecute事件,于是在界面上显示了出了死掉的客户端所发出的幽灵数据。也正是因为这次阴差阳错,让俺猜测IdTCPServer虽然强制关闭了客户端Socket,但是有可能根本就没有真正的关掉,所以每次在Active:=false时,它又会去尝试关闭,从而不断的抛出Thread Terminate Timeout异常。说到这里,估计很多高手已经看出这个办法的不足了:AThread有可能更本就没被释放掉,或许永远也不会被释放(在程序退出后,或者客户端主动关闭了Socket的情况下可能可以被释放)。俺在想TIdTCPServer的编写者应该考虑过Thread Terminate Timeout这种异常的情况。也许有这种处理的官方做法,只是俺不知道而已,各位高手有知道的,请一定告知,感激不尽PS:现阶段,俺非常的痛恨TIdTCPServer!

解决方案 »

  1.   

    再补充一句
    用上面的方法,当你调用CloseClientSocket(AThread: TIdPeerThread)过程时,虽然关闭了客户端Socket,但是似乎并不会触发IdTCPServer的OnDisconnect事件,但是如果客服端主动关闭则会触发此事件,这也算上面方法的一种弊端了。俺想已经有高手知道俺这种解决方法了,俺写出来只是为了抛砖引玉,希望看到高手们有其他的解决办法,为我们这些仍然在TIdTCPServer上苦苦挣扎的菜鸟们提供一条明路,再次拜谢!PS:如今的现阶段,俺依然非常痛恨TIdTCPServer!
      

  2.   

    我也比较痛恨。
    还没有socket那两个控件好用
      

  3.   

    一直都用的socket的控件,从来不用这个。不知道。
      

  4.   

    最近也再为Indy的问题烦恼...关注下
      

  5.   

    其实那个list的locklist方法本质上是借助于临界区,用起来我感觉有点脱裤子放P的感觉,当然有些时候必须要那样
      

  6.   

    我碰到这个情况,处理方法如下:1. 创建一个 TThreadList 变量,用来保存连接。
    2. 在 Onconnect 事件中将 AThread 保存到 上面这个 list 变量中。
    3. 在 OnDisconnect 事件中从 list 中删除 AThread。
    4. 在停止服务器时,对 list 遍历,AThread.Connection.Disconnect 。这样就没问题了。由于 indy 不是事件触发,当服务器停止时,客户端并不知道服务器已经停止了,此时客户端继续使用连接的话,会报错。因此,要在客户端的代码里加上 try
      idTcpClient.CheckForGracefulDisconnect(true)
    except
    end;之后,再检测 idTcpClient.Connected 就是准确的了我也是刚用使用 网络编程,一点小经验