Проблема расчета длины байта при отправке данных через сокеты - PullRequest
2 голосов
/ 30 июля 2011

Я пишу клиент-серверное приложение.Кажется, это хорошо работает при подключении с использованием адреса обратной связи, но при подключении по локальной сети или через Интернет у меня возникает очень странная проблема, которую я просто не могу понять.

Все данные, передаваемые между сервером и клиентом, содержатся в классе под названием «DataPacket».Затем этот класс сериализуется в двоичный файл, чтобы его можно было отправлять с использованием сокетов.

Перед отправкой пакета присоединяется 4-байтовый заголовок, который содержит длину пакета в байтах, чтобы получатель знал, сколько байтовожидать, прежде чем он может быть десериализован.Проблема в том, что в какой-то момент получатель считает, что он ожидает гораздо больше данных, чем фактически отправлено в заголовке.Иногда эта цифра исчисляется миллионами байтов, что вызывает серьезные проблемы.

Вот как отправляются данные:

public override void Send(DataPacket packet)
{
    byte[] content = packet.Serialize();
    byte[] header = BitConverter.GetBytes(content.Length);

    List<Byte> bytes = new List<Byte>();
    bytes.AddRange(header);
    bytes.AddRange(content);

    if (socket.Connected)
    {
        socket.BeginSend(bytes.ToArray(), 0, bytes.Count, SocketFlags.None, OnSendPacket, socket);
    }
}

На стороне сервера каждому клиенту назначается StateObject- это класс, который содержит пользовательский сокет, буфер и любые данные, которые были получены до сих пор, которые являются неполными.

Вот так выглядит класс StateObject:

public class StateObject
{
    public const int HeaderSize = 4;
    public const int BufferSize = 8192;

    public Socket Socket { get; private set; }
    public List<Byte> Data { get; set; }
    public byte[] Header { get; set; }
    public byte[] Buffer { get; set; }

    public StateObject(Socket sock)
    {
        Socket = sock;
        Data = new List<Byte>();
        Header = new byte[HeaderSize];
        Buffer = new byte[BufferSize];
    }
}

Вот как используется класс StateObject:

private void OnClientConnect(IAsyncResult result)
{
    Socket serverSock = (Socket)result.AsyncState;
    Socket clientSock = serverSock.EndAccept(result);

    StateObject so = new StateObject(clientSock);
    so.Socket.BeginReceive(so.Buffer, 0, StateObject.BufferSize, SocketFlags.None, OnReceiveData, so);
}

Когда некоторые данные получены от клиента,EndReceive называется.Если StateObject.Data пуст, то предполагается, что это первая часть нового пакета, поэтому заголовок проверяется.Если количество полученных байтов меньше размера, указанного в заголовке, я снова вызываю BeginReceive, повторно используя тот же StateObject.

Вот код для OnReceiveData:

private void OnReceiveData(IAsyncResult result)
{
    StateObject state = (StateObject)result.AsyncState;

    int bytes = state.Socket.EndReceive(result);
    if (bytes > 0)
    {
        state.ProcessReceivedData(bytes);

        if (state.CheckDataOutstanding())
        {
            //Wait until all data has been received.
            WaitForData(state);
        }
        else
        {
            ThreadPool.QueueUserWorkItem(OnPacketReceived, state);

            //Continue receiving data from client.
            WaitForData(new StateObject(state.Socket));
        }
    }
}

Как видно выше, сначала я вызываю state.ProcessReceivedData () - здесь я отсоединяю заголовок от данных, а затем перемещаю данные из буфера:

public void ProcessReceivedData(int byteCount)
{
    int offset = 0;

    if (Data.Count == 0)
    {
        //This is the first part of the message so get the header.
        System.Buffer.BlockCopy(Buffer, 0, Header, 0, HeaderSize);
        offset = HeaderSize;
    }

    byte[] temp = new byte[byteCount - offset];
    System.Buffer.BlockCopy(Buffer, offset, temp, 0, temp.Length);
    Data.AddRange(temp);
}

Затем вызываю CheckDataOutstanding ()сравнить количество полученных байтов с размером в заголовке.Если они совпадают, тогда я могу безопасно десериализовать данные:

public bool CheckDataOutstanding()
{
    int totalBytes = BitConverter.ToInt32(Header, 0);
    int receivedBytes = Data.Count;
    int outstanding = totalBytes - receivedBytes;

    return outstanding > 0;
}

Проблема в том, что каким-то образом значение в заголовке оказывается не таким, как было отправлено.Это происходит случайно, иногда на сервере, а иногда на клиенте.Из-за случайности и общей сложности отладки как сервера, так и клиента по сети, я нахожу практически невозможным это выяснить.

Есть ли что-то очевидное или глупое, что я здесь упускаю?

1 Ответ

2 голосов
/ 30 июля 2011

Что произойдет, если вы получите только часть заголовка? То есть, предположим, вы получили три байта в первом чтении вместо четырех или более? Ваша ProcessReceivedData функция должна будет учитывать это и не пытаться извлечь размер заголовка, пока у вас не будет не менее четырех байтов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...