2011-01-12 3 views
0

私は単純なtcp/ipチャットプログラムを構築しています。たとえば、2つのメッセージを送信し、両方が20文字を保持できるバッファより大きい場合、最初のメッセージの20文字が送信され、次のメッセージの20文字が送信され、最初のメッセージの残りの部分最後のメッセージの残りの部分。だから私は文字列を解析して連結すると、最初のメッセージの始めと2番目のメッセージの始めと2番目のメッセージの最初と最後の2つのメッセージが得られます。私はメッセージを送信する方法を知りたいし、最初のメッセージがすでに送信されるまで次のメッセージを待ち行列に入れたい。私は非同期メソッド呼び出しとスレッドを使用しています。TCP/IPチャットプログラムが私に混在メッセージを送信しています

マイコード:

クライアント:

protected virtual void Write(string mymessage) 
{ 


       var buffer = Encoding.ASCII.GetBytes(mymessage); 
       MySocket.BeginSend(buffer, 0, buffer.Length, 
SocketFlags.None,EndSendCallBack, null); 

       if (OnWrite != null) 
       { 
        var target = (Control) OnWrite.Target; 
        if (target != null && target.InvokeRequired) 
        { 
         target.Invoke(OnWrite, this, new EventArgs()); 
        } 
        else 
        { 
         OnWrite(this, new EventArgs()); 
        } 
       } 
     } 

と混合取得の二つの呼び出し:

client.SendMessage("CONNECT",Parser<Connect>.TextSerialize(connect)); 
    client.SendMessage("BUDDYLIST",""); 

、最終的には、読み取り機能(私はすべての先頭に番号を使用しますメッセージが角括弧で囲まれたときを知るメッセージ):

private void Read(IAsyncResult ar) 
     { 

      string content; 
      var buffer = ((byte[]) ar.AsyncState); 
      int len = MySocket.EndReceive(ar); 
      if (len > 0) 
      { 
       string cleanMessage; 
       content = Encoding.ASCII.GetString(buffer, 0, len); 
       if (MessageLength == 0) 
       { 
        MessageLength = int.Parse(content.Substring(1, content.IndexOf("]", 1) - 1)); 
        cleanMessage = content.Replace(content.Substring(0, content.IndexOf("]", 0) + 1), ""); 
       } 
       else 
        cleanMessage = content; 

       if(cleanMessage.Length <1) 
       { 
        if(MySocket.Connected) 
         MySocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(Read), buffer); 
        return; 
       } 

       MessageLength = MessageLength > cleanMessage.Length? MessageLength - cleanMessage.Length : 0; 
       amessage += cleanMessage; 

       if(MessageLength == 0) 
       { 
        if (OnRead != null) 
        { 
         var e = new CommandEventArgs(this, amessage); 
         Control target = null; 
         if (OnRead.Target is Control) 
          target = (Control)OnRead.Target; 
         if (target != null && target.InvokeRequired) 
          target.Invoke(OnRead, this, e); 
         else 
          OnRead(this, e); 
        } 
        amessage = String.Empty; 
       } 
       MySocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(Read), buffer); 
        return; 
      } 
     } 
+0

TCPはメッセージの概念では機能しません。それは流れと何も与えません。ストリームをメッセージに分割するメカニズムを作成する必要があります。 – CodesInChaos

+0

私は文字列の先頭に括弧で囲まれた数字を持っています。メッセージの終わりを知るには[3]ねえ。問題は私が[3]方法とあなた[3]をあなたに送ったなら、私は[3] h、それから[3] y、そして 'それはバッファが4の場合だけです。 – Eitan

+0

"[3] how [3]あなた"を送り、 "[3] h [3] yowyou"を受け取りますか?私には信じられないほどの音。しかし、私は通常、自分のネットワークコードをシングルスレッドで書く。私はあなたのスレッドコードが正しく書かれていないと思われます。 – CodesInChaos

答えて

1

TCP do あなたのメッセージ全体を1回の読書で受け取ることを保証します。したがって、メッセージの開始位置と終了位置を検出できる必要があります。

通常、メッセージの最後に特殊文字を追加します。または、実際のメッセージの前に長さヘッダーを使用してください。

クライアントで通常BeginSendを使用する必要はありません。送信は十分に速くなければならず、複雑さも軽減されます。また、私は通常、サーバが実際に実行可能でなければ、サーバでBeginSendを使用しません。

更新

実際のソケット実装は、これまでにあなたのメッセージを混在することはありません、唯一のあなたのコードは、それを行うことができます。複数の送信を呼び出すことによってメッセージを送信することはできません。アプリケーションがマルチスレッドの場合、メッセージが混合されるからです。言い換えれば

、これは動作しません。

_socket.BeginSend(Encoding.ASCII.GetBytes("[" + message.Length + "]")) 
_socket.BeginSend(Encoding.ASCII.GetBytes(message)); 

あなたは1回の送信で全てを送信する必要があります。

アップデート2

あなたの読み取りの実装では、2つのメッセージが同じ読むに来ることができることを考慮していません。それがあなたの混在したメッセージの原因である可能性が最も高いです。

あなたが送信する場合:

[11]Hello world 
[5]Something else 

は、彼らはのように到着することができます:

[11]Hello World[5]Some 
thing else 

換言すれば、第2のメッセージの一部は、最初のBeginReadに到着することができます。受信したすべてのコンテンツ(常にStringBuilder)を含むバッファを作成し、処理された部分を削除する必要があります。

擬似コード:

method OnRead 
    myStringBuilder.Append(receivedData); 
    do while gotPacket(myStringBuilder) 
     var length = myStringBuilder.Get(2, 5) 
     if (myStringBuilder.Length < 7 + length) 
      break; 

     var myMessage = myStringBuilder.Get(7, length); 
     handle(myMessage); 

     myStringBuilder.Remove(0, 7+length); 
    loop 
end method 

あなたは私がやっているものを見ていますか?私は常に受信したデータをstringbuilderに追加してから、完全なメッセージを削除します。私は複数のメッセージが一度に到着できるので、ループを使用しています。

+0

上記の私の返信はコピーされ、貼り付けられました: 'メッセージがいつ終了するかを知るために、文字列の始めに角カッコで囲まれた数字があります。[3]ねえ。問題は私が[3]方法とあなた[3]をあなたに送ったなら、私は[3] h、それから[3] y、そして 'それはバッファが4の場合だけです。 ' – Eitan

+0

私はメカニズムを持っており、メッセージが終了すると、問題はメッセージの混合であることがわかります。 – Eitan

+0

私の答えを更新しました。 – jgauffin

1

1つの送信呼び出しでメッセージ全体を送信すると、そのメッセージは順番に送信されます。メッセージを区切るには区切り記号が必要です。受信側では、メッセージをバッファにバッファリングする必要があります。大量のデータを送信しない場合は、受信メッセージを処理するために少しばかだが簡単にコーディングすることができます。

// Note: Untested pseudocode 
buffer += stringDataRead; 
while (buffer.Contains("\r\n")) { 
    // You may want to compensate for \r\n by doing a -2 here and +2 on next line 
    line = buffer.Substring(0, buffer.IndexOf("\r\n")); 
    buffer = buffer.Remove(0, line.Length); 

    DoSomehingWithThisLine(line); 
} 

魔法は必要ありません。 :)

+0

私はこのために文字列を使用しませんでしたが、メモリ消費量はかなり忙しいサーバーで膨大になります。しかし、私より鮮明なサンプルを作るために+1。 – jgauffin

関連する問題