C # Socket. Получать длину сообщения - PullRequest
7 голосов
/ 23 марта 2011

В настоящее время я занимаюсь разработкой сервера сокетов 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 ]

10 голосов
/ 24 марта 2011

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

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

Извините за длинное введение, под некоторыми советами:

  1. Попробуйте использовать сравнительно «новый» API для высокопроизводительного сокет-сервера, здесьsamples Networking Samples для .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, который вы упоминаете, эффективно разрушает асинхронную парадигму, как указанов других ответах.

4 голосов
/ 24 марта 2011

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

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

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

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

2 голосов
/ 23 марта 2011

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

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