Delphi2010中DataSnap進階技術

來源:互聯網
上載者:User

標籤:des   http   io   os   使用   ar   for   檔案   資料   

Delphi2010中DataSnap進階技術 日期:2010年12月1日 SUNSTONE原創 人氣:13498 查看:[大字型 中字型 小字型]  (1)—為DataSnap系統服務程式添加描述

這幾天一直在研究Delphi 2010的DataSnap,感覺功能真是很強大,現在足有理由證明Delphi7該下崗了。

DataSnap有三種服務模式,其中Service Application方式建立的windows服務沒有描述,描述部分是空的,感覺總是欠缺點什麼。

現找到辦法添加描述:

procedure TServerContainer2.ServiceAfterInstall(Sender: TService);
var

    reg: TRegistry;
begin  
        reg := TRegistry.Create;  
        try    
    with reg do
        begin
             RootKey := HKEY_LOCAL_MACHINE;
             if OpenKey(‘SYSTEM\CurrentControlSet\Services\‘ + Self.Name, false)
              then
                 begin
                     WriteString(‘Description‘, ‘It is my service‘);
                 end;
            CloseKey;    
                    end;  
                 finally
                     reg.Free;
                 end;
end;

(2)—DataSnap服務端和用戶端發布分發方法

這幾天繼續研究DataSnap技術。

針對伺服器和用戶端軟體,如何發布呢?經過研究發現,分發方法非常簡單!

伺服器發布方法:

1.在unit ServerMethodsUnit1單元中,添加uses MidasLib;(添加MidasLib的目的是省去發布Midas.dll)

2.我用的是火鳥資料庫,只需拷貝dbxfb.dll和fbclient.dll

分發的伺服器軟體只需三個檔案:你的伺服器程式、dbxfb.dll 和 fbclient.dll

用戶端發布方法:

1.在用戶端程式中加上uses MidasLib;(添加MidasLib的目的是省去發布Midas.dll)

2.如果伺服器使用了http協議作為datasnap通訊的話,還需在用戶端程式中加上 users DSHTTPLayer;如果使用tcp協議,無需此步驟

分發的用戶端軟體只需一個檔案:你的用戶端程式

伺服器和用戶端無需Midas.dll,也不需要註冊regsvr32 Midas.dll,看來Delphi2010的datasnap拋棄使用COM真是進步不少!

發布程式竟是如此簡單!!!

(3)—DataSnap伺服器如何得到用戶端的IP和連接埠

作為一個伺服器軟體,必須做到對用戶端強有力的控制,想要控制,就必須得到用戶端的網路基本資料,比如用戶端IP和連接埠。有了用戶端IP就能隨心所欲操控用戶端,比如終止某些用戶端的串連、限制功能等等。

在delphi2010中的datasnap伺服器如何獲得用戶端ip,的確花了我點時間,奇怪為什麼這個功能不做的更人性化點呢,功能總是藏著掖著。還得讓程式員像尋寶一樣摸索,浪費時間。現在把我整理的結果奉獻給大家,免得大家在花時間研究這個。

另外,通過研究發現,DSConnectEventObject.ChannelInfo.Id 屬性實際上是記憶體位址,並不是一個簡單的數字。

 

以下代碼紅色部分是關鍵。

uses IdTCPConnection;

......

