2

在玩够了异步套接字编程之后,我注意到服务器正在接收分块的有效负载(即:多个完整的有效负载位于同一个缓冲区中)。所以我想出了以下内容:

if (bytes_to_read > 0)
{
    while (bytes_to_read > 0)

        // Get payload size as int.

        // Get payload in byte format.

        // Do something with payload.

        // Decrease the amount of bytes to read.
    }

    // Wait for more data.
}

然后我注意到数据包碎片(即:我认为完整的有效载荷并不总是这样),这将之前的代码更改为:

if (bytes_to_read > 0)
{
    while (bytes_to_read > 0)
    {
        // Get payload size as int.

        // Check if the payload size is less than or equal to the amount of bytes left to read.
        if (payload_size <= bytes_to_read)
        {
            // Get payload in byte format.

            // Do something with payload.

            // Decrease the amount of bytes to read.
        }
        else
        {
            // We received a fragmented payload.
            break;
        }
    }

    if (bytes_to_read == 0)
    {
        // Wait for more data.
    }
    else if (bytes_to_read > 0)
    {
        // Wait for more data where we left off. ***
    }
    else
    {
        // Something awful happened.
    }
}

*** 我什至不知道该怎么做,想看看它的代码。我有一个想法,它涉及将未完成的有效负载复制到缓冲区的开头,然后从那里拾取。

