2017-01-16 184 views
0

當我們將自定義屬性分配給TIdTCPServer上的連接的上下文時,如何以線程安全的方式訪問此屬性(讀/寫)?例如:TIdTCPServer訪問自定義AContext屬性

自定義屬性:

type 
    Local_Socket = class(TIdContext) 
    public 
    Tunnel_Requested: bool; 
    Remote_Tunnel: TIdContext; 
    end; 

type 
    Remote_Socket = class(TIdContext) 
    public 
    Local_Tunnel: TIdContext; 
    end; 

分配他們:

procedure TForm1.IdTCPServer1Connect(AContext: TIdContext); 
begin 
     if AContext.Binding.PeerIP = '127.0.0.1' then 
     begin 
     Local_Socket(AContext).Tunnel_Requested:= false; 
     Local_Socket(AContext).Remote_Tunnel:= Nil; 
     end 
     else 
     begin 
     AssignRemoteTunnel(AContext); 
     end; 
end; 

procedure TForm1.AssignRemoteTunnel(AContext: TIdContext); 
var 
    iContext: integer; 
    List: TIdContextList; 
    Assigned: bool; 
begin 
    Assigned:= false; 
    List:= IdTCPServer1.Contexts.LockList; 
    try 
    for iContext:= 0 to List.Count - 1 do 
    begin 
     if (TIdContext(List[iContext]).Binding.PeerIP = '127.0.0.1') and 
     (Local_Socket(List[iContext]).Remote_Tunnel = Nil) then 
     begin 
     Local_Socket(List[iContext]).Remote_Tunnel:= AContext; 
     Remote_Socket(AContext).Local_Tunnel:= TIdContext(List[iContext]); 
     Assigned:= true; 
     end; 
    end; 
    if Assigned = false then 
     AContext.Connection.Disconnect; 
    finally 
    IdTCPServer1.Contexts.UnlockList; 
    end; 
end; 

我嘗試實現這個代碼,如果連接是本地(127.0.0.1),我需要什麼將其重定向到遠程連接,這將在下面的代碼中請求。一旦遠程連接到達服務器上,我AssignRemoteTunnel,與遠程連接local_socket.remote_tunnel財產,並與當地連接remote_socket.local_tunnel,這樣我可以在隧道之間的透明通信相關:

procedure TForm1.IdTCPServer1Execute(AContext: TIdContext); 
var 
    Buffer: TIdBytes; 
begin 
     if AContext.Binding.PeerIP = '127.0.0.1' then 
     begin 
      if Local_Socket(AContext).Tunnel_Requested = false then 
      begin 
      TunnelSocket.Connection.IOHandler.Write(REQ_TUNNEL); 
      Local_Socket(AContext).Tunnel_Requested:= true; 
      end; 
      if (Local_Socket(AContext).Remote_Tunnel <> Nil) and 
      (Local_Socket(AContext).Remote_Tunnel.Connection.Connected) then 
      begin 
      AContext.Connection.IOHandler.CheckForDataOnSource(500); 
      if not AContext.Connection.IOHandler.InputBufferIsEmpty then 
      begin 
       AContext.Connection.IOHandler.InputBuffer.ExtractToBytes(Buffer); 
       Local_Socket(AContext).Remote_Tunnel.Connection.IOHandler.Write(Buffer); 
      end; 
end; 

這裏我正在觀察是否分配了一個remote_tunnel屬性來通過這個remote_tunnel發送緩衝區......但是當我讀取這個屬性時,也許我正在將它寫在AssignRemoteTunnel過程中。這個可以嗎?

+0

線程安全可以通過TMonitor.Lock實現 - https://mikejustin.wordpress.com/2010/11/21/thread-synchronization-with-guarded-在德爾福塊/但真正的問題似乎是類型安全。 'Local_Socket(AContext).Tunnel_Requested' - 是否正確?我認爲你應該檢查'IF AContext IS Local_Socket THEN BEGIN ...'或者更好在這裏做'WITH AContext AS Local_Socket DO BEGIN Tunnel_Requested:= false; Remote_Tunnel:=無; END;' –

+0

感謝您的信息。關於TMonitor.Lock,我在考慮使用TIdThreadSafe類,這樣我就可以保留Indy類的所有內容。 – user2864778

+0

我不熟悉Indy ......但你爲什麼認爲你的上下文不是標準的Indy類,而是你自己的擴展類? –

回答

2

您不能只鍵入指向另一個類類型的TIdContext指針,除非指向的對象實際上是該類類型的開頭。 TIdTCPServer有一個ContextClass屬性來指定TIdContext對象的類類型,但是您只能爲其指定一個類類型,因此您不能讓某些使用Local_Socket的客戶端以及某些使用Remote_Socket的客戶端代替。你需要將它們合併成一個類。

確保您使用TIdTCPServer.OnDisconnect事件來取消關聯您的Context對象。

此外,請確保使用指針Tunnel的任何代碼都是線程安全的,因爲TIdTCPServer是多線程的,並且TCP連接可以在其他線程仍在訪問時隨時退出。所以,這可能意味着在每個想要通過Tunnel讀取/寫入內容時,都會將TCriticalSection添加到每個TMyContext或使用TMonitor來鎖定訪問。

嘗試更多的東西是這樣的:

