簡體   English   中英

首先在 Indy 10 的 delphi 中從服務器開始通信

[英]Start Communication from server first in delphi by Indy 10

在由 TCPServer/Client 組件編寫的 Socket 應用程序中,通常我們激活服務器端,然后將客戶端連接到服務器,當我們需要從一側到另一端獲取或發送數據時,首先我們從客戶端向服務器發送命令,然后進行通信開始。

但問題是我們總是需要從客戶端開始對話!

我想問一下在沒有客戶端請求的情況下從服務器端隨機開始對話有什么想法嗎?

我需要此功能從服務器端通知客戶端。 例如,當注冊用戶(客戶端)連接到服務器時,其他連接的用戶(在其他客戶端)必須從服務器向所有用戶發送通知(如 Yahoo Messenger)。

我正在使用TIdCmdTCPServerTIdTCPClient組件

您正在使用TIdCmdTCPServer 根據定義,它發送對客戶端發出的命令的響應。 對於您所要求的,您應該改用TIdTCPServer ,然后您可以在TIdTCPServer.OnExecute事件中做任何您想做的事情。

您要求的是可行的,但其實現取決於您對協議的特定需求。

如果您只想發送未經請求的服務器到客戶端的消息,並且從不響應客戶端到服務器的命令,那么實現非常簡單。 需要時使用TIdContext.Connection.IOHandler 您可以遍歷TIdTCPServer.Contexts列表中的現有客戶端,例如在TIdTCPServer.OnConnectTIdTCPServer.OnDisconnect事件中。 在客戶端,您需要一個計時器或線程來定期檢查服務器消息。 查看TIdCmdTCPClientTIdTelnet以獲取相關示例。

但是,如果您需要在同一連接上混合使用客戶端到服務器的命令和未經請求的服務器到客戶端的消息,則必須將協議設計為異步工作,這會使實現更加復雜。 未經請求的服務器消息可以隨時出現,甚至在響應客戶端命令之前。 命令需要包含一個在響應中回顯的值,以便客戶端可以匹配響應,並且數據包需要能夠區分響應和未經請求的消息。 您還必須在服務器端為每個客戶端提供自己的出站隊列。 您可以為此使用TIdContext.Data屬性。 然后,您可以在需要時將服務器消息添加到隊列中,並讓OnExecute事件在不執行任何其他操作時定期發送隊列。 您仍然需要客戶端的計時器/線程,它需要處理對客戶端命令和未經請求的服務器消息的響應,因此您不能使用TIdConnection.SendCmd()或相關方法,因為它不知道它是什么將結束閱讀。

我之前在 Embarcadero 和 Indy 論壇上多次發布了這兩種方法的示例。

客戶端發起通信。 這就是客戶端的定義——發起通信的參與者。 但是,一旦建立連接,雙方就可以發送數據。 因此,客戶端連接到服務器。 服務器維護所有已連接客戶端的列表。 當服務器想要發出通信時,它只是將數據發送到所有連接的客戶端。

由於客戶端發起通信,因此在通信中斷的情況下,重新建立連接是客戶端的工作。

如果您想查看服務器發送數據的工作代碼示例,請查看 Indy IdTelnet:telnet 客戶端使用線程來偵聽服務器消息。 只有一個由客戶端創建的套接字,但服務器在任何時候都使用相同的套接字向客戶端發送消息。

客戶端開始連接,但不必通過說“你好”或類似的話來開始對話。

從技術上講,客戶端只需要打開套接字連接,而不需要發送任何額外的數據。 客戶端可以根據需要保持安靜,甚至可以一直保持到連接結束。

一旦客戶端連接,服務器就會與客戶端建立套接字連接。 通過這個套接字,服務器可以向客戶端發送數據。

當然,客戶端必須從連接套接字中讀取才能看到服務器數據。 這可以在后台線程的循環中完成,甚至可以在主線程中完成(當然不是在 VCL 應用程序中,因為它會阻塞)。