我包含的伪代码基于我正在使用的 Begin* End* 方法(我知道我应该使用此处找到的 *Async 方法集 -> http://msdn.microsoft.com/en-us /library/system.net.sockets.socketasynceventargs.aspx <- 但我认为我的整体问题仍然适用)。

我正在寻找 2 个问题的答案——即:

  1. 这种方法是正确的还是我错过了什么?
  2. 在 C# 中处理数据包碎片的工作示例是什么样的?

编辑:我正在使用原始套接字。

提前感谢您的所有帮助。

编辑:John Saunders 和 Greg Hewgill 提出了将数据视为流的观点,但这并没有为我提供如何处理有时被碎片化的最后一个分块有效负载的具体示例。

编辑:我在这里阅读了 Jon Skeet 的答案,该答案与我看到的其他答案基本相同,但它对我没有多大帮助,因为我已经得到了我必须做的事情,但不知道如何去做。

编辑:要详细说明碎片的含义,请考虑以下接收缓冲区:

  • 224TEST3foo3bar
  • 224TEST3foo3bar224TEST3foo3bar
  • 224TEST3foo3bar224TEST3foo
  • 3bar224TEST3foo3bar

编辑:我发现这个这个导致我来到这里Vadym Stetsiak几乎清除了所有问题(他是我一直在寻找的答案之一)。

4

2 回答 2

3

这可能与碎片化有关,也可能无关。

一般来说,套接字一次会传递给你尽可能多的字节。您的工作是了解整个消息中有多少字节,并全部阅读。继续循环,直到获得所需的所有字节,或者直到出现异常。


以下代码目前未经测试。我想我会在编写它的服务器端并测试两者之前发布它。

private static string ReceiveMessage(Socket socket)
{
    const int BUFFER_SIZE = 1024;
    var inputBuffer = new byte[BUFFER_SIZE];
    var offset = 0;
    var bytesReceived = socket.Receive(
        inputBuffer, offset, BUFFER_SIZE - offset, SocketFlags.None);
    if (bytesReceived < 2)
    {
        throw new InvalidOperationException("Receive error");
    }

    var inputMessageLength = inputBuffer[0]*256 + inputBuffer[1];
    offset += bytesReceived;
    var totalBytesReceived = bytesReceived;
    while (bytesReceived > 0 &&
           totalBytesReceived < inputMessageLength + 2)
    {
        bytesReceived = socket.Receive(
            inputBuffer, offset, BUFFER_SIZE - offset, SocketFlags.None);
        offset += bytesReceived;
        totalBytesReceived += bytesReceived;
    }

    return Encoding.UTF8.GetString(
        inputBuffer, 2, totalBytesReceived - 2);
}

注意收到的消息长度是错误的。套接字层一次可以给我一个字节。作为重构的一部分,我将重新审视它,它将接收计数到一个单独的两字节缓冲区,并将循环更改为单个 do/while。

于 2009-07-15T01:57:01.733 回答
0

当你必须自己做时,可以这样做(参考这里):

/// 
/// Server state holds current state of the client socket
///
class AsyncServerState
{
   public byte[] Buffer = new byte[512]; //buffer for network i/o
   public int DataSize = 0; //data size to be received by the server

   //flag that indicates whether prefix was received
   public bool DataSizeReceived = false;

   public MemoryStream Data = new MemoryStream(); //place where data is stored
   public SocketAsyncEventArgs ReadEventArgs = new SocketAsyncEventArgs();
   public Socket Client;
}

/// 
/// Implements server receive logic
/// 
private void ProcessReceive(SocketAsyncEventArgs e)
{
    //single message can be received using several receive operation
    AsyncServerState state = e.UserToken as AsyncServerState;

    if (e.BytesTransferred <= 0 || e.SocketError != SocketError.Success)
    {
        CloseConnection(e);
    }

    int dataRead = e.BytesTransferred;
    int dataOffset = 0;
    int restOfData = 0;

    while (dataRead > 0)
    {
        if (!state.DataSizeReceived)
        {
            //there is already some data in the buffer
            if (state.Data.Length > 0)
            {
                restOfData = PrefixSize - (int)state.Data.Length;
                state.Data.Write(state.Buffer, dataOffset, restOfData);
                dataRead -= restOfData;
                dataOffset += restOfData;
            }
            else if (dataRead >= PrefixSize)
            {   //store whole data size prefix
                state.Data.Write(state.Buffer, dataOffset, PrefixSize);
                dataRead -= PrefixSize;
                dataOffset += PrefixSize;
            }
            else
            {   // store only part of the size prefix
                state.Data.Write(state.Buffer, dataOffset, dataRead);
                dataOffset += dataRead;
                dataRead = 0;
            }

            if (state.Data.Length == PrefixSize)
            {   //we received data size prefix
                state.DataSize = BitConverter.ToInt32(state.Data.GetBuffer(), 0);
                state.DataSizeReceived = true;

                state.Data.Position = 0;
                state.Data.SetLength(0);
            }
            else
            {   //we received just part of the headers information
                //issue another read
                if (!state.Client.ReceiveAsync(state.ReadEventArgs))
                    ProcessReceive(state.ReadEventArgs);
                    return;
            }
        }

        //at this point we know the size of the pending data
        if ((state.Data.Length + dataRead) >= state.DataSize)
        {   //we have all the data for this message

            restOfData = state.DataSize - (int)state.Data.Length;

            state.Data.Write(state.Buffer, dataOffset, restOfData);
            Console.WriteLine("Data message received. Size: {0}",
                                  state.DataSize);

            dataOffset += restOfData;
            dataRead -= restOfData;

            state.Data.SetLength(0);
            state.Data.Position = 0;
            state.DataSizeReceived = false;
            state.DataSize = 0;

            if (dataRead == 0)
            {
                if (!state.Client.ReceiveAsync(state.ReadEventArgs))
                    ProcessReceive(state.ReadEventArgs);
                    return;
            }
            else
                continue;
        }
        else
        {   //there is still data pending, store what we've
            //received and issue another BeginReceive
            state.Data.Write(state.Buffer, dataOffset, dataRead);

            if (!state.Client.ReceiveAsync(state.ReadEventArgs))
                ProcessReceive(state.ReadEventArgs);

            dataRead = 0;
        }
    }
}

我自己并没有完全这样做,但它有所帮助。

于 2009-07-30T06:32:47.510 回答