1、爲DataSnap系統服務程序添加描述html
這幾天一直在研究Delphi 2010的DataSnap,感受功能真是很強大,如今足有理由證實Delphi7該下崗了。ios
DataSnap有三種服務模式,其中Service Application方式創建的Windows服務沒有描述,描述部分是空的,可用以下方法添加服務描述:程序員
procedure TServerContainer.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', '機房管理系統核心服務'); end; CloseKey; end; finally reg.Free; end; end;
2、DataSnap服務端和客戶端發佈分發方法數據庫
服務器發佈方法:windows
1.在unit ServerMethodsUnit1單元中,添加uses MidasLib;(添加MidasLib的目的是省去發佈Midas.dll)服務器
2.若是用的是火鳥數據庫,只需拷貝dbxfb.dll和fbclient.dll,若是用的是SQLite,則什麼都不用拷貝。網絡
分發的服務器軟件只需三個文件:你的服務器程序、dbxfb.dll 和 fbclient.dll併發
客戶端發佈方法:socket
1.在客戶端程序中加上uses MidasLib;(添加MidasLib的目的是省去發佈Midas.dll)tcp
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屬性其實是內存地址,並非一個簡單的數字。
如下代碼中if .. then 裏面的內容是關鍵。
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;
或者也能夠這樣:
procedure TServerContainer1.DSServer1Connect(DSConnectEventObject: TDSConnectEventObject); var ClientConnection: TDBXClientInfo; Val: TTCP_KeepAlive; Ret: DWord; begin ClientConnection := DSConnectEventObject.ChannelInfo.ClientInfo; AddLog( ClientConnection.IpAddress +':'+ ClientConnection.ClientPort +'登陸服務器'); UpdateLinkToList(ClientConnection.IpAddress +':'+ ClientConnection.ClientPort ,IntToStr(DSConnectEventObject.ChannelInfo.Id) ,0); end;
4、DataSnap中的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;
固然,以後的版本已無需手動加入心跳包代碼了,由於TDSTCPServerTransport自己已經增長了相應的屬性和功能,如:
https://www.cnblogs.com/m0488/p/9759702.html