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

來源:互聯網
上載者:User

標籤:

 

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 []:[email protected]

很多都是你填寫的內容,記住中國的國別縮寫是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使用心得

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.