procedure TServerContainer1.DSServer1Connect   (DSConnectEventObject: TDSConnectEventObject);
var   ClientConnection: TIdTCPConnection;
begin  
    with Form1 do  
        begin
            dsShowDataSet.Append;
           dsShowDataSet[‘ClientConnectTime‘] := Now;

           if DSConnectEventObject.ChannelInfo <> nil then   
              begin   
                     ClientConnection := TIdTCPConnection(DSConnectEventObject.ChannelInfo.Id);

                    dsShowDataSet[‘ClientID‘] := DSConnectEventObject.ChannelInfo.Id;
                    dsShowDataSet[‘ClientIP‘] := ClientConnection.Socket.Binding.PeerIP +         ‘:‘ + IntToStr(ClientConnection.Socket.Binding.PeerPort);                          
                   dsShowDataSet[‘ServerIP‘] := ClientConnection.Socket.Binding.IP + ‘:‘ +         IntToStr(ClientConnection.Socket.Binding.Port);     end;

                    dsShowDataSet[‘ClientUserName‘] := DSConnectEventObject.ConnectProperties       [TDBXPropertyNames.UserName];    
                    dsShowDataSet[‘ClientUserPassword‘] :=       DSConnectEventObject.ConnectProperties[TDBXPropertyNames.Password];     
                    dsShowDataSet[‘ServerInfo‘] := DSConnectEventObject.ConnectProperties       [TDBXPropertyNames.ServerConnection];                                                 dsShowDataSet.Post;
  end;
end;

(4)—TCP keepAlive和KeepAliveInterval參數詳解

Delphi2010中DataSnap,如果用戶端異常掉線或拔掉網線,那麼在服務端會留下一個TCP串連,這個串連會變成死串連(經過測試,如果windows的TCP保持串連禁用的話,三個小時該死串連還不消失)。如果大量用戶端並發,出現的死TCP串連過多,伺服器記憶體和連接埠將會增加,直到佔滿伺服器的連接埠和耗盡記憶體為止。如果這樣的話,伺服器無法健壯穩定的運行。

大家可以另開線程來監控用戶端串連,但是今天要給大家講解的不是這個方法,而是使用TCP協議內建的心跳包功能解決這個問題。

大家先瞭解一下 TCP keep-alive原理

一個TCP keep-alive 包是一個簡單的ACK,該ACK包內容為一個比當前串連sequence number 小於一的包。主機接受到這些ACKs會返

回一個包含當前sequence number 的ACK包。 Keep-alives一般被用來驗證遠端串連是否有效。如果該串連上沒有其他資料被傳輸,或者更高level 的 keep-alives被傳送,keep-alives 在每個KeepAliveTime被發送。(預設是 7,200,000 milliseconds ,也就是2個小時)。

如果沒有收到 keep-alive 應答,keep-alive 將在每 KeepAliveInterval 秒重發一次。KeepAliveInterval 預設為1秒。如 Microsoft 網路功能中很多部分中採用的 NETBT 串連,更常見的是發送 NETBios keep-alives,所以,在 NetBios 串連中通常不發送TCP keep-alives。 TCP保持串連預設被禁用,但是微軟Sockets應用程式可以使用SetSockOpt函數去啟用他們。

請看下面的類

type   TCP_KeepAlive = record     OnOff: Cardinal;     KeepAliveTime: Cardinal; // 多長時間(ms)沒有資料就開始send心跳包     KeepAliveInterval: Cardinal // 每隔多長時間(ms)send一個心跳包,發5次(系統值) end;

KeepAliveTime: TCP串連多長時間(毫秒)沒有資料就開始發送心跳包,有資料傳遞的時候不發送心跳包 KeepAliveInterval: 每隔多長時間(毫秒)發送一個心跳包,發5次(系統預設值)

如果用戶端網路中斷,伺服器系統發送心跳包後,伺服器會自動解除TCP串連。這一點,大家可以使用 netstat -p -tcp 命令查看

接下來我們將結合Delphi2010 DataSnap技術使用心跳包功能!敬請關注

(5)—建立穩定服務程式之TCP心跳包的使用

為了能讓我們的服務程式更加穩定,有些細節問題必須解決。就如上一講中提到的用戶端拔掉網線,造成伺服器上TCP變成死串連,如果死串連數量過多,對伺服器能長期穩定運行是一個巨大的威脅。

另外,經過測試,如果伺服器上有TCP死串連,那麼服務程式串連資料庫,也會產生那個一個死串連。這樣的話,給資料庫伺服器也造成威脅。所以,伺服器程式編寫的好壞,直接影響系統的穩定性!

