Очень странная проблема отправки данных через сокеты в C#

извините за этот длинный пост. Я сделал это как можно меньше, все еще передавая проблему.

ок, это сводит меня с ума. У меня есть клиент и программа-сервер на C#. Сервер отправляет данные клиенту через сокет.Посылать.)( Клиент получает данные через сокет.BeginReceive и розетка.Получить. Мой псевдо-протокол выглядит следующим образом: сервер отправляет значение two-bye (short), указывающее длину фактических данных, за которыми немедленно следует фактические данные. Клиент читает первые два байта асинхронно, преобразует байты в короткие и сразу же считывает, что многие байты из сокета синхронно.

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

серверный метод отправки данных:

private static object myLock = new object();
private static bool sendData(Socket sock, String prefix, byte[] data)
{
    lock(myLock){
        try
        {
            // prefix is always a 4-bytes string
            // encoder is an ASCIIEncoding object    
            byte[] prefixBytes = encoder.GetBytes(prefix);
            short length = (short)(prefixBytes.Length + data.Length);

            sock.Send(BitConverter.GetBytes(length));
            sock.Send(prefixBytes);
            sock.Send(data);

            return true;
        } 
        catch(Exception e){/*blah blah blah*/}
    }
}

метод на стороне клиента для получения данных:

private static object myLock = new object();
private void receiveData(IAsyncResult result)
{
    lock(myLock){
        byte[] buffer = new byte[1024];
        Socket sock = result.AsyncState as Socket;
        try
        {
            sock.EndReceive(result);
            short n = BitConverter.ToInt16(smallBuffer, 0);
            // smallBuffer is a 2-byte array

            // Receive n bytes
            sock.Receive(buffer, n, SocketFlags.None);

            // Determine the prefix.  encoder is an ASCIIEncoding object
            String prefix = encoder.GetString(buffer, 0, 4);

            // Code to process the data goes here

            sock.BeginReceive(smallBuffer, 0, 2, SocketFlags.None, receiveData, sock);
        }
        catch(Exception e){/*blah blah blah*/}
    }
}

серверный код для надежного воссоздания проблемы:

byte[] b = new byte[1020];  // arbitrary length

for (int i = 0; i < b.Length; i++)
    b[i] = 7;  // arbitrary value of 7

while (true)
{
    sendData(socket, "PRFX", b);
    // socket is a Socket connected to a client running the same code as above
    // "PRFX" is an arbitrary 4-character string that will be sent
}

глядя на код выше, можно определить, что сервер навсегда отправит число 1024, длину общих данных, включая префикс, как короткий( 0x400), за которым следует "PRFX" в двоичном формате ASCII, а затем куча 7 (0x07). Клиент навсегда прочитает первые два байта (0x400), интерпретирует это как 1024, сохраняет это значение как n, а затем считывает 1024 байта из потока.

это действительно то, что он делает для первых 40 или около того итераций, но, спонтанно, клиент будет читать первые два byes и интерпретировать их как 1799, а не 1024! 1799 в Хекс-0x0707 что два подряд 7-х!!! То есть данные, а не длина! Что случилось с этими двумя байтами? Это происходит с любым значением, которое я помещаю в массив байтов, я просто выбрал 7, потому что легко увидеть корреляцию с 1799.

Если вы все еще читаете это, я приветствую Вашу преданность.

некоторые важные замечания:

  • уменьшение длины b увеличит количество итераций до возникновения проблемы, но не предотвратит проблему от происходящее
  • добавление значительной задержки между каждой итерацией цикла может предотвратить возникновение проблемы
  • этой НЕ происходит при использовании как клиента, так и сервера на одном хосте и при подключении через петлевой адрес.

как уже упоминалось, это сводит меня с ума! Я всегда могу решить свои проблемы с программированием, но это полностью поставило меня в тупик. Поэтому я здесь, умоляя о любом совете или знании об этом предмет:.

спасибо.

5 ответов


Я подозреваю, что вы предполагаете, что все сообщение доставлено одним вызовом receiveData. В общем, это не так. Фрагментация, задержки и т. д. могут в конечном итоге означать, что данные поступают в каплях, так что вы можете получить свой receiveData функция вызывается, когда есть только, например, 900 байт готовы. Ваш звонок sock.Receive(buffer, n, SocketFlags.None); говорит: "Дайте мне данные в буфер, до n bytes" - вы можете получить меньше, и количество фактически прочитанных байтов возвращается Receive.

это объясняет, почему снижение b, добавление дополнительной задержки или использование одного и того же хоста, похоже, "исправляет" проблему - шансы значительно увеличиваются, что все сообщение поступит за один раз, используя эти методы (меньше b, меньшие данные; добавление задержки означает, что в трубе меньше общих данных; нет сети на пути для локального адреса).

чтобы узнать, действительно ли это проблема, зарегистрируйте возвращаемое значение Receive. Это будет иногда меньше, чем 1024 если мой диагноз верен. Чтобы исправить это, вам нужно либо подождать, пока буфер локального сетевого стека не будет иметь все ваши данные (не идеально), либо просто получать данные по мере их поступления, сохраняя их локально, пока не будет готово целое сообщение и его обработка.


мне кажется, что основная проблема здесь не проверять результат EndReceive который число прочитанных байтов. Если сокет открыт, это может быть что угодно > 0 и <= макс вы указали. Небезопасно предполагать, что, поскольку вы запросили 2 байта, было прочитано 2 байта. То же самое при чтении фактических данных.

вы всегда должны делать цикл, накапливая ожидаемый объем данных (и уменьшая оставшееся количество и увеличивая смещение, соответственно.)

сочетание sync/async не поможет сделать его легким, либо:£


sock.Receive(buffer, n, SocketFlags.None);

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


при использовании сокетов вы должны предвидеть, что сокет может передавать меньше байтов, чем вы ожидаете. Вы должны петля На .Метод Receive для получения оставшейся части байтов.

Это также верно, когда вы отправляете байты через сокет. Вы должны проверить, сколько байтов было отправлено, и цикл на отправке, пока все байты не будут отправлены.

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

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


Я предполагаю, что smallBuffer объявляется вне метода receive и повторно используется из нескольких потоков. Другими словами, это небезопасно.

Writeing хороший код сокета сложно. Поскольку вы пишете как клиент, так и сервер, вы можете взглянуть на Windows Communication Foundation (WCF), который позволяет отправлять и получать целые объекты с гораздо меньшими хлопотами.