Delphi2010中DataSnap技术网摘

Delphi2010中DataSnap技术网摘

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

相关文章
相关标签/搜索