如何解決TCP死串連的問題,有多種方法,其中最有效就是心跳包技術。

我們在DSServer的OnConnect事件中加入心跳包代碼

uses IdTCPConnection,IdWinsock2

........

type   TCP_KeepAlive = record     OnOff: Cardinal;     KeepAliveTime: Cardinal;     KeepAliveInterval: Cardinal; end;

........

procedure TServerContainer1.DSServer1Connect   (DSConnectEventObject: TDSConnectEventObject);
var  
    Val: TCP_KeepAlive;
    Ret: DWord;
    ClientConnection: TIdTCPConnection;
begin
  ClientConnection := TIdTCPConnection(DSConnectEventObject.ChannelInfo.Id);
  Val.OnOff := 1;
  Val.KeepAliveTime := 5000;
  Val.KeepAliveInterval := 3000;
  WSAIoctl(ClientConnection.Socket.Binding.Handle, IOC_IN or IOC_VENDOR or 4,     @Val, SizeOf(Val), nil, 0, @Ret, nil, nil);
end;

觀察上述代碼,我們把心跳包放到服務端上執行,如果伺服器的某個TCP串連在5秒鐘沒有收到資料,將會發送向對端發送心跳包,間隔3秒鐘,連續發送5次(參數詳解見上一講進階技術4)。如果5次以後對端還沒有應答,伺服器將結束該TCP串連。TCP的串連可以使用 netstat -p tcp 命令查看。

當該TCP結束後,delphi編寫的服務程式會自動結束和資料庫的串連。我用的是FireBird資料庫,大家可以使用命令查看 SELECT MON$USER, MON$REMOTE_ADDRESS,   MON$REMOTE_PID,   MON$TIMESTAMP  FROM MON$ATTACHMENTS

現在伺服器的tcp死串連和資料庫的死串連都清除了,我們的系統將能長期穩定的運行。

(6)—加強服務程式對訪問者的控制能力

1)作為一個服務程式,如果不限制用戶端訪問數量,後果將是很可怕的。如果有人惡搞,伺服器不堪重負,記憶體將耗盡,最終伺服器將宕機。如何限制訪問者的數量呢?

我們可以設定一個變數,來記錄來訪者的數量,如果超過我們既定的數字,那麼後續的串連伺服器請求,都將被斷掉。

2)限制了訪問數量,但是如果不做密碼身份認證,無關的人員也將能登陸伺服器!解決辦法是用戶端傳入使用者名稱和密碼,如果使用者名稱和密碼不正確,串連將被掛斷。

在用戶端的SQLConnection1中driver分類的username和password屬性設定好使用者名稱和密碼。

3)盡量不要設定DSTCPServerTransport1的Maxthreads屬性,還有資料庫連接池也不要設定,delphi2010會有記憶體泄露,這兩個參數儲存預設即可。

在dsserver1控制項的onconnect事件中加入如下代碼(使用的是tcp/ip串連):

procedure TMainForm.DSServer1Connect   (DSConnectEventObject: TDSConnectEventObject);
var
  val: TCP_KeepAlive;
  Ret: Integer;
  ClientConnection: TIdTCPConnection;
begin   // 最大串連數量,驗證來訪者密碼  
   if (DSConnectEventObject.ChannelInfo = nil) or (Connections >= 500) or     (DSConnectEventObject.ConnectProperties[TDBXPropertyNames.UserName]       <> ‘sunstone‘) or (DSConnectEventObject.ConnectProperties       [TDBXPropertyNames.Password] <> ‘mypassword‘) then  
begin
    DSConnectEventObject.DbxConnection.Destroy;     // ClientConnection.Disconnect;  
