2014-02-06 70 views
0

我想限制TIdTcpServer可以采用的传入连接的数量,但我需要应用的规则有点复杂,所以我认为MaxConnections属性不适用于我。TIdTcpServer连接限制

我有一个应用程序运行N个服务器,每个服务器在不同的端口上使用不同的协议。我需要限制所有N台服务器的客户端总数。例如,有16个服务器和16个客户端,我允许每个客户端上有一个客户端,或者16个全部在一台服务器上。

这是可能的,我可以操纵MaxConnections动态地解决这个问题(例如,它们都设置为零,当我确定我们是“全”,再回到16或什么时候我们不充分,但这种感觉有点太调皮。

是否有某种虚拟IsConnectionAllowed的方法,我可以用我自己的逻辑覆盖,或在连接过程中适当的地方,我可以抛出一个异常,如果我确定已超出限制?

+0

如果客户端连接,然后从未发送数据或尝试从连接读取(可能是因为套接字在客户端关闭)会发生什么?我想这需要一个“连接超时”来保护服务器免受DOS攻击...... – mjn

+0

@mjn这些都是内部的,并且基于持久连接。 – Roddy

+0

为什么不直接创建一个从'TIdTcpServer'派生的自定义类来实现你需要的功能呢?即实现一个'IsConnectionAllowed'方法并从修改的OnConnect? –

回答

1

创建一个新组件 - 例如TIdTCPServerCollection - 它是所有服务器组件的“所有者”。

在此组件中,声明存储可用 - 当前未使用的连接计数的线程安全属性。

在服务器连接和断开逻辑中,递减/递增此变量,并设置MaxConnections以反映新的限制。

+0

事实证明,这将无法正常工作,因为MaxConnections = 0意味着无限的连接。因此,如果服务器1有全部16个连接,我不能使用MaxConnections来阻止其他服务器接受它。 – Roddy

+1

@Roddy Indy是这么做的。客户端连接,indy检查是否已经达到最大连接数并基于该连接断开客户端。在设备IdCustomTCPServer中搜索“检查MaxConnections”。 –

+0

@ioan你说得对。在这种情况下(因为MaxConnections不能用零当前连接阻塞服务器)只需立即从OnExecute事件返回可能是正确的方法。 – Roddy

1

一种选择可能是实现自定义TIdScheduler类从TIdSchedulerofThread...组件之一派生并覆盖其虚拟AcquireYarn()方法之一:

  1. 引发EAbort异常,如果调度的ActiveYarns名单已经达到了允许的最大连接数。不过,这可能会导致TIdTCPServer监听线程中的循环太紧。为了缓解这种情况,您可以在方法中放置一个小型定时器,并且只在列表保持最短时间的情况下才会引发异常。

  2. 块调用线程(TIdTCPServer监听线程),直到ActiveYarns比你的最大连接数限制纱线越少,则调用inherited方法正常返回一个新的TIdYarn对象。

例如:

type 
    TMyScheduler = class(TIdSchedulerOfThreadDefault) 
    public 
    function AcquireYarn: TIdYarn; override; 
    end; 

function TMyScheduler.AcquireYarn: TIdYarn; 
begin 
    if not ActiveYarns.IsCountLessThan(SomeLimit) then 
    begin 
    Sleep(1000); 
    if not ActiveYarns.IsCountLessThan(SomeLimit) then 
     Abort; 
    end; 
    Result := inherited; 
end; 

这个类的一个实例。然后分配到所有服务器的Scheduler属性。 TIdTCPServer在接受新的客户端连接之前调用AcquireYarn()

另一种选择,仅适用于Windows,是从TIdStackWindows派生新类TIdStack并覆盖其虚拟Accept()方法使用的Winsock的WSAAccept()功能,而不是它的accept()功能。 WSAAccept()允许您分配一个回调函数,该函数根据传递给回调(QOS等)的标准来决定是否接受或拒绝新客户端。该回调可以检查您为维持多少个活动连接运行的全局计数器(或者总计所有服务器的活动Contexts计数),然后在达到限制时返回CF_REJECT,否则返回CF_ACCEPT。然后,您可以使用IdStack单元中的SetStackClass()函数将您的类指定为所有Indy套接字连接的活动堆栈。

例如:

type 
    TMyStack = class(TIdStackWindows) 
    public 
    function Accept(ASocket: TIdStackSocketHandle; var VIP: string; var VPort: TIdPort; var VIPVersion: TIdIPVersion): TIdStackSocketHandle; override; 
    end; 

function MyAcceptCallback(lpCallerId: LPWSABUF; lpCallerData: LPWSABUF; lpSQOS, pGQOS: LPQOS; lpCalleeId, lpCalleeData: LPWSABUF; g: PGROUP; dwCallbackData: DWORD_PTR): Integer; stdcall; 
begin 
    if NumActiveConnections >= SomeLimit then 
    Result := CF_REJECT 
    else 
    Result := CF_ACCEPT; 
end; 

function TMyStack.Accept(ASocket: TIdStackSocketHandle; var VIP: string; var VPort: TIdPort; var VIPVersion: TIdIPVersion): TIdStackSocketHandle; 
var 
    LSize: Integer; 
    LAddr: SOCKADDR_STORAGE; 
begin 
    LSize := SizeOf(LAddr); 
    //Result := IdWinsock2.accept(ASocket, IdWinsock2.PSOCKADDR(@LAddr), @LSize); 
    Result := IdWinsock2.WSAAccept(ASocket, IdWinsock2.PSOCKADDR(@LAddr), @LSize, @MyAcceptCallback, 0); 
    if Result <> INVALID_SOCKET then begin 
    case LAddr.ss_family of 
     Id_PF_INET4: begin 
     VIP := TranslateTInAddrToString(PSockAddrIn(@LAddr)^.sin_addr, Id_IPv4); 
     VPort := ntohs(PSockAddrIn(@LAddr)^.sin_port); 
     VIPVersion := Id_IPv4; 
     end; 
     Id_PF_INET6: begin 
     VIP := TranslateTInAddrToString(PSockAddrIn6(@LAddr)^.sin6_addr, Id_IPv6); 
     VPort := ntohs(PSockAddrIn6(@LAddr)^.sin6_port); 
     VIPVersion := Id_IPv6; 
     end; 
     else begin 
     CloseSocket(Result); 
     Result := INVALID_SOCKET; 
     IPVersionUnsupported; 
     end; 
    end; 
    end; 
end; 

initialization 
    SetStackClass(TMyStack); 

这样,印地将再也看不到任何被拒绝的客户端连接的一切,你不必担心执行的TIdTCPServer或它的各种依赖内部的任何其他黑客。只要TIdStack.Accept()未返回接受的客户端,一切都将正常工作,并且只是按预期进行阻止。