簡體   English   中英

C#Socket.Receive消息長度

[英]C# Socket.Receive message length

我目前正在開發一個可以接受來自多個客戶端計算機的多個連接的C#Socket服務器。 服務器的目標是允許客戶端從服務器事件“訂閱”和“取消訂閱”。

到目前為止,我已經在這里采取了一個快樂的好看: http//msdn.microsoft.com/en-us/library/5w7b7x5f( v = VS.100) .aspxhttp://msdn.microsoft.com/ en-us / library / fx6588te.aspx的想法。

我發送的所有消息都是加密的,所以我接收了我希望發送的字符串消息,將其轉換為byte []數組,然后在將消息長度預先掛起到數據之前加密數據並通過連接發送出去。

令我印象深刻的一件事是:在接收端,當接收到一半消息時,Socket.EndReceive()(或相關的回調)似乎可能會返回。 是否有一種簡單的方法可以確保每封郵件都“完整”並且一次只收到一封郵件?

編輯:例如,我認為.NET / Windows套接字沒有“包裝”消息,以確保在一個Socket.Receive()調用中收到與Socket.Send()一起發送的單個消息? 或者是嗎?

到目前為止我的實施:

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);
    }         
}

是的,你可能每次接收只有部分信息,在傳輸過程中可能會更糟,只會發送部分信息。 通常您可以在網絡狀況不佳或網絡負載較重時看到。

要在網絡級別上清楚,TCP保證按指定的順序傳輸數據,但不能保證部分數據與您發送的數據相同。 該軟件有很多原因(例如看看Nagle的算法 ),硬件(跟蹤中的不同路由器),操作系統實現,所以一般情況下你不應該假設已經傳輸或接收了哪些數據。

很抱歉很長時間的介紹,下面是一些建議:

  1. 嘗試為高性能套接字服務器使用相關的“新”API,這里為.NET v4.0的網絡樣本示例

  2. 不要以為你總是發送完整的數據包。 Socket.EndSend()返回實際調度發送的字節數,在網絡負載較重的情況下甚至可以是1-2個字節。 因此,您必須在需要時實現重新發送緩沖區的其余部分。

    MSDN上有警告:

    無法保證您發送的數據會立即顯示在網絡上。 為了提高網絡效率,底層系統可能會延遲傳輸,直到收集到大量的傳出數據。 成功完成BeginSend方法意味着底層系統有足夠的空間來緩沖網絡發送的數據。

  3. 不要以為你總是收到完整的數據包。 將接收到的數據加入某種緩沖區並在有足夠數據時進行分析。

  4. 通常,對於二進制協議,我添加字段以指示傳入的數據量,具有消息類型的字段(或者您可以使用每種消息類型的固定長度(通常不好,例如版本控制問題)),版本字段(如果適用)並添加CRC -field到消息結束。

  5. 它不是真的需要閱讀,有點舊並直接適用於Winsock但可能值得研究: Winsock程序員的常見問題解答

  6. 看看ProtocolBuffers ,值得學習: http//code.google.com/p/protobuf-csharp-port/,http : //code.google.com/p/protobuf-net/

希望能幫助到你。

PS遺憾的是你在MSDN上的樣本有效地破壞了異步范式,如其他答案中所述。

你的代碼非常錯誤。 像這樣做循環會破壞異步編程的目的。 Async 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.
}

請注意,我已刪除所有錯誤處理以使代碼更清晰。 該代碼不會阻止任何線程,因此更有效。 我會在兩個類中分解代碼:服務器處理代碼和客戶端處理代碼。 它使維護和擴展更容易。

接下來要理解的是TCP是一種流協議。 它不保證消息到達一個Receive。 因此,您必須知道消息的大小或消息何時結束。

第一種解決方案是為每條消息添加一個首先解析的標題,然后繼續閱讀,直到獲得完整的正文/消息。

第二種解決方案是在每條消息的末尾放置一些控制字符並繼續讀取,直到讀取控制字符。 請記住,如果該字符可以存在於實際消息中,則應對該字符進行編碼。

您需要發送固定長度的消息或在標頭中包含消息的長度。 嘗試使用能夠清楚識別數據包開始的內容。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM