2

TCPServer /クライアントコンポーネントによってプログラムされたSocketアプリケーションでは、通常、サーバー側をアクティブにしてからクライアントをサーバーに接続します。一方の側からもう一方の側にデータを取得または送信する必要がある場合は、最初にクライアントからサーバーにコマンドを送信します。開始します。

しかし、問題は常にクライアント側から会話を始める必要があるということです!

クライアント側の要求なしにサーバー側からランダムに会話を開始するためのアイデアはありますか?

サーバー側からクライアントに通知するには、この機能が必要です。たとえば、登録ユーザー(クライアント側)がサーバーに接続し、他の接続ユーザー(他のクライアント側)が接続している場合、サーバーからすべてのユーザー(Yahoo Messengerなど)に通知を送信する必要があります。

TIdCmdTCPServerおよびTIdTCPClientコンポーネントを使用しています

4

4 に答える 4

8

を使用してTIdCmdTCPServerいます。定義上、クライアントが発行したコマンドへの応答を送信します。あなたが求めていることについては、代わりに使用する必要があります。そうすれば、イベントTIdTCPServerでやりたいことが何でもできます。TIdTCPServer.OnExecute

あなたが求めることは実行可能ですが、その実装はあなたのプロトコルに対するあなたの特定のニーズに依存します。

未承諾のサーバーからクライアントへのメッセージを送信し、クライアントからサーバーへのコマンドに応答しない場合は、実装はかなり簡単です。必要に応じて使用TIdContext.Connection.IOHandlerしてください。およびイベントTIdTCPServer.Contexts内など、リスト内の既存のクライアントをループできます。クライアント側では、サーバーメッセージを定期的にチェックするためのタイマーまたはスレッドが必要です。その例を見てください。TIdTCPServer.OnConnectTIdTCPServer.OnDisconnectTIdCmdTCPClientTIdTelnet

ただし、同じ接続でクライアントからサーバーへのコマンドと一方的なサーバーからクライアントへのメッセージの両方を混在させる必要がある場合は、非同期で動作するようにプロトコルを設計する必要があり、実装がより複雑になります。未承諾のサーバーメッセージは、クライアントコマンドへの応答前であっても、いつでも表示される可能性があります。コマンドには、クライアントが応答を照合できるように応答にエコーされる値を含める必要があり、パケットは応答と非送信請求メッセージを区別できる必要があります。また、サーバー側で各クライアントに独自のアウトバウンドキューを与える必要があります。TIdContext.Dataそのためのプロパティを使用できます。その後、必要に応じてサーバーメッセージをキューに追加し、OnExecuteイベントは、他に何もしていないときに定期的にキューを送信します。クライアント側にはまだタイマー/スレッドが必要であり、クライアントコマンドへの応答と一方的なサーバーメッセージの両方を処理する必要があるため、TIdConnection.SendCmd()最終的に何を読み取るかわからないため、または関連するメソッドを使用できません。

私はこれまで何度もエンバカデロとインディのフォーラムに両方のアプローチの例を投稿しました。

于 2012-12-15T18:59:13.603 に答える
4

クライアントは通信を開始します。これが、コミュニケーションを開始するアクターであるクライアントの定義です。ただし、接続が確立されると、両側でデータを送信できます。したがって、クライアントはサーバーに接続します。サーバーは、接続されているすべてのクライアントのリストを維持します。サーバーが通信を送信する場合は、接続されているすべてのクライアントにデータを送信するだけです。

クライアントは通信を開始するため、通信が切断された場合、接続を再確立するのはクライアントの仕事になります。

于 2012-12-15T09:09:21.720 に答える
4

サーバーがデータを送信する実際のコード例を確認したい場合は、IndyIdTelnetを確認してください。telnetクライアントはスレッドを使用してサーバーメッセージをリッスンします。クライアントによって作成されたソケットは1つだけですが、サーバーはいつでもクライアントへのメッセージに同じソケットを使用します。

クライアントは接続を開始しますが、「HELLO」などと言って会話を開始する必要はありません。

技術的には、クライアントは追加のデータを送信せずに、ソケット接続を開くだけで済みます。クライアントは、接続が終了するまで、必要な限り静かな状態を保つことができます。

クライアントが接続するとすぐに、サーバーはクライアントへのソケット接続を持ちます。そして、このソケットを介して、サーバーはクライアントにデータを送信できます。

もちろん、クライアントはサーバーデータを表示するために接続ソケットから読み取る必要があります。これは、バックグラウンドスレッドのループで実行することも、メインスレッドで実行することもできます(もちろん、ブロックされるため、VCLアプリケーションでは実行できません)。

于 2012-12-15T11:18:31.967 に答える
3

最後に、これは私の問題を解決するために使用したコードです。

// 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;
于 2012-12-18T11:14:27.853 に答える