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的心得。

开源给我的印象真的是越来越糟糕,早些时候处理MySQL的版本兼容性就觉得受不了,文档不全,版本差异显著,不同版本的外部接口不一致,甚至都不知道要保持向下兼容性,范例少,没事就是叫你去看源程序,说实话大多数时候做开发哪有那么多时间去人家的源程序啊,一会儿pascal,一会儿C++,一会儿在Pascal中不打;结尾,一会儿在C++写起了begin end,累啊。

indy中的SSL组件是IdServerIOHandlerSSLOpenSSL和IdSSLIOHandlerSocketOpenSSL,前一个用在服务器,后一个用在客户端。

刚开始用,找不到DLL,我想大家都遇到,看了半天E文,哦,他用OpenSSL的,哪个是什么玩艺?下载一个吧,原来是开源项目,还是多平台的,我日,要我安装perl生成mack文件,还要安装VC6才能编译成二进制代码,晕,我只要2个DLL,不至于这么折腾我吧。算了,再去找,哦?有编译好的下载,不错。有个最新版的好像是0.9.7c不过比我那个源代码的0.9.8版本低了一点,算了不管了,下载完毕,拷贝DLL过去,我日,版本不对,DLL外部接口函数不正确。indy究竟用哪个版本的呢?在indy官网上看了半天他也没说他的indy10基于哪个版本的,可见这种组件供应商的素质。没辙,下载了一Demo,Demo中包含了这两个DLL,好,拷贝过去,能够用,总算开工了。

开工了,它不工作,看帮助,indy的帮助啥也没有,有的简陋到居然只有一句话,这是个string类型的属性,这要她说吗?没辙,只好看Demo去(我现在有点理解开源的含义了),这么多年养成的好习惯都费了,以前做开发,用到新组件,首先就是看官方帮助,微软的那个帮助叫做详细啊,完了,现在第一件事情是去看Demo,还不知道是不是标准的范例,不知道是不是能够涵盖所有的内容。看了半天范例,原来要证书文件,我也够蠢的,SSL没证书怎么玩?证书?怎么办?看了半天,我应该需要一个自签名的根证书,然后需要一个二级证书,最后是key文件。其实我只需要一个自签名的证书就可以了。怎么搞?windows下没一个工具可以产生新证书的,IIS有,但是证书文件不知道给他藏在什么地方了。只好用openSSL生成新证书。

搞了一整天,终于看明白了OpenSLL的关键指令,大致结构有个模糊的印象,先产生一个key文件,然后用这个Key文件产生一个自签名的证书,这个证书可以作为根证书使用,更多的方法就不知道了,key文件要用des加密,用其他的加密,indy不能识别。crt要用x509协议。具体的openSSL指令如下,十多年没用DOS界面了,看样子要练练指法了。

产生一个key,当然要先启动openSSL,一个Dos界面,专业点吧,命令行界面,或者叫做XXXXXX

openssl>genrsa -des3 -out -voiceService.key 1024
下面是OpenSSL的反应

Loading 'screen' into random state - done
Generating RSA private key, 1024 bit long modulus
............................................................................++++++
........++++++
e is 65537 (0x10001)
Enter pass phrase for -ca.key:123456
Verifying - Enter pass phrase for -ca.key:123456

123456是你输入的密码,高版本的OpenSSL不会显示的。

产生自签名证书

req -new -x509 -days 3650 -key voiceService.key -out voiceService.crt -config openssl.cnf

注意openssl.cnf文件是配置文件,可以自己编辑用edit编辑,不要用记事本。不过一般用自带的,比较混蛋的是你下载的编译好的openssl里面没这个文件,只有下载源代码的才有,拷贝到openssl一起的目录,或者你喜欢的其他目录。

openssl的反应

Enter pass phrase for ca.key:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:cn
State or Province Name (full name) [Some-State]:zj
Locality Name (eg, city) []:hz
Organization Name (eg, company) [Internet Widgits Pty Ltd]:voiceService
Organizational Unit Name (eg, section) []:voiceService
Common Name (eg, YOUR name) []:voiceService
Email Address []:bluetrees@yeah.net

很多都是你填写的内容,记住中国的国别缩写是cn,我查了半天,我够苯啊,省份和城市缩写随便写,下面的也是随便写。

弄好以后产生2个文件

VoiceService.key和VoiceService.crt

VoiceService.crt这个文件复制一份修改后缀为VoiceService.pem,其实不修改也没事。

然后拷贝到服务器程序所在目录,在indy中加载的程序段,我这里在Form的Create事件中修改

var
  appDir: string;
begin
  Users:=TUsers.Create;
  appDir:= extractFilePath(application.exename);
   IdServerIOHandlerSSLOpenSSL1.SSLOptions.KeyFile:= appDir + 'VoiceService.key';
  IdServerIOHandlerSSLOpenSSL1.SSLOptions.CertFile:= appDir + 'VoiceService.crt';
  IdServerIOHandlerSSLOpenSSL1.SSLOptions.RootCertFile:= appDir + 'VoiceService.pem';
这些文件路径还不能用相对路径,只能用绝对路径,够操蛋吧。

在IdServerIOHandlerSSLOpenSSL的OnGetPassword事件中填写密码

Password:=123456;

然后就可以工作了。

对开源的感觉,在开源社区中最容易找到当老大的感觉,因为没有官方文档,没有范例,有的只是对源代码的熟悉程度。

