Сокет C#.Длина сообщения приема

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

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

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

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

EDIT: например, я считаю, что .NET / Windows sockets не "обертывает" сообщения, чтобы гарантировать, что одно сообщение, отправленное с сокетом.Send () принимается в одном сокете.Receive () call? Или нет?

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

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 гарантирует передачу ваших данных в указанном порядке, но не гарантирует, что части данных будут такими же, как вы отправили. Есть много причин для этого программного обеспечения (взгляните на алгоритм Нэгла например), аппаратное обеспечение (разные маршрутизаторы в трассировке), реализация ОС, поэтому в целом вы никогда не должны предполагать, какая часть данных уже передана или получена.

извините за долгое введение, ниже некоторых советов:

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

  2. сделать не предположим, вы всегда отправляете полный пакет. гнездо.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/

надеюсь, что это помогает.

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


ваш код очень неправильно. Выполнение таких циклов побеждает цель асинхронного программирования. Async IO используется, чтобы не блокировать поток, но позволить им продолжать выполнять другую работу. Таким образом, вы блокируете нить.

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

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

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


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