【问题标题】:One-Server vs Multi Clients Realtime monitoring System using - Indy components(idTCPServer/idTCPClient) in DELPHI单服务器 vs 多客户端实时监控系统使用 - DELPHI 中的 Indy 组件(idTCPServer/idTCPClient)
【发布时间】:2016-02-24 14:53:32
【问题描述】:

我在构建 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 次截图流... 并且这些流必须发送到相应的监视器 请给我建议....

【问题讨论】:

    标签: multithreading delphi indy indy10 server-push


    【解决方案1】:

    在“监视器”端,线程中的 TIdTCPClient 可以侦听来自服务器的传入屏幕截图数据。我在这里发布了一篇关于 Indy 的服务器端推送消息技术的博客文章(源代码):

    Indy 10 TIdTCPServer: Server-side message push example

    需要额外的服务器端代码来将传入数据定向到监控客户端。实际上,您只需要在上下文中添加“标签”(可以是布尔标志),指示此连接是否正在发送或监视屏幕截图数据。如何将自定义属性分配给连接上下文并对其进行迭代已在 SO 上的其他问题中得到解答。

    【讨论】:

    • 感谢您的建议。但我想知道的是为什么会在服务器端发生异常......
    【解决方案2】:

    您的代码甚至还没有接近线程安全,这就是您遇到错误的原因。 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;
    

    【讨论】:

    • 非常感谢。你真的给了我重要的建议和帮助。事实上,在发布后我发现了一个与线程安全相关的问题.....
    • 您好,先生!我已经尝试过你的修改代码,但是你的代码在我的 DELPHI 和 INDY 环境中不起作用。比如......TIdThreadSafeObjectList、TIdContextThreadList都不存在......我该怎么办???
    • TIdContextThreadList 是在更高版本中添加的。用 TThreadList 替换它。 TIdThreadSafeObjectList 在 IdThreadSafe 单元中。
    猜你喜欢
    • 1970-01-01
    • 2015-01-28
    • 1970-01-01
    • 2012-04-11
    • 1970-01-01
    • 2014-08-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多