最后,這是我用來解決問題的代碼:

// Thread at client-side
procedure FNotifRecieverThread.Execute;
var
  str: string;
  MID: Integer;
  TCP1: TIdTCPClient;
begin
  if frmRecieverMain.IdTCPClient1.Connected then
  begin
    TCP1 := TIdTCPClient.Create(nil);
    TCP1.Host := frmRecieverMain.IdTCPClient1.Host;
    TCP1.Port := frmRecieverMain.IdTCPClient1.Port;
    TCP1.ConnectTimeout := 20000;

    while True do
    begin
      try
        TCP1.Connect;

        while True do
        begin
          try
            str := '';
            TCP1.SendCmd('checkmynotif');

            TCP1.Socket.WriteLn(IntToStr(frmRecieverMain.UserID));
            str := TCP1.Socket.ReadLn;

            if Pos('showmessage_', str) = 1 then
            begin
              MID := StrToInt(Copy(str, Pos('_', str) + 1, 5));
              frmRecieverMain.NotifyMessage(MID);
            end
            else
            if str = 'updateusers' then
            begin
              LoadUsers;

              frmRecieverMain.sgMsgInbox.Invalidate;
              frmRecieverMain.sgMsgSent.Invalidate;
              frmRecieverMain.cbReceipent.Invalidate;
            end
            else
              if str = 'updatemessages' then
            begin
              LoadMessages;
              frmRecieverMain.DisplayMessages;
            end;
          except
            // be quite and try next time :D
          end;          

          Sleep(2000);
        end;
      finally
        TCP1.Disconnect;
        TCP1.Free;
      end;

      Sleep(5000);
    end;
  end;
end;

// And command handlers at server-side
procedure TfrmServer.cmhCheckMyNotifCommand(ASender: TIdCommand);
var
  UserID, i: Integer;
  str: string;
begin
  str := 'notifnotfound';
  UserID := StrToIntDef(ASender.Context.Connection.Socket.ReadLn, -1);

  for i := 0 to NotificationStack.Count - 1 do
    if NotificationStack.Notifs[i].Active and
      (NotificationStack.Notifs[i].UserID = UserID)
    then
    begin
      NotificationStack.Notifs[i].Active := False;
      str := NotificationStack.Notifs[i].NotiffText;
      Break;
    end;

  ASender.Context.Connection.Socket.WriteLn(str);
end;

// And when i want to some client notificated from server, I use some methodes like this:
procedure TfrmServer.cmhSetUserOnlineCommand(ASender: TIdCommand);
var
  UserID, i: Integer;
begin
  UserID := StrToIntDef(ASender.Context.Connection.Socket.ReadLn, -1);

  if UserID <> -1 then
  begin
    for i := 0 to OnLineUsersCount - 1 do // search for duplication...
      if OnLineUsers[i].Active and (OnLineUsers[i].UserID = UserID) then
        Exit; // duplication rejected!    

    Inc(OnLineUsersCount);
    SetLength(OnLineUsers, OnLineUsersCount);

    OnLineUsers[OnLineUsersCount - 1].UserID := UserID;
    OnLineUsers[OnLineUsersCount - 1].Context := ASender.Context;
    OnLineUsers[OnLineUsersCount - 1].Active := True;

    for i := 0 to OnLineUsersCount - 1 do      // notify all other users for refresh users list
      if OnLineUsers[i].Active and (OnLineUsers[i].UserID <> UserID) then
      begin
        Inc(NotificationStack.Count);
        SetLength(NotificationStack.Notifs, NotificationStack.Count);

        NotificationStack.Notifs[NotificationStack.Count - 1].UserID := OnLineUsers[i].UserID;
        NotificationStack.Notifs[NotificationStack.Count - 1].NotiffText := 'updateusers';
        NotificationStack.Notifs[NotificationStack.Count - 1].Active := True;
      end;
  end;
end;

暫無
暫無

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

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