type 
    TMyContext = class(TIdServerContext) // <-- must derive from TIdServerContext, not TIdContext itself 
    public 
    IsLocal: Boolean; 
    Tunnel: TIdContext; 
    WaitingForTunnel: Boolean; 
    end; 

procedure TForm1.FormCreate(Sender: TObject); 
begin 
    IdTCPServer1.ContextClass := TMyContext; // <-- must be done BEFORE the server is activated! 
    IdTCPServer1.Active := True; 
end; 

procedure TForm1.IdTCPServer1Connect(AContext: TIdContext); 
var 
    Ctx: TMyContext; 
    PeerIP: string; 
    LocalIPs: TIdStackLocalAddressList; 
begin 
    Ctx := TMyContext(AContext); 

    // Note: unless your server is listening specifically on 127.0.0.1 only, 
    // you should match the connected PeerIP to all IPs reported by 
    // GStack.GetLocalAddressList(), not just 127.0.0.1, since the client 
    // could be connecting from any local adapter/interface... 
    // 
    PeerIP := AContext.Binding.PeerIP; 
    Ctx.IsLocal := (PeerIP = '127.0.0.1') or (PeerIP = '0:0:0:0:0:0:0:1') or (PeerIP = '::1'); 
    if not Ctx.IsLocal then 
    begin 
    LocalIPs := TIdStackLocalAddressList.Create; 
    try 
     GStack.GetLocalAddressList(LocalIPs); 
     Ctx.IsLocal := (LocalIPs.IndexOfIP(PeerIP) <> -1); 
    finally 
     LocalIPs.Free; 
    end; 
    end; 
    if Ctx.IsLocal then 
    begin 
    Ctx.WaitingForTunnel := True; 

    // NOTE: unless REQ_TUNNEL is a single Byte, you need to serialize 
    // access to TunnelSocket.Connection.IOHandler.Write() so that multiple 
    // requests cannot overlap on top of each other, corrupting the 
    // communications on that connection! 
    // 
    TMonitor.Enter(TunnelSocket); 
    try 
     TunnelSocket.Connection.IOHandler.Write(REQ_TUNNEL); 
    finally 
     TMonitor.Leave(TunnelSocket); 
    end; 
    end 
    else 
    AssignRemoteTunnel(AContext); 
end; 

procedure TForm1.IdTCPServer1Disconnect(AContext: TIdContext); 
var 
    i: integer; 
    List: TIdContextList; 
    Ctx: TIdContext; 
begin 
    List := IdTCPServer1.Contexts.LockList; 
    try 
    for I := 0 to List.Count - 1 do 
    begin 
     Ctx := TIdContext(List[i]); 
     if Ctx <> AContext then 
     begin 
     TMonitor.Enter(Ctx); 
     try 
      if Ctx.Tunnel = AContext then 
      begin 
      Ctx.Tunnel := nil; 
      Exit; 
      end; 
     finally 
      TMonitor.Leave(Ctx); 
     end; 
     end; 
    end; 
    finally 
    IdTCPServer1.Contexts.UnlockList; 
    end; 
end; 

procedure TForm1.AssignRemoteTunnel(AContext: TIdContext); 
var 
    i: integer; 
    List: TIdContextList; 
    Ctx: TIdContext; 
begin 
    Assigned := False; 
    List := IdTCPServer1.Contexts.LockList; 
    try 
    for I := 0 to List.Count - 1 do 
    begin 
     Ctx := TIdContext(List[i]); 
     if (Ctx <> AContext) and Ctx.IsLocal and Ctx.WaitingForTunnel then 
     begin 
     TMonitor.Enter(Ctx); 
     try 
      Ctx.Tunnel := AContext; 
      Ctx.WaitingForTunnel := False; 
     finally 
      TMonitor.Leave(Ctx); 
     end; 
     TMonitor.Enter(AContext); 
     try 
      TMyContext(AContext).Tunnel := Ctx; 
     finally 
      TMonitor.Leave(AContext); 
     end; 
     Exit; 
     end; 
    end; 
    finally 
    IdTCPServer1.Contexts.UnlockList; 
    end; 
    AContext.Connection.Disconnect; 
end; 

procedure TForm1.IdTCPServer1Execute(AContext: TIdContext); 
var 
    Ctx: TMyContext; 
    Buffer: TIdBytes; 
begin 
    Ctx := TMyContext(AContext); 
    if Ctx.Tunnel = nil then 
    begin 
    if Ctx.IsLocal and Ctx.WaitingForTunnel then 
     IndySleep(50) 
    else 
     AContext.Connection.Disconnect; 
    Exit; 
    end; 
    if AContext.Connection.IOHandler.InputBufferIsEmpty then 
    begin 
    AContext.Connection.IOHandler.CheckForDataOnSource(500); 
    if AContext.Connection.IOHandler.InputBufferIsEmpty then Exit; 
    end; 
    AContext.Connection.IOHandler.InputBuffer.ExtractToBytes(Buffer); 
    TMonitor.Enter(Ctx); 
    try 
    if Ctx.Tunnel <> nil then 
     Ctx.Tunnel.Connection.IOHandler.Write(Buffer); 
    finally 
    TMonitor.Leave(Ctx); 
    end; 
end;