end
  else
  begin     // 擷取socket串連     ClientConnection := TIdTCPConnection(DSConnectEventObject.ChannelInfo.Id);        

         ClientConnection.OnDisconnected :=          ClientDisconnectEvent;

    // 記錄來訪者數量    
        inc(Connections);    
         lblShowConnections.Caption := IntToStr(Connections);

    if Trim(ShowConnections.Cells[0, 1]) <> ‘‘ then
      ShowConnections.RowCount := ShowConnections.RowCount + 1;

    ShowConnections.Cells[0, ShowConnections.RowCount - 1] := IntToStr       (DSConnectEventObject.ChannelInfo.Id);
    ShowConnections.Cells[1, ShowConnections.RowCount - 1] :=       ClientConnection.Socket.Binding.PeerIP + ‘:‘ + IntToStr       (ClientConnection.Socket.Binding.PeerPort);
    ShowConnections.Cells[2, ShowConnections.RowCount - 1] :=       DSConnectEventObject.ConnectProperties[TDBXPropertyNames.UserName];     ShowConnections.Cells[3, ShowConnections.RowCount - 1] :=       DSConnectEventObject.ConnectProperties[TDBXPropertyNames.Password];     ShowConnections.Cells[4, ShowConnections.RowCount - 1] := FormatDateTime       (‘yyyy-mm-dd hh:nn:ss‘, Now);     // ShowConnections.Cells[6, ShowConnections.RowCount - 1] :=     // DSConnectEventObject.ConnectProperties     // [TDBXPropertyNames.ServerConnection];

    // 設定心跳包     val.OnOff := 1;
    val.KeepAliveTime := 5000;
    val.KeepAliveInterval := 1000;
    WSAIoctl(ClientConnection.Socket.Binding.Handle, IOC_IN or IOC_VENDOR or 4,       @val, SizeOf(val), nil, 0, @Ret, nil, nil);
  end;
end;

(7)—TDSServerClass中Lifecycle生命週期三種屬性說明

Lifecycle 三種屬性: Session、Invocation、Server

這三種屬性都用在什麼情況,有什麼要注意的事項,Delphi2010中罕有說明。

如果亂用這三種屬性,你的服務程式有可能崩潰,資料混亂,記憶體佔用大,效率低等問題!

下面我對這三種屬性的使用環境逐一介紹:

1. Session

說明:這是delphi2010中預設屬性,也是delphi推薦設定。Session會為每個來自用戶端的連結,建立一個線程來執行個體化。執行個體化是什麼概念呢?就是這個線程把所有你將要用到的類、函數等等都建立好了,等待你用戶端直接使用。這個線程和執行個體化並不釋放,直到用戶端中斷串連。如果有300個用戶端,那麼你的伺服器將會有300線程和執行個體,對伺服器硬體和記憶體是個考驗。

適用環境:這個設定是安全執行緒的!

用戶端數量少,每台伺服器不超過串連數量: 200 x CPU個數 x (每個CPU核心數x0.7) (這是經驗值,穩定串連的數量,不是極限數量,別誤解^_^),記憶體現在很便宜了,想加多大就多大! 用戶端頻繁調用伺服器資料,無論串連數量是多少,最好都用這種設定。如果用戶端很多,建議採用Server Load Balancer和多台伺服器來解決。 2. Invocation

說明:伺服器只是建立串連,但是先不做執行個體化,只有當用戶端請求功能的時候,伺服器才開線程並執行個體化,當用戶端用完後,伺服器就釋放線程和執行個體。

 適用環境:這個設定是安全執行緒的!

如果用戶端調用伺服器資料頻率低,這種方法很不錯,會節約很多記憶體。 3. Server

說明:伺服器對所有用戶端串連使用一個執行個體,不是安全執行緒的。所以要自己控制用戶端並發調用的問題(可以使用互斥、原子量等方法),讓用戶端的調用排成一隊使用伺服器資源。

 適用環境:這個設定不是安全執行緒的!!

配置較低的伺服器 伺服器串連的另一端只能是單線程工作的模式

 

本文來自Delphi園地,轉載請標明出處:http://www.delphifans.com/InfoView/Article_6521.html

Delphi2010中DataSnap進階技術

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.