簡體   English   中英

C#異步套接字客戶端/服務器

[英]C# Asynchronous Socket client/server

我在使用套接字編程TCP客戶端/服務器時遇到麻煩。 我制作了一個小程序,使用我在這里找到的MSDN示例: http : //msdn.microsoft.com/en-us/library/bew39x2a%28v=vs.110%29.aspx,但做了一些修改,例如在發送功能上排隊:

public void Send(String data)
{
    // Wait for server connection
    if (connectDone.WaitOne())
    {
        // If already sending data, add to the queue
        // it will be sent by SendCallback method
        if (_sending)
        {
            _queue.Enqueue(data);
        }
        else
        {
            // Convert the string data to byte data using ASCII encoding.
            byte[] byteData = Encoding.ASCII.GetBytes(data);

            // Begin sending the data to the remote device.
            _sending = true;
            SocketHolder.BeginSend(byteData, 0, byteData.Length, 0, SendCallback, SocketHolder);
        }
    }
}

服務器通過readcallback函數讀取數據:

    public void ReadCallback(IAsyncResult ar)
    {
        string content = string.Empty;

        // Retrieve the state object and the handler socket
        // from the asynchronous state object.
        StateObject state = (StateObject)ar.AsyncState;
        Socket handler = state.workSocket;

        // Read data from the client socket. 
        int bytesRead = handler.EndReceive(ar);

        if (bytesRead > 0)
        {
            // There  might be more data, so store the data received so far.
            state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead));

            // Check for end of message tag & raise event
            content = state.sb.ToString();
            if (content.IndexOf("</MetrixData>") > -1)
            {
                Console.WriteLine("Read {0} bytes from socket. \n Data : {1}", content.Length, content);
                OnMessageReceived(EventArgs.Empty, content);
                state.sb = new StringBuilder(); // Clear the message from state object string builder
            }
            handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, ReadCallback, state);

        }
    }

好了,現在這是我的問題 :我的應用程序的重點是將變量高頻發送到服務器(每個變量最多每秒60次)。 但是,當我嘗試快速更新數據時,似乎我的狀態對象的字符串生成器沒有時間正確清除,因為我同時在OnMessageReceived函數中接收到多個數據(這是一個問題,因為我序列化我發送的每個數據,因此我在服務器接收到的消息中以多個根元素結尾)。

如果您想仔細看一下,可以在這里找到整個項目(不確定我的解釋是否清楚...)

預先感謝您的幫助和您的時間:)


編輯:對不起,我將嘗試對我的問題進行更好的解釋:p

這是我僅更新數據時正確發送和接收的消息示例。

<MetrixData>
  <DataId>IntTest</DataId>
  <Type>System.Int32</Type>
  <Value TimeStamp="22/07/14 22:22:19">10</Value>
</MetrixData>

如果我在很短的時間內多次更新數據,這就是服務器收到的信息。

<MetrixData>
  <DataId>IntTest</DataId>
  <Type>System.Int32</Type>
  <Value TimeStamp="22/07/14 22:25:06">12</Value>
</MetrixData><MetrixData>
  <DataId>IntTest</DataId>
  <Type>System.Int32</Type>
  <Value TimeStamp="22/07/14 22:25:06">13</Value>
</MetrixData><MetrixData>
  <DataId>IntTest</DataId>
  <Type>System.Int32</Type>
  <Value TimeStamp="22/07/14 22:25:06">14</Value>
</MetrixData>

(可以同時接收2、3 ...或最多10條消息)

我不知道為什么我的ReadCallback不能檢測到當前消息的結尾並且不重置緩沖區,這應該由ReadCallBack的此示例完成

// Check for end of message tag & raise event
        content = state.sb.ToString();
        if (content.IndexOf("</MetrixData>") > -1)
        {
            Console.WriteLine("Read {0} bytes from socket. \n Data : {1}", content.Length, content);
            OnMessageReceived(EventArgs.Empty, content);
            state.sb = new StringBuilder(); // Clear the message from state object string builder
        }

TCP為您提供字節流。 TCP中沒有消息。 您可能在一次讀取中收到了兩條邏輯消息。 當您希望處理運行兩次時,這會使處理執行一次。

如果需要消息語義,則必須自己實現它們。 通常,原始TCP連接一開始是錯誤的。 為什么不使用HTTP或Web服務這樣的高級原語? 至少要使用protopuf作為序列化格式。 如果您執行了任何這些操作,則不會發生此問題。 當您自己實施有線協議時,還有更多地雷在您面前。

另外,一旦超出ASCII范圍(換句話說,當此應用投入生產時),字符串編碼將使您失敗。 同樣,此問題僅存在是因為您正在執行所有這些低級工作。 通過網絡電纜發送消息和字符串已自動完成。 利用這項工作。

這是我的推測。 我認為沒有數據包定向(UDP)的情況下,您只有連續的TCP流。 您需要實現以下之一:

  • 恢復為UDP(可能使用Lidgren)=>非常昂貴且耗時;
  • 用分隔符分隔流=>需要一組明確定義的字母和分隔符,例如:在BSON(二進制JSON)中,您具有base-64字母,並且可以使用任何非base-64字符作為分隔符如“ |”;
  • 實現一個消息信封,使您可以分隔單個消息=>這是您正在執行的自然解決方案。

郵件信封很簡單:

  • 使用MemoryStream緩沖輸出數據;
  • 使用BinaryWriter寫入該緩沖區;
  • 有一個“發送/傳輸/提交/提交”方法,可以關閉信封並將其發送到NetworkStream上。

這是一個快速的提示:

// NetworkStream ns; <-- already set up

using(MemoryStream memory = new MemoryStream())
using(BinaryWriter writer = new BinaryWriter(memory))
{
    // all output to writer goes here

    // now close the envelop and send it:
    byte[] dataBuffer, sizeBuffer;

    dataBuffer = memory.ToArray();
    sizeBuffer = BitConverter.GetBytes(dataBuffer.Length);
    ns.SendBytes(sizeBuffer, 0, 4); // send message length (32 bit int)
    ns.SendBytes(dataBuffer, 0, dataBuffer.Length); // send message data
}

在該行的另一端,您將打開信封並按照相反的過程提取消息:

// NetworkStream ns; <-- already set up

byte[] sizeBuffer, dataBuffer;
int size;

sizeBuffer = new byte[4];
ns.ReadBytes(sizeBuffer, 0, 4); // read message length
size = BitConverter.ToInt(sizeBuffer);

dataBuffer = new byte[size];
ns.ReadBytes(dataBuffer, 0, size); // read message data

using(MemoryStream memory = new MemoryStream(dataBuffer))
using(BinaryReader reader = new BinaryReader(memory))
{
    // all input from reader goes here
}

暫無
暫無

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

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