7

現在、複数のクライアント コンピューターからの複数の接続を受け入れることができる C# ソケット サーバーを開発中です。サーバーの目的は、クライアントがサーバー イベントから「サブスクライブ」および「サブスクライブ解除」できるようにすることです。

これまでのところ、私はここをよく見てみました: http://msdn.microsoft.com/en-us/library/5w7b7x5f(v=VS.100).aspxおよびhttp://msdn.microsoft.com/アイデアについては、 en-us/library/fx6588te.aspxを参照してください。

送信するすべてのメッセージは暗号化されているため、送信したい文字列メッセージを取得し、それを byte[] 配列に変換してからデータを暗号化してから、メッセージの長さをデータに追加して接続経由で送信します.

問題として私を襲う 1 つのことはこれです: 受信側では、メッセージの半分しか受信されていないときに Socket.EndReceive() (または関連するコールバック) が返される可能性があるようです。各メッセージが「完全」に受信され、一度に 1 つのメッセージのみが受信されるようにする簡単な方法はありますか?

編集:たとえば、.NET / Windows ソケットは、Socket.Send() で送信された単一のメッセージが 1 つの Socket.Receive() 呼び出しで受信されるようにメッセージを「ラップ」しませんか? それともそうですか?

これまでの私の実装:

private void StartListening()
{
    IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
    IPEndPoint localEP = new IPEndPoint(ipHostInfo.AddressList[0], Constants.PortNumber);

    Socket listener = new Socket(localEP.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
    listener.Bind(localEP);
    listener.Listen(10);

    while (true)
    {
        // Reset the event.
        this.listenAllDone.Reset();

        // Begin waiting for a connection
        listener.BeginAccept(new AsyncCallback(this.AcceptCallback), listener);

        // Wait for the event.
        this.listenAllDone.WaitOne();
    }
}

private void AcceptCallback(IAsyncResult ar)
{
    // Get the socket that handles the client request.
    Socket listener = (Socket) ar.AsyncState;
    Socket handler = listener.EndAccept(ar);

    // Signal the main thread to continue.
    this.listenAllDone.Set();

    // Accept the incoming connection and save a reference to the new Socket in the client data.
    CClient client = new CClient();
    client.Socket = handler;

    lock (this.clientList)
    {
        this.clientList.Add(client);
    }

    while (true)
    {
        this.readAllDone.Reset();

        // Begin waiting on data from the client.
        handler.BeginReceive(client.DataBuffer, 0, client.DataBuffer.Length, 0, new AsyncCallback(this.ReadCallback), client);

        this.readAllDone.WaitOne();
    }
}

private void ReadCallback(IAsyncResult asyn)
{
    CClient theClient = (CClient)asyn.AsyncState;

    // End the receive and get the number of bytes read.
    int iRx = theClient.Socket.EndReceive(asyn);
    if (iRx != 0)
    {
        // Data was read from the socket.
        // So save the data 
        byte[] recievedMsg = new byte[iRx];
        Array.Copy(theClient.DataBuffer, recievedMsg, iRx);

        this.readAllDone.Set();

        // Decode the message recieved and act accordingly.
        theClient.DecodeAndProcessMessage(recievedMsg);

        // Go back to waiting for data.
        this.WaitForData(theClient);
    }         
}
4

4 に答える 4

10

はい、受信するメッセージの一部のみが送信される可能性がありますまた、転送中にメッセージの一部のみが送信される場合はさらに悪化する可能性があります。通常、悪いネットワーク状態または重いネットワーク負荷の下でそれを見ることができます。

ネットワークレベルで明確にするために、TCPは指定された順序でデータを転送することを保証しますが、データの一部が送信したものと同じになることは保証されません。そのソフトウェア(たとえば、Nagleのアルゴリズムを見てください)、ハードウェア(トレース内のさまざまなルーター)、OSの実装には多くの理由があります。したがって、一般に、データのどの部分がすでに転送または受信されているかを想定しないでください。

長い紹介で申し訳ありませんが、いくつかのアドバイスの下に:

  1. 高性能ソケットサーバー用の関連する「新しい」APIを使用してみてください。ここでは、 .NETv4.0のネットワークサンプルをサンプルします。

  2. 常に完全なパケットを送信するとは限りません。Socket.EndSend()は、実際に送信するようにスケジュールされたバイト数を返します。ネットワークの負荷が高い場合は、1〜2バイトになることもあります。したがって、必要に応じて、バッファの残りの部分を再送信するように実装する必要があります。

    MSDNに警告があります:

    送信したデータがすぐにネットワークに表示される保証はありません。ネットワーク効率を高めるために、基盤となるシステムは、大量の発信データが収集されるまで送信を遅らせる場合があります。BeginSendメソッドが正常に完了すると、基盤となるシステムにネットワーク送信用にデータをバッファリングする余地があることを意味します。

  3. 常に完全なパケットを受信するとは限りません。受信したデータをある種のバッファに結合し、十分なデータがあるときに分析します。

  4. 通常、バイナリプロトコルの場合、受信するデータの量を示すフィールド、メッセージタイプのフィールド(またはメッセージタイプごとに固定長を使用できます(通常はバージョン管理の問題など))、バージョンフィールド(該当する場合)、CRCを追加します-フィールドからメッセージの終わりまで。

  5. 読む必要はなく、少し古く、Winsockに直接適用されますが、勉強する価値があるかもしれません:WinsockプログラマーのFAQ

  6. ProtocolBuffersを見てください。学ぶ価値があります:http ://code.google.com/p/protobuf-csharp-port/、http: //code.google.com/p/protobuf-net/

それが役に立てば幸い。

PS悲しいことに、MSDNでサンプルを参照すると、他の回答で述べられているように、非同期パラダイムが事実上台無しになります。

于 2011-03-24T15:53:50.963 に答える
5

あなたのコードは非常に間違っています。そのようなループを行うと、非同期プログラミングの目的が無効になります。非同期 IO は、スレッドをブロックせずに他の作業を続行させるために使用されます。そのようにループすることで、スレッドをブロックしています。

void StartListening()
{
    _listener.BeginAccept(OnAccept, null);
}

void OnAccept(IAsyncResult res)
{
    var clientSocket = listener.EndAccept(res);

    //begin accepting again
    _listener.BeginAccept(OnAccept, null);

   clientSocket.BeginReceive(xxxxxx, OnRead, clientSocket);
}

void OnReceive(IAsyncResult res)
{
    var socket = (Socket)res.Asyncstate;

    var bytesRead = socket.EndReceive(res);
    socket.BeginReceive(xxxxx, OnReceive, socket);

    //handle buffer here.
}

コードを簡潔にするために、すべてのエラー処理を削除したことに注意してください。このコードはどのスレッドもブロックしないため、はるかに効率的です。コードをサーバー処理コードとクライアント処理コードの 2 つのクラスに分割します。これにより、保守と拡張が容易になります。

次に理解すべきことは、TCP がストリーム プロトコルであることです。一度の受信でメッセージが届くことを保証するものではありません。したがって、メッセージの大きさまたはメッセージがいつ終了するかを知っておく必要があります。

最初の解決策は、最初に解析するヘッダーを各メッセージの前に付けてから、完全な本文/メッセージを取得するまで読み続けることです。

2 番目の解決策は、各メッセージの最後に何らかの制御文字を置き、その制御文字が読み取られるまで読み続けることです。実際のメッセージに存在する可能性がある場合は、その文字をエンコードする必要があることに注意してください。

于 2011-03-24T15:38:45.393 に答える
2

固定長のメッセージを送信するか、メッセージの長さをヘッダーに含める必要があります。パケットの開始を明確に識別できるものを用意してください。

于 2011-03-23T16:33:25.860 に答える
2

2 つの非常に役立つリンクを見つけました。

http://vadmyst.blogspot.com/2008/03/part-2-how-to-transfer-fixed-sized-data.html

C# 非同期 TCP ソケット: バッファ サイズと巨大な転送の処理

于 2011-07-31T02:35:26.067 に答える