2

現在書いているプログラムに少し問題があります。

まず、それが達成すべきことを説明しましょう。

これはチャット プログラムに非常に似ているため、基本的に、クライアント ウィンドウで発生するいくつかのことに関するデータを格納する情報クラス (私はパケットと呼びます) を持っています。プログラム自体は、2 つの異なるクライアント ウィンドウとサーバー ウィンドウで構成されます。(そして、それらのそれぞれを開くための 1 つのウィンドウですが、それは今は重要ではありません。)

したがって、クライアントは自分のウィンドウでいくつかのことを変更し、送信を押します。

次に、私のパケットはこのすべての情報を保存します。これは、ニックネーム、テキスト、いくつかの異なる数値 (基本的にはサイコロの結果)、および「種類」変数で構成されます。

メッセージには 3 つの異なる種類があり、この「パケット」は送信できます。

  1. 通常のテキスト メッセージ (完全に機能します。)
  2. 3 つのサイコロの結果、難易度の値、才能の値で構成されるサイコロ メッセージ。
  3. 別のサイコロ メッセージ。今回は 1 つのサイコロの結果のみで構成されます。

メッセージが送信される (そしてサーバーによって受信される) たびに、プログラムはメッセージの種類を (kind 変数を介して) 読み取り、メッセージの内容に応じてメッセージを処理するためのさまざまな手順を開始します。

チャット メッセージはチャットに表示されるだけで、各クライアントに送信されて表示されます。これは、バグや何かなしで動作します。

サイコロメッセージは両方とも、表示される特別な形をしています。基本的には、ダイスロールが成功したかどうかを示すために、この形で生成されたチャット メッセージです。


今私の問題があります:

プログラムを LAN バージョンで (ロケール IP を使用して) テストしたところ、完全に機能しました。問題なし、バグなし。

しかし今、私は友人にインターネット経由でテストするのを手伝ってくれるように頼みました. ポート転送が機能し、クライアントは接続できます。

いよいよメッセージの送信です。チャットメッセージ: 正常に動作します Dice1-Message: ニックネームと 2 つのサイコロの結果のみが送信されます。3 番目のサイコロの結果は表示されず、他のすべての情報は、表示されるはずの場所に空白が残るだけです。2 番目のダイス メッセージについても同様です。ダイスの結果とニックネームのみが表示され、その他の情報は表示されません。

知っておくと役立つ場合、プログラムは Delphi 6.0 で書かれています (はい、少し古いですが、私はこのプログラムに慣れていて、まだプログラミングが得意ではありません..基本を学び、自分のそのような最初の大きなプログラム。)

パケット自体は次のように定義されます。

type
TPacket  = Record
  Nickname : string[255];
  Text     : string[255];
  OtherInfo: VarType;
end;

メッセージは、次のような情報を参照します。

ServerSocket.Socket.ReceiveBuf (Packet, SizeOf(Packet));

MemMessage.Lines.Add(Packet.Nickname + 'succeeded his dice roll for ' + Packet.Talent + 'Restofmessage ' + Packet.OtherInformation)

そう..それはどのようにです。誰かが何が問題なのか (または、この場合は LAN とインターネットの問題の原因となる違い) を知っていて、これを解決する方法を知っていることを願っています。

よろしくお願いします〜

PS: サーバーは単純に情報を取得し、同じ方法で表示します。どのクライアントも同じように表示し、それを各クライアントに直接送信します。各クライアントはそれを受信して​​表示します。


編集:

うーん、「その他の情報」は大差ないので、このままでいいと思いました。

でも大丈夫〜詳細:)

「その他の情報」はすべて文字列 [255] 型ですが、そのうちの 1 つはブール値です。

Dice1 -Type は以下を使用します

ニックネーム、リザルト1、リザルト2、リザルト3、難易度、スキルポイント、タレント(ダイスロールが成功または失敗をテストするタレントの名前のみ)、および「成功」と呼ばれるブール値。

Dice2 の用途:

ニックネーム、結果、難易度、才能 (タレントの名前。値を間違えないでください^^)、TalentPoints (基本的に、タレントの値からダイスロールの難易度を差し引いたもの)、およびブール値の「成功」と同様に、 「種類」と呼ばれる文字列[255]。種類の機能は上で説明されています。

プロトコルに関しては、私は特別なものを使用していないと思います..

SendBuf と ReceiveBuf を介して TPacket クラス全体を送信するだけです。私はプログラミングにまったく慣れていないので、それ以上の情報を提供できなくて申し訳ありません:/

サーバーが TPacket の受信とリダイレクトに使用する手順のコードを示します。

私はドイツ語でプログラムを書いているので、間違いがないことを願っています..^^

procedure TFormServer.ServerSocketClientRead (Sender:TObject; Socket: TCustomWinSocket);
var
Message : TPacket;
i       : ShortInt;
begin
  {receive Message}
 Socket.ReceiveBuf (Message, SizeOf(Message))

{checking, what kind the message is of}
   if Message.Kind = 'Chat' then begin
     MemMessage.Lines.Add ('By' + Message.Nickname + ': ' + Message.Text);

     {Redirecting to other clients}
    with ServerSocket.Socket do begin
      for i := 0 to ActiveConnections -1 do
         Connections[I].SendBuf(Message, SizeOf(Message))
    end;
   end;


   if Message.Kind = 'DiceRoll_1' then begin
     if Message.Success = true
        then MemMessage.Lines.Add (Message.Nickname + ' succeeded his Dice Roll for ' + Message.Talent + ' (Difficulty: ' + Message.Difficulty + ') with the results: (' + Message.Result1 + '/' + Message.Result2 + '/' + Message.Result3 + '), Skill lvl: ' + Message.Skillpoints);

//The Message should look like that
{Dummy succeeded his Dice Roll for climbing (Difficulty: 4) with the results: (12/14/13), Skill lvl: 8}

//For Comparison, the buggy message looks like that
{Dummy succeeded his Dice Roll for  (Difficulty:  ) with the results: (12/14/ ), Skill lvl:  }
        else {The same as when it succeeded, just saying it did 'not' succeed..}


     {redirection}
    with ServerSocket.Socket do begin
      for i := 0 to ActiveConnections -1 do
         Connections[i].SendBuf (Message, SizeOf(Message))
    end;

  end; {if kind = DiceRoll_1}

{Same goes for the DiceRoll_2 now. This time it uses: Message.Nickname, Message.Talent, Message.Difficulty, Message.Result, Message.TalentPoints, Message.Success}

もう一度、私の TPacket の宣言全体..重要かもしれません

type    TPacket = Result
  Kind        : string[255];
  Nickname    : string[255];
  Text        : string[255];
  Result      : string[255];
  Result1     : string[255];
  Result2     : string[255];
  Result3     : string[255];
  Difficulty  : string[255];
  Talent      : string[255];
  Skillpoints : string[255];
  TalentPoints: string[255];
  Succeed     : boolean;
end;

私はそれがより理解しやすくなったことを願っています..私は説明するのが得意ではないと思います:/

しかし、ここに私を歓迎してくれてありがとう、私はあなたたちに迷惑をかけず、何かのケースでも助けられることを願っています!

4

2 に答える 2

2

うーん..それが私が考えているコンポーネントである場合、ClientReadイベントがデータでいっぱいの完全なメッセージで発生するのはロックではありません。

この場合、メッセージの転送を可能にするためにTCP上にプロトコルが必要です。TCP自体はメッセージを転送できず、バイトストリームのみを転送します。

TCustomWinSocketドキュメント、(私のイタリック体):

'ReceiveBufを使用して、WindowsソケットオブジェクトのOnSocketEventイベントハンドラー、またはソケットコンポーネントのOnReadまたはOnClientReadイベントハンドラーのソケット接続から読み取ります。

ReceiveBufは、実際に読み取られたバイト数を返しますこれは、呼び出しで要求された数より少ない場合があります)。

バイトが読み取られない場合、ReceiveBufは–1を返します。

したがって、特大のバッファを宣言し、受信したデータの解析を回避するために常にロットを転送するという狡猾な計画は機能していません。

あなたはそれを正しくする必要があります。ClientRead()イベントがメッセージ全体、部分的なメッセージ、結合されたメッセージ、または毎回1バイトで何度も発生するかどうかに関係なく機能するプロトコルを設計します。私の好みの設計は、「byteLoad(thischar:char):boolean;」で「PDU」クラスを使用することです。内部ステートマシンを使用してプロトコルを処理し、フィールドにcorectデータをロードし、完全なメッセージがアセンブルされた場合にのみtrueを返すメソッド。すべてのClientRead呼び出しで、受信したすべてのバイトを繰り返し、trueが返されるまで「byteLoad」を呼び出してから、PDUを処理し(おそらくどこかでキューに入れて)、別のPDUを作成してロードを開始します。

非同期の非スレッドTServerSocketを使用しているため、PDUインスタンスなどのクライアント固有のデータや、ClientRead呼び出し全体で保持する必要のあるその他のデータは、渡されるTCustomWinSocketインスタンスのフィールドとして保存する必要があることに注意してください。これは、TCustomWinSocketの子孫がこのデータを保持するか、ベースTCustomWinSocketの「data」プロパティを使用して別のオブジェクトまたはレコードを保持することを意味します。そのようなものは通常、OnConnectedイベントで作成/初期化/ロードされます。切断時のTCustomWinSocketの動作に注意してください-データフィールド内のnil以外のポインターを解放しようとするという面白い感じがします-あなたが望まないかもしれません。

于 2012-04-25T20:49:12.780 に答える
0

どのプロトコルを使用していますか?

これが UDP の場合、これらの問題はこのプロトコルの「設計によるもの」です。

信頼性に欠けるため、UDP アプリケーションは通常、損失、エラー、または重複を許容する必要があります。

http://en.wikipedia.org/wiki/User_Datagram_Protocolを参照してください

ローカル ネットワークでは、条件が最適であるため、問題なく動作します。

しかし、インターネット上では、一部のパケットが失われます。

データをルーティングしたい場合は、UDP を介してアプリケーションに確認と再送信のメカニズムを追加するか (素朴なプロトコルについては TFTP プロトコルを参照)、再試行する TCP などのより高度なプロトコルを使用します。パケット損失の場合にデータを送信しますが、オーバーヘッドが大きくなります。

単純なチャット プログラムの場合、TCP を使用することは理にかなっています (必要に応じて HTTP レイアウトを使用することもできます。これはよりファイアウォールに適しています)。このプロトコルがデュプレックスでない場合でも、定期的に (1 秒など) データを取得して、保留中のメッセージについてサーバーに問い合わせることができます。

ところで、使用string[255]はアプリケーション レイヤーに対して少し失礼です。#13#10 (改行) で終了するテキスト、または #0 で終了するテキストを使用することも理にかなっています。パケットのほとんどはチャット中に使用されず、テキストに 255 文字を超える文字を使用することはできません。または、メッセージ レイアウトに XML や JSON などの標準形式を使用します。

于 2012-04-25T20:05:17.543 に答える