zoukankan      html  css  js  c++  java
  • Delphi2010中DataSnap高级技术(转)

    一、 为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;

     

    一、 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通讯的话,还需在客户端程序中加上 uses DSHTTPLayer;如果使用tcp协议,无需此步骤

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

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

    发布程序竟是如此简单!!!

     

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

     

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

     

     

    七、 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

    说明:服务器对所有客户端连接使用一个实例,不是线程安全的。所以要自己控制客户端并发调用的问题(可以使用互斥、原子量等方法),让客户端的调用排成一队使用服务器资源。

     适用环境:这个设置不是线程安全的!!

    配置较低的服务器 

    服务器连接的另一端只能是单线程工作的模式 

    提前祝大家2010年春节快乐!

     

    本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/sunstone/archive/2010/02/02/5282666.aspx

  • 相关阅读:
    nginx能访问html静态文件但无法访问php文件
    LeetCode "498. Diagonal Traverse"
    LeetCode "Teemo Attacking"
    LeetCode "501. Find Mode in Binary Search Tree"
    LeetCode "483. Smallest Good Base" !!
    LeetCode "467. Unique Substrings in Wraparound String" !!
    LeetCode "437. Path Sum III"
    LeetCode "454. 4Sum II"
    LeetCode "445. Add Two Numbers II"
    LeetCode "486. Predict the Winner" !!
  • 原文地址:https://www.cnblogs.com/fhweixin/p/13743062.html
Copyright © 2011-2022 走看看