簡體   English   中英

使用-DELPHI中的Indy組件(idTCPServer / idTCPClient)的單服務器與多客戶端實時監視系統

[英]One-Server vs Multi Clients Realtime monitoring System using - Indy components(idTCPServer/idTCPClient) in DELPHI

我在構建Indy Server / Clients實時監視系統時遇到了一個重要問題...我正在使用DELPHI 2010,而Indy版本是10.5.5 .........我的目的是許多客戶端PC連續發送屏幕截圖(4〜10fps)到服務器,服務器必須將這些屏幕截圖幀發送到某些監控PC。 換一種說法....

Many clients -----send streams--------> to Server
Some monitors <---receive streams----- from Server

當然,在一個客戶端和一台帶有服務器的監視器的情況下工作正常……但是,如果連接另一台客戶端或監視器,則服務器將引發異常“地址000000000 .....上的訪問沖突”或“無效的指針”操作”,並斷開客戶端或顯示器的連接。

結果,客戶端或監視器將與服務器斷開連接。

我使用了idTCPClient組件,使用線程描述了客戶端和監視代碼,用於發送和接收流。 我確定客戶端和監視器端的代碼沒有問題...但是我認為服務器端絕對有問題。

對於服務器端,我使用了兩個TidTCPServer控件:一個是從客戶端PC接收流,另一個是發送流以監視PC。

服務器代碼如下

{one side----idTCPServerRecv is to receive screenshot streams from clients}

procedure TIndyServerForm.IdTCPServer_RecvExecute(AContext: TIdContext);
var
  Hb: TIdIOHandler;
  TempStr: TStrings;
begin

  Hb := AContext.Connection.IOHandler;
  if Not Hb.InputBufferIsEmpty then
  Begin
    Hb.CheckForDisconnect(True, True);
    AContext.Connection.IOHandler.CheckForDisconnect(True, True);

    recv_Stream := TMemoryStream.Create;
    recv_Stream.Clear;
    if (ReceiveStream(AContext, TStream(recv_Stream)) = False) then
    begin
      ROutMsg :=AContext.Binding.PeerIP+' -> receiving failed: ' + IntToStr(recv_Stream.Size)+'byte';
      recv_Stream.Free;
      Exit;
    end;
    if recv_Stream.Size < 1024 then
    begin
      recv_Stream.Seek(0, soFromBeginning);

      ROutMsg :=AContext.Binding.PeerIP+' -> captionString received('+
                IntToStr(recv_Stream.Size)+' byte) : "'+StringFromStream(TStream(recv_Stream))+'"';
      recv_Stream.Free;

    end
    else
    begin
      ROutMsg :=AContext.Binding.PeerIP+' -> screenshot received: ' + KBStr(recv_Stream.Size)+' KB';
      if G_Sendable = False then
      begin
        send_Stream:=TMemoryStream.Create;
        send_Stream.Clear;
        recv_Stream.Seek(0, soFromBeginning);
        send_Stream.Seek(0, soFromBeginning);
        send_Stream.CopyFrom(recv_Stream, recv_Stream.Size);
        G_Sendable :=True;
      end;
      recv_Stream.Free;

    end;
  end;
  Application.ProcessMessages;
  WaitForSingleObject(Handle, 1);
end;


{another side----idTCPServerSend is to send screenshot streams to monitors}

procedure TIndyServerForm.IdTCPServer_SendExecute(AContext: TIdContext);
begin
    if G_Sendable then
    begin
      send_Stream.Seek(0,soFromBeginning);
      if (SendStream(AContext, TStream(send_Stream)) = False) then
      begin
        SOutMsg :=AContext.Binding.PeerIP+' -> sending failed -> ' + KBStr(send_Stream.Size)+' KB';
        send_Stream.Free;
        G_Sendable :=False;
        Exit;
      end;
      SOutMsg :=AContext.Binding.PeerIP+' -> sending successful-> ' + KBStr(send_Stream.Size)+' KB';
      send_Stream.Free;
      G_Sendable :=False;
    end;
    Application.ProcessMessages;
    WaitForSingleObject(Handle, 1);


end;

對於具有實時流交換的多客戶端連接,我該怎么辦...每個客戶端PC每秒發送屏幕截圖流4〜10次...並且這些流必須發送到相應的監視器,請給我建議。...

在“監視器”端,線程中的TIdTCPClient可以偵聽來自服務器的傳入屏幕截圖數據。 我在這里發布了有關使用Indy(源代碼)進行服務器端推送消息傳遞技術的博客文章:

Indy 10 TIdTCPServer:服務器端消息推送示例

需要其他服務器端代碼才能將傳入的數據定向到監視客戶端。 實際上,您只需要在上下文中添加“標簽”(可能是布爾標志),即可表明該連接是否正在發送或監視屏幕截圖數據。 SO上的其他問題已經回答了如何為連接上下文分配自定義屬性並對其進行迭代。

您的代碼甚至都不是線程安全的,這就是為什么您會出錯。 IdTCPServer_Recv每個客戶端線程IdTCPServer_Recv將其各自的快照接收到單個共享的recv_Stream變量,然后將該數據復制到單個共享的send_Stream變量。 然后,連接到IdTCPServer_Send所有客戶端將同時讀取並發送相同的send_Stream 您到處都在踐踏記憶。

