代码之家  ›  专栏  ›  技术社区  ›  nup

一个应用程序中的TiptcpServer和客户端

  •  -1
  • nup  · 技术社区  · 6 年前

    我创建了一个应用程序,其中客户机和服务器在同一个程序中。我使用DelphiXe7和组件tiptcpserver/…客户端。但是当我试图关闭连接了客户机的服务器时(在同一窗口中),程序停止响应。也许这与多线程有关。如何在一个应用程序中实现具有客户机和服务器的程序,这是正确的方法吗?

    procedure TfrmMain.startClick(Sender: TObject);
    begin
      if (server.active) then stopServer()
      else startServer();
    end;
    
    procedure TfrmMain.startServer();
    var
      binding: TIdSocketHandle;
    begin
      server.bindings.clear();
    
      try
        server.defaultPort := strToInt(port.text);
        binding := server.bindings.add();
        binding.ip := ip;
        binding.port := strToInt(port.text);
    
        server.active := true;
    
        if (server.active) then begin
          addToLog('Server started');
          start.caption := 'Stop';
        end;
      except on e: exception do
        addToLog('Error: ' + e.message + '.');
      end;
    end;
    
    procedure TfrmMain.stopServer();
    begin
      server.active := false;
      server.bindings.clear();
    
      if (not(server.active)) then begin
        addToLog('Server stopped');
        start.caption := 'Start';
      end
      else addToLog('Server shutdown error.');
    end;
    
    procedure TfrmMain.serverConnect(AContext: TIdContext);
    var
      i: integer;
    begin
      addToLog('New client: ' + aContext.connection.socket.binding.peerIP + '.');
    
      clients.clear();
      for i := 0 to server.contexts.lockList.count - 1 do begin
        with TIdContext(server.contexts.lockList[i]) do
          clients.items.add(connection.socket.binding.peerIP);
      end;
      server.contexts.unlockList();
    end;
    
    procedure TfrmMain.serverDisconnect(AContext: TIdContext);
    begin
      addToLog('Client ' + aContext.connection.socket.binding.peerIP + ' disconnected from the server.');
    end;
    
    procedure TfrmMain.clientConnected(Sender: TObject);
    begin
      addToConsole('You connected to server successfully.');
    end;
    
    procedure TfrmMain.clientDisconnected(Sender: TObject);
    begin
      addToConsole('The connection to the server was interrupted.');
    end;
    

    和连接代码:

    client.host := ip;
    
    try
      client.connect();
    except on e: exception do
      addToConsole('Error: ' + e.message);
    end;
    
    1 回复  |  直到 6 年前
        1
  •  2
  •   Remy Lebeau    6 年前

    我看到这个代码有很多问题。

    • 如何 addToLog() addToConsole() 实施?它们是安全的吗?记住 TIdTCPServer 是一个多线程组件,它的事件是在工作线程的上下文中触发的,而不是主UI线程,因此必须同步对UI、共享变量等的任何访问。

    • 是什么 clients ?它是一个UI控件吗?您需要同步对它的访问,这样当多个线程试图同时访问它时,您就不会损坏它的内容。

    • 你的使用 TIdTCPServer.Contexts 属性未得到充分保护,无法免受例外情况的影响。你需要一个 try..finally 阻止以便您可以呼叫 Contexts.UnlockList() 安全地。

    • 更重要的是,你打电话来 Contexts.LockList() 太多次 在你 serverConnect() 循环(这是问题的根本原因)。 LockList() 返回A TIdContextList 对象。在循环中,您应该访问该列表的 Items[] 属性而不是调用 锁列表() 再一次。因为你没有匹配的 UnlockList() 对于每一个 锁列表() ,一旦客户机连接到服务器, Contexts 列表已死锁,无法再访问一次 Server连接() 退出,包括客户端连接/断开连接以及 TIDTCP服务器 关闭(例如在您的情况下)。

    • serverDisconnect() 没有从中删除任何项目 客户 . Server连接() 不应重置 客户 完全。它应该只添加调用 TIdContext 客户 然后 服务器断开连接() 应该去掉那个 Twitter上下文 客户 后来。

    有了这句话,试着做些类似的事情:

    procedure TfrmMain.addToConsole(const AMsg: string);
    begin
      TThread.Queue(nil,
        procedure
        begin
          // add AMsg to console ...
        end
      );
    end;
    
    procedure TfrmMain.addToLog(const AMsg: string);
    begin
      TThread.Queue(nil,
        procedure
        begin
          // add AMsg to log ...
        end
      );
    end;
    
    procedure TfrmMain.startClick(Sender: TObject);
    begin
      if server.Active then
        stopServer()
      else
        startServer();
    end;
    
    procedure TfrmMain.startServer();
    var
      binding: TIdSocketHandle;
    begin
      server.Bindings.Clear();
    
      try
        server.DefaultPort := StrToInt(port.Text);
        binding := server.Bindings.Add();
        binding.IP := ip;
        binding.Port := StrToInt(port.Text);
    
        server.Active := True;
    
        addToLog('Server started');
        start.Caption := 'Stop';
      except
        on e: Exception do
          addToLog('Error: ' + e.message + '.');
      end;
    end;
    
    procedure TfrmMain.stopServer();
    begin
      try
        server.Active := False;
        server.Bindings.Clear();
    
        addToLog('Server stopped');
        start.Caption := 'Start';
      except
        on e: Exception do
          addToLog('Server shutdown error.');
      end;
    end;
    
    procedure TfrmMain.serverConnect(AContext: TIdContext);
    var
      PeerIP: string;
    begin
      PeerIP := AContext.Binding.PeerIP;
      addToLog('New client: ' + PeerIP + '.');
    
      TThread.Queue(nil,
        procedure
        {
        var
          i: integer;
          list: TIdContextList;
        }
        begin
          {
          clients.clear();
          list := server.Contexts.LockList;
          try
            for i := 0 to list.count - 1 do begin
              clients.Items.Add(TIdContext(list[i]).Binding.PeerIP);
            end;
          finally
            list.UnlockList();
          end;
          }
    
          // I'm assuming clients is a UI control whose Items property
          // is a TStrings object.  If not, adjust this code as needed...
          clients.Items.AddObject(PeerIP, AContext);
        end;
      );
    end;
    
    procedure TfrmMain.serverDisconnect(AContext: TIdContext);
    begin
      addToLog('Client ' + AContext.Binding.PeerIP + ' disconnected from the server.');
    
      TThread.Queue(nil,
        procedure
        var
          i: Integer;
        begin
          // I'm assuming clients is a UI control whose Items property
          // is a TStrings object.  If not, adjust this code as needed...
          i := clients.Items.IndexOfObject(AContext);
          if i <> -1 then
            clients.Items.Delete(i);
        end
      );
    end;
    
    procedure TfrmMain.clientConnected(Sender: TObject);
    begin
      addToConsole('You connected to server successfully.');
    end;
    
    procedure TfrmMain.clientDisconnected(Sender: TObject);
    begin
      addToConsole('The connection to the server was interrupted.');
    end;