C# Socket. Получать длину сообщения

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

До сих пор я смотрел здесь очень хорошо: http://msdn.microsoft.com/en-us/library/5w7b7x5f(v=VS.100).aspx и http://msdn.microsoft.com/en-us/library/fx6588te.aspx для идей.

Все сообщения, которые я отправляю, зашифрованы, поэтому я беру строковое сообщение, которое я хочу отправить, преобразую его в массив byte[], а затем зашифрую данные перед тем, как передать ожидаемую длину сообщения в данные и отправить его по соединению.,

Одна вещь, которая поражает меня как проблема, заключается в следующем: на принимающей стороне кажется возможным, что Socket.EndReceive() (или связанный с ним обратный вызов) мог бы вернуться, когда была получена только половина сообщения. Есть ли простой способ убедиться, что каждое сообщение получено "полным" и только одно сообщение за раз?

РЕДАКТИРОВАТЬ: Например, я так понимаю, сокеты.NET / Windows не "обертывают" сообщения, чтобы гарантировать, что одно сообщение, отправленное с Socket.Send(), получено в одном вызове Socket.Receive()? Или это?

Моя реализация до сих пор:

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

4 ответа

Решение

Да, возможно, у вас будет только часть сообщения за один прием, а во время передачи может быть еще хуже, будет отправлена ​​только часть сообщения. Обычно вы можете видеть это в плохих условиях сети или при большой нагрузке на сеть.

Для ясности на уровне сети TCP гарантирует передачу ваших данных в указанном порядке, но не гарантирует, что части данных будут такими же, как вы отправили. Существует много причин для этого программного обеспечения (посмотрите на алгоритм Nagle), аппаратное обеспечение (различные маршрутизаторы в трассировке), реализация ОС, поэтому в общем случае вы никогда не должны предполагать, какая часть данных уже передана или получена.

Извините за долгое знакомство, ниже несколько советов:

  1. Попробуйте использовать сравнительно "новый" API для высокопроизводительного сокет-сервера, вот примеры сетевых примеров для.NET v4.0

  2. Не думайте, что вы всегда отправляете полный пакет. Socket.EndSend () возвращает количество байтов, фактически запланированных для отправки, оно может быть даже 1-2 байта при большой загрузке сети. Таким образом, вы должны осуществить повторную отправку оставшейся части буфера, когда это потребуется.

    На MSDN есть предупреждение:

    Нет гарантии, что отправленные вами данные появятся в сети немедленно. Для повышения эффективности сети базовая система может задерживать передачу до тех пор, пока не будет собран значительный объем исходящих данных. Успешное завершение метода BeginSend означает, что в базовой системе есть место для буферизации ваших данных для отправки по сети.

  3. Не думайте, что вы всегда получаете полный пакет. Объедините полученные данные в какой-то буфер и проанализируйте их, когда данных будет достаточно.

  4. Обычно для двоичных протоколов я добавляю поле, чтобы указать объем поступающих данных, поле с типом сообщения (или вы можете использовать фиксированную длину для каждого типа сообщения (обычно это плохо, например, проблема с версиями)), поле версии (где применимо) и добавьте CRC-поле до конца сообщения.

  5. На самом деле его не нужно читать, он немного устарел и относится непосредственно к Winsock, но, возможно, стоит изучить: FAQ программиста Winsock

  6. Взгляните на ProtocolBuffers, его стоит узнать: http://code.google.com/p/protobuf-csharp-port/, http://code.google.com/p/protobuf-net/

Надеюсь, поможет.

PS К сожалению, пример на MSDN, который вы упоминаете, эффективно разрушает асинхронную парадигму, как указано в других ответах.

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

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 является потоковым протоколом. Не гарантируется, что сообщение поступит за один прием. Поэтому вы должны знать, насколько велико сообщение или когда оно заканчивается.

Первое решение состоит в том, чтобы поставить перед каждым сообщением заголовок, который вы сначала анализируете, а затем продолжить чтение, пока не получите полное тело / сообщение.

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

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

Другие вопросы по тегам