Очень странная проблема отправки данных через сокеты в C # - PullRequest
7 голосов
/ 31 января 2011

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

Хорошо, это сводит меня с умаУ меня есть клиентская и серверная программа, обе на C #.Сервер отправляет данные клиенту через Socket.Send ().Клиент получает данные через Socket.BeginReceive и Socket.Receive.Мой псевдопротокольный протокол выглядит следующим образом: Сервер отправляет двухбайтовое (короткое) значение, указывающее длину фактических данных, за которыми немедленно следуют фактические данные.Клиент считывает первые два байта асинхронно, преобразует байты в короткий и немедленно считывает столько байтов из сокета синхронно.

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

Метод отправки на стороне сервера:

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 или около того итераций, но, спонтанно, клиент читает первые два байта и интерпретирует их как 1799, а не 1024!1799 в шестнадцатеричном виде - это 0x0707, что является двумя последовательными семерками !!!Это данные, а не длина!Что случилось с этими двумя байтами?Это происходит с любым значением, которое я ввел в байтовый массив, я просто выбрал 7, потому что легко увидеть корреляцию с 1799.

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

Некоторые важные наблюдения:

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

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

Спасибо.

Ответы [ 5 ]

3 голосов
/ 31 января 2011

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

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

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

2 голосов
/ 31 января 2011
sock.Receive(buffer, n, SocketFlags.None);

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

2 голосов
/ 31 января 2011

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

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

Сочетание синхронизации / асинхронности не поможет облегчить ситуацию: £

1 голос
/ 31 января 2011

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

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

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

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

0 голосов
/ 31 января 2011

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

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

...