zoukankan      html  css  js  c++  java
  • Delphi2010中DataSnap技术网摘

    一、为DataSnap系统服务程序添加描述

    这几天一直在研究Delphi 2010的DataSnap,感觉功能真是很强大,现在足有理由证明Delphi7该下岗了。

    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;


    二、DataSnap服务端和客户端发布分发方法

    服务器发布方法:

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

    2.如果用的是火鸟数据库,只需拷贝dbxfb.dll和fbclient.dll,如果用的是SQLite,则什么都不用拷贝。

     分发的服务器软件只需三个文件:你的服务器程序、dbxfb.dll 和 fbclient.dll

      

    客户端发布方法:

    1.在客户端程序中加上uses MidasLib;(添加MidasLib的目的是省去发布Midas.dll)

    2.如果服务器使用了http协议作为DataSnap通讯的话,还需在客户端程序中加上users DSHTTPLayer,如果使用tcp协议,无需此步骤。

    分发的客户端软件只需一个文件:你的客户端程序 

    服务器和客户端无需Midas.dll,也不需要注册regsvr32 Midas.dll,看来Delphi2010的datasnap抛弃使用COM真是进步不少!

    三、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;


     

    四、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技术使用心跳包功能!敬请关注

    五、建立稳定服务程序之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死连接和数据库的死连接都清除了,我们的系统将能长期稳定的运行。


    六、加强服务程序对访问者的控制能力 

    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本身已经增加了相应的属性和功能,如:

     

    版权声明:本文为博主原创文章,未经博主允许不得转载。

  • 相关阅读:
    Advanced Configuration Tricks
    Reviewing the Blog Module
    Editing and Deleting Data
    Making Use of Forms and Fieldsets
    Understanding the Router
    SQL Abstraction and Object Hydration
    Preparing for Different Databases
    Java学习理解路线图
    Openstack学习历程_1_视频
    CentOS安装Nginx负载
  • 原文地址:https://www.cnblogs.com/xieyunc/p/4839596.html
Copyright © 2011-2022 走看看