从工业化的角度来说,完备的文档和成熟的范例才是技术进步的基石,而不是源代码。

开源社区有英雄,但是没有工程师,那里有思想,却没有规范。

商业软件看样子不可能消失,同样,开源社区也不能没有。

http://www.cnblogs.com/qiubole/archive/2006/04/06/368229.html

Delphi组件indy 10中IdTCPServer修正及SSL使用心得的更多相关文章

  1. Delphi使用Indy、ICS组件读取网页

    使用Indy 10中TIdHTTP的例子: 代码 uses IdHttp; . . . function HttpGet(const Url: string; var Html: string): B ...

  2. Delphi中Indy 10的安装和老版本的卸载

    http://www.cnblogs.com/railgunman/archive/2010/08/31/1814112.html Indy 10的安装和老版本的卸载 Indy 10下载地址: htt ...

  3. Indy 10.5.8 for Delphi and Lazarus 修改版(2011)

    Indy 10.5.8 for Delphi and Lazarus 修改版(2011)    Internet Direct(Indy)是一组开放源代码的Internet组件,涵盖了几乎所有流行的I ...

  4. delphi 7 下安装 indy 10.5.8 教程

    本教程用 indy 10.5.8 替换 delphi 7 自带的 indy 版本,让大家深入了解 delphi 组件安装的方法. 第一步:下载 indy 10.5.8 组件,解压到合适的目录里.如 D ...

  5. 删除delphi组件TStringlist中的重复项目

    https://blog.csdn.net/ozhy111/article/details/87975663 删除delphi组件TStringlist中的重复项目 2019年02月27日 15:41 ...

  6. [delphi]修改indy源码后重新编译

    http://blog.csdn.net/nerdy/article/details/8702568 虽然indy有一身的毛病,但是一般情况下使用起来还是多方便的. 今天在做一个使用到indy的程序的 ...

  7. 用Delphi从内存流中判断图片格式[转]

    http://blog.163.com/tfn2008%40yeah/blog/static/110321319201222243214337/ 用Delphi从内存流中判断图片格式[转] 2012- ...

  8. 关于 iOS 10 中 ATS 的问题

    本文于 2016 年 11 月 28 日按照 Apple 最新的文档和 Xcode 8 中的表现进行了部分更新. WWDC 15 提出的 ATS (App Transport Security) 是 ...

  9. 关于 iOS 10 中 ATS / HTTPS /2017 问题

    本文于 2016 年 11 月 28 日按照 Apple 最新的文档和 Xcode 8 中的表现进行了部分更新. WWDC 15 提出的 ATS (App Transport Security) 是 ...

随机推荐

  1. 第九篇:web之前端之web上传文件的方式

    前端之web上传文件的方式   前端之web上传文件的方式 本节内容 web上传文件方式介绍 form上传文件 原生js实现ajax上传文件 jquery实现ajax上传文件 form+iframe构 ...

  2. css 多栏自适应布局

    在页面重构中,我们经常会需要实现多栏布局,例如n栏固定宽度 + m栏自适应宽度的组合,绝对布局+padding+百分比宽度是容易想到的比较暴力的解决方法,但是作为未来的"工程师", ...

  3. MarkDown Pad2的一些用法

    一.标题 1.使用命令Ctrl+1 标题一 2.使用文字回车后,加上"-"号,再回车.就有如下的示例: 标题二 注意:减(-)号是用于最近的那一行文字变成标题. 二.背景 例如我要 ...

  4. 关于cnpm的一点小bug

    在实际工作中,一个项目完成后,在上线前,常常需要把代码进行压缩,一般是用gulp或者 webpack 进行压缩.(小妹是用gulp) gulp是运行在node 环境下的. 所以首先,下载并安装了nod ...

  5. 使用hwclock同步RTC(硬件时钟)和OS Clock(操作系统时钟)

    Linux下面有个命令叫做hwclock,其主要的功能是用来在RTC和操作系统之间进行时钟同步.既可以将RTC的时钟同步到操作系统,也可以在通过hwclock将当前系统时间.Internet时间.本地 ...

  6. linux 信号处理

    查看信号 kill -l 信号实际就是一个进程发送给另一个进程的消息

  7. zTree的getChangeCheckedNodes()使用

    zTree的getChangeCheckedNodes()方法用于获取输入框勾选状态被改变的节点集合.如果需要获取每次操作后全部被改变勾选状态的节点数据,请在每次勾选操作后,遍历所有被改变勾选状态的节 ...

  8. div 被Object盖住的。解决办法

    今天遇到一个比较头疼的问题,就是在一个标签上右键,弹出的菜单div被标签内的Office控件Object挡住了下半部分,始终无法显示.查了好多解决方案,最终都不能解决问题,几乎都要放弃了.中午吃饭的时 ...

  9. js事件对象--DOM中的事件对象/IE中的事件对象/跨浏览器的事件对象

    事件对象    在触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含着所有与事件有关的信息.包括导致事件的元素.事件的类型,以及其他与特定事件相关的信息.例如,鼠标操作导致的事件对 ...

  10. PHP小记录

    正的framework(大量使用)      thinkphp(部分使用)      cakephpyii(极少使用) [一]函数    1:函数的声明:每个函数的第一行都是函数开头,有声明函数的关键 ...