Как выглядит рабочий пример работы с фрагментацией пакетов в C # при использовании метода Begin * End *? - PullRequest
2 голосов
/ 15 июля 2009

После достаточного количества игр с асинхронным программированием сокетов я заметил, что сервер получал фрагментированные полезные нагрузки (то есть: более одной полной полезной нагрузки, находящейся в одном и том же буфере). Итак, я придумал следующее:

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 <- но я думаю, что мой общий вопрос все еще применяется). </p>

Я ищу ответы на 2 вопроса, а именно:

  1. Правильный ли это подход или я что-то упустил?
  2. Что означает рабочий пример имеем дело с фрагментацией пакетов в C # похож?

РЕДАКТИРОВАТЬ: я использую сырые сокеты.

Заранее спасибо за вашу помощь.

РЕДАКТИРОВАТЬ: Джон Сондерс и Грег Хьюгилл подняли вопрос о том, как обрабатывать данные как поток, но это не дает мне конкретного примера того, как справляться с последними фрагментами полезной нагрузки, иногда фрагментированными.

РЕДАКТИРОВАТЬ: Я прочитал ответ Джона Скита здесь , который в основном совпадает с другими ответами, которые я видел, но это мне не очень помогает, так как я уже получаю то, что должен сделать, но не как это сделать.

РЕДАКТИРОВАТЬ: Чтобы уточнить, что я имею в виду под фрагментацией, рассмотрим следующие буферы приема:

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

РЕДАКТИРОВАТЬ: я нашел это и это , что привело меня сюда . Вадим Стецяк очистил почти все (его был один из ответов, которые я искал).

Ответы [ 2 ]

3 голосов
/ 15 июля 2009

Это может иметь или не иметь никакого отношения к фрагментации.

Как правило, сокет будет пропускать столько байтов за раз, сколько ему кажется. Ваша задача - узнать, сколько байтов содержится в вашем общем сообщении, и прочитать их все. Просто продолжайте цикл до тех пор, пока не получите все необходимые байты или пока не возникнет исключение.


Следующий код сейчас не проверен. Я думал, что выложу его перед тем, как написать его на стороне сервера и протестировать оба.

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.

0 голосов
/ 30 июля 2009

Когда вам нужно сделать это самостоятельно, это можно сделать так ( ссылка здесь ):

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

Я сам так не делал, но это помогло.

...