Delphi2010中DataSnap高級技術

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) (這是經驗值,穩定連接的數量,不是極限數量,別誤解^_^),內存現在很便宜了,想加多大就多大!
客戶端頻繁調用服務器數據,無論連接數量是多少,最好都用這種設置。如果客戶端很多,建議採用負載平衡和多臺服務器來解決。
2. Invocation

說明:服務器只是建立連接,但是先不做實例化,只有當客戶端請求功能的時候,服務器纔開線程並實例化,當客戶端用完後,服務器就釋放線程和實例。

 適用環境:這個設置是線程安全的!

如果客戶端調用服務器數據頻率低,這種方法很不錯,會節約很多內存。
3. Server

說明:服務器對所有客戶端連接使用一個實例,不是線程安全的。所以要自己控制客戶端併發調用的問題(可以使用互斥、原子量等方法),讓客戶端的調用排成一隊使用服務器資源。

 適用環境:這個設置不是線程安全的!!

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

轉載:http://blog.csdn.net/sunstone

(出處:http://blog.csdn.net/sunstone/archive/2009/12/05/4944779.aspx


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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章