2011-07-30 3 views
2

クライアント/サーバーアプリケーションを作成しています。ループバックアドレスを使用して接続するとうまくいくように見えますが、LANやインターネットに接続すると非常に奇妙な問題が発生します。ソケットを介してデータを送信するときのバイト長の計算に関する問題

サーバーとクライアントの間で送信されるすべてのデータは、「DataPacket」というクラスに含まれています。このクラスは、ソケットを使用して送信できるように、バイナリにシリアル化されます。

パケットが送信される前に、パケットのバイト長を含む4バイトのヘッダーが添付されているため、受信者はデシリアライズする前に予想されるバイト数を知ることができます。問題は、ある時点で、受信者はヘッダーで実際に送信されるデータよりもはるかに多くのデータを待っていると考えていることです。ときにはこの数字が何百万バイトにもなることもあり、深刻な問題を引き起こしています。 - これは、ユーザーのソケット、バッファ、および受信されたすべてのデータを保持するクラスです

サーバー側で
public override void Send(DataPacket packet) 
{ 
    byte[] content = packet.Serialize(); 
    byte[] header = BitConverter.GetBytes(content.Length); 

    List<Byte> bytes = new List<Byte>(); 
    bytes.AddRange(header); 
    bytes.AddRange(content); 

    if (socket.Connected) 
    { 
     socket.BeginSend(bytes.ToArray(), 0, bytes.Count, SocketFlags.None, OnSendPacket, socket); 
    } 
} 

は、各クライアントがStateObjectが割り当てられます。

これは、データが送信される方法です今のところそれは不完全です。

これはStateObjectクラスがどのように見えるかです:

public class StateObject 
{ 
    public const int HeaderSize = 4; 
    public const int BufferSize = 8192; 

    public Socket Socket { get; private set; } 
    public List<Byte> Data { get; set; } 
    public byte[] Header { get; set; } 
    public byte[] Buffer { get; set; } 

    public StateObject(Socket sock) 
    { 
     Socket = sock; 
     Data = new List<Byte>(); 
     Header = new byte[HeaderSize]; 
     Buffer = new byte[BufferSize]; 
    } 
} 

これはStateObjectクラスが使用されている方法です:いくつかのデータは、クライアントから受信される

private void OnClientConnect(IAsyncResult result) 
{ 
    Socket serverSock = (Socket)result.AsyncState; 
    Socket clientSock = serverSock.EndAccept(result); 

    StateObject so = new StateObject(clientSock); 
    so.Socket.BeginReceive(so.Buffer, 0, StateObject.BufferSize, SocketFlags.None, OnReceiveData, so); 
} 

は、EndReceiveが呼び出されます。 StateObject.Dataが空の場合は、これが新しいパケットの最初の部分であるとみなされるため、ヘッダーがチェックされます。受信したバイト数がヘッダに示されているサイズよりも小さい場合は、同じStateObjectを再利用してBeginReceiveを再度呼び出します。ここ

はOnReceiveDataためのコードである:

private void OnReceiveData(IAsyncResult result) 
{ 
    StateObject state = (StateObject)result.AsyncState; 

    int bytes = state.Socket.EndReceive(result); 
    if (bytes > 0) 
    { 
     state.ProcessReceivedData(bytes); 

     if (state.CheckDataOutstanding()) 
     { 
      //Wait until all data has been received. 
      WaitForData(state); 
     } 
     else 
     { 
      ThreadPool.QueueUserWorkItem(OnPacketReceived, state); 

      //Continue receiving data from client. 
      WaitForData(new StateObject(state.Socket)); 
     } 
    } 
} 

上記から分かるように、最初のIはstate.ProcessReceivedData()を呼び出す - これは私がデータからヘッダを取り外す場合であり、その後のデータを移動させますバッファ:

CheckDataOutstanding()を呼び出して、受信したバイト数とヘッダーのサイズを比較します。これらの試合はその後、私は安全にデータをデシリアライズすることができた場合:

public bool CheckDataOutstanding() 
{ 
    int totalBytes = BitConverter.ToInt32(Header, 0); 
    int receivedBytes = Data.Count; 
    int outstanding = totalBytes - receivedBytes; 

    return outstanding > 0; 
} 

を問題は何とかヘッダの値が、それは次のように送信されたものに異なる値で終わるされていることです。これは無作為に、時にはサーバー上やクライアント上の他の時に発生するようです。サーバーとクライアントの両方をデバッグする際のランダム性と一般的な難しさのために、私はこれを理解することはほとんど不可能であることを見出しています。

私がここで紛失している何か明白か愚かなことがありますか?

+0

投稿されたコードは* one *パケットに対してのみ正しく動作します。StateObjectをリセットするために必要なコード、特にDataを空にするコードがありません。記載されている問題の原因になります。 –

答えて

2

ヘッダーのがの場合はどうなりますか?つまり、4つ以上ではなく、最初の読み込みで3バイトが得られたとします。 ProcessReceivedData関数はそれを考慮に入れ、少なくとも4バイトまでヘッダサイズを抽出しないようにする必要があります。

+0

あなたは正しいと思いますが、これは私が見落としていたものであり、間違いなく説明する必要がありますが、これが問題の原因ではないと思います。私は、ProcessReceivedDataに4バイト未満のチェックをブレークポイントで追加しようとしましたが、問題が発生したときにヒットしませんでした。 – Adam

関連する問題