You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用TIdMappedPortTCP实现按客户端IP分流至不同目标?

解决方案:基于TIdMappedPortTCP的动态IP映射代理

核心问题分析

你之前的尝试都没命中关键:修改AContext.Binding是操作客户端的连接参数,而非TIdMappedPortTCP要转发到的后端服务器参数。TIdMappedPortTCP的工作逻辑是:接受客户端连接后,主动创建一个到后端服务器的新连接,你需要修改的是这个新连接的目标地址,而非客户端侧的连接信息。

正确实现方式

方法1:利用OnCreateMappedClient事件(推荐)

新版本Indy的TIdMappedPortTCP提供了OnCreateMappedClient事件,该事件在创建后端转发连接之前触发,正好可以在这里根据客户端IP动态设置目标地址。

代码示例:

procedure TMapperApplication.MapperCreateMappedClient(Sender: TIdMappedPortTCP;
  var VDestIP: string; var VDestPort: Integer; AClientContext: TIdContext);
var
  IP: string;
  I: Integer;
begin
  IP := AClientContext.Binding.PeerIP;

  // 根据客户端IP匹配目标服务器
  for I := 0 to High(Servers) do
  begin
    if IP = Servers[I].Source then
    begin
      VDestIP := Servers[I].Host;
      VDestPort := Servers[I].Port;
      // 输出日志
      TConsole.TextColor := TConsoleColor.Yellow;
      TConsole.Write('Mapped connection from ' + IP + ' to ');
      TConsole.ResetStyle;
      TConsole.WriteLn(Format('%S:%D', [VDestIP, VDestPort]));
      Exit;
    end;
  end;

  // 处理默认映射
  if EnableDefault then
  begin
    // 建议将数组最后一项设为默认服务,避免越界
    VDestIP := Servers[High(Servers)].Host;
    VDestPort := Servers[High(Servers)].Port;
    TConsole.WriteLn('Mapped connection from ' + IP + ' to default host.');
    Exit;
  end;

  // 无匹配时拒绝连接
  VDestIP := ''; // 设置为空则TIdMappedPortTCP会自动断开客户端连接
  TConsole.TextColor := TConsoleColor.Red;
  TConsole.WriteLn('Connection from ' + IP + ' dropped: No mapping found.');
  TConsole.ResetStyle;
end;

方法2:自定义继承TIdMappedPortTCP

如果你的Indy版本没有OnCreateMappedClient事件,可以自定义组件重写CreateMappedClient方法:

type
  TCustomMappedPortTCP = class(TIdMappedPortTCP)
  protected
    function CreateMappedClient(AClientContext: TIdContext): TIdTCPConnection; override;
  end;

function TCustomMappedPortTCP.CreateMappedClient(AClientContext: TIdContext): TIdTCPConnection;
var
  IP: string;
  I: Integer;
  DestIP: string;
  DestPort: Integer;
begin
  IP := AClientContext.Binding.PeerIP;
  DestIP := '';
  DestPort := 0;

  // 匹配目标服务器逻辑
  for I := 0 to High(Servers) do
  begin
    if IP = Servers[I].Source then
    begin
      DestIP := Servers[I].Host;
      DestPort := Servers[I].Port;
      Break;
    end;
  end;

  // 应用默认映射
  if (DestIP = '') and EnableDefault then
  begin
    DestIP := Servers[High(Servers)].Host;
    DestPort := Servers[High(Servers)].Port;
  end;

  // 无匹配则拒绝连接
  if DestIP = '' then
  begin
    Result := nil;
    TConsole.TextColor := TConsoleColor.Red;
    TConsole.WriteLn('Connection from ' + IP + ' dropped: No mapping found.');
    TConsole.ResetStyle;
    Exit;
  end;

  // 创建并配置后端连接
  Result := inherited CreateMappedClient(AClientContext);
  Result.Host := DestIP;
  Result.Port := DestPort;
end;

原代码的问题修正

  • 默认映射部分的Servers[I].Host存在越界风险:循环结束后I的值为High(Servers)+1,直接访问会触发数组越界,建议单独指定默认项(比如数组最后一个元素)。
  • Mapper.Active:=true;无需在事件中重复设置,组件启动时已激活,重复调用无意义。

后续域名检测的实现思路

如果要实现基于域名(如server1.site.com)的映射,分两种场景处理:

  1. HTTP代理:改用TIdHTTPProxyServer组件,在OnHTTPBeforeCommand事件中解析HTTP请求的Host头,再动态转发到对应后端。
  2. HTTPS代理(SNI):借助TIdSSLIOHandlerSocketOpenSSL,在OnGetSSLContext事件中通过OpenSSL API提取TLS握手包中的SNI字段,以此判断目标域名。

GStack.HostByAddress返回空的解决办法

反向DNS解析失败通常是因为客户端IP无反向记录、系统DNS配置错误,可直接调用Linux系统函数getnameinfo实现解析:

function ReverseDNS(const AIP: string): string;
var
  hints: addrinfo;
  res: Paddrinfo;
  err: Integer;
  HostName: array[0..NI_MAXHOST] of Char;
begin
  Result := '';
  FillChar(hints, SizeOf(hints), 0);
  hints.ai_family := AF_UNSPEC;
  hints.ai_socktype := SOCK_STREAM;
  hints.ai_flags := AI_NUMERICHOST;

  err := getaddrinfo(PChar(AIP), nil, @hints, @res);
  if err = 0 then
  begin
    err := getnameinfo(res^.ai_addr, res^.ai_addrlen, HostName, SizeOf(HostName), nil, 0, NI_NAMEREQD);
    if err = 0 then
      Result := StrPas(HostName);
    freeaddrinfo(res);
  end;
end;

内容的提问来源于stack exchange,提问作者Codrut

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 14:15:55