一种选择可能是实现自定义TIdScheduler
类从TIdSchedulerofThread...
组件之一派生并覆盖其虚拟AcquireYarn()
方法之一:
引发EAbort
异常,如果调度的ActiveYarns
名单已经达到了允许的最大连接数。不过,这可能会导致TIdTCPServer
监听线程中的循环太紧。为了缓解这种情况,您可以在方法中放置一个小型定时器,并且只在列表保持最短时间的情况下才会引发异常。
块调用线程(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()
未返回接受的客户端,一切都将正常工作,并且只是按预期进行阻止。
如果客户端连接,然后从未发送数据或尝试从连接读取(可能是因为套接字在客户端关闭)会发生什么?我想这需要一个“连接超时”来保护服务器免受DOS攻击...... – mjn
@mjn这些都是内部的,并且基于持久连接。 – Roddy
为什么不直接创建一个从'TIdTcpServer'派生的自定义类来实现你需要的功能呢?即实现一个'IsConnectionAllowed'方法并从修改的OnConnect? –