您需要使用局部變量而不是共享變量來接收每個屏幕快照,並且需要為每個監視器客戶端使用單獨的 TStream對象。 不要使用共享的TStream進行發送,當然也不要使用全局的布爾變量讓每個監視器客戶端使用它。 IdTCPServer_RecvExecute()主動創建一個新的TMemoryStream對象並將其傳遞給需要發送當前屏幕截圖的每個監視器客戶端。

嘗試更多類似這樣的方法:

uses
  ..., IdThreadSafe;

type
  TMonitorContext = class(TIdServerContext)
  public
    Screenshots: TIdThreadSafeObjectList;
    ScreenshotEvent: THandle;
    constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TThreadList = nil); override;
    destructor Destroy; override;
  end;

  TScreenshotInfo = class
  public
    ClientIP: string;
    ClientPort: TIdPort;
    Data: TMemoryStream;
    constructor Create;
    destructor Destroy; override;
  end;

constructor TMonitorContext.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TThreadList);
begin
  inherited;
  Screenshots := TIdThreadSafeObjectList.Create;
  Screenshots.OwnsObjects := True;
  ScreenshotEvent := CreateEvent(null, True, False, nil);
end;

destructor TMonitorContext.Destroy;
begin
  Screenshots.Free;
  CloseHandle(ScreenshotEvent);
  inherited;
end;

constructor TScreenshotInfo.Create;
begin
  inherited;
  Data := TMemoryStream.Create;
end;

destructor TScreenshotInfo.Destroy;
begin
  Data.Free;
  inherited;
end;

{one side----idTCPServerRecv is to receive screenshot streams from clients}

procedure TIndyServerForm.IdTCPServer_RecvExecute(AContext: TIdContext);
var
  recv_stream: TMemoryStream;
  monitors, queue: TList;
  i: Integer;
  screenshot: TScreenshotInfo;
  monitor: TMonitorContext;
begin
  recv_stream := TMemoryStream.Create;
  try
    if not ReceiveStream(AContext, recv_stream) then
    begin
      ROutMsg := AContext.Binding.PeerIP + ' -> receiving failed: ' + IntToStr(recv_Stream.Size) + ' byte';
      Exit;
    end;
    if recv_Stream.Size < 1024 then
    begin
      recv_Stream.Position := 0;
      ROutMsg := AContext.Binding.PeerIP + ' -> captionString received(' + 
                IntToStr(recv_Stream.Size) + ' byte) : "' + StringFromStream(recv_Stream) + '"';
    end
    else
    begin
      ROutMsg := AContext.Binding.PeerIP + ' -> screenshot received: ' + KBStr(recv_Stream.Size) + ' KB';

      monitors := IdTCPServer_Send.Contexts.LockList;
      try
        // alternatively, only queue the screenshot to particular monitors
        // that are actually interested in this client...
        for i := 0 to monitors.Count-1 do
        begin
          monitor := TMonitorContext(monitors[i]);
          screenshot := TScreenshotInfo.Create;
          try
            recv_Stream.Position := 0;
            screenshot.Data.CopyFrom(recv_stream, 0);
            screenshot.Data.Position := 0;
            queue := monitor.Screenshots.LockList;
            try
              queue.Add(screenshot);
              SetEvent(monitor.ScreenshotEvent);
            finally
              monitor.Screenshots.UnlockList;
            end;
          except
            screenshot.Free;
          end;
        end;
      finally
        IdTCPServer_Send.Contexts.UnlockList;
      end;
    end;
  finally
    recv_stream.Free;
  end;
end;

{another side----idTCPServerSend is to send screenshot streams to monitors}

procedure TIndyServerForm.FormCreate(Sender: TObject);
begin
  IdTCPServer_Send.ContextClass := TMonitorContext;
end;

procedure TIndyServerForm.IdTCPServer_SendExecute(AContext: TIdContext);
var
  monitor: TMonitorContext;
  queue: TList;
  i: Integer;
  screenshot: TScreenshotInfo;
begin
  monitor := TMonitorContext(AContext);
  if WaitForSingleObject(monitor.ScreenshotEvent, 1000) <> WAIT_OBJECT_0 then Exit;
  screenshot := nil;
  try
    queue := monitor.Screenshots.LockList;
    try
      if queue.Count > 0 then
      begin
        screenshot := TScreenshotInfo(queue[0]);
        queue.Delete(0);
      end;
      if queue.Count = 0 then
        ResetEvent(monitor.ScreenshotEvent);
    finally
      monitor.Screenshots.UnlockList;
    end;
    if screenshot = nil then Exit;
    // you should send screenshot.ClientIP and screenshot.ClientPort to
    // this monitor so it knows which client the screenshot came from...
    if not SendStream(AContext, screenshot.Data) then
    begin
      SOutMsg := AContext.Binding.PeerIP + ' -> sending failed -> ' + KBStr(screenshot.Data.Size) + ' KB';
      Exit;
    end;
    SOutMsg := AContext.Binding.PeerIP + ' -> sending successful-> ' + KBStr(screenshot.Data.Size) + ' KB';
  finally
    screenshot.Free;
  end;
end;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM