Обработка сброшенных пакетов TCP в C # - PullRequest
7 голосов
/ 27 августа 2009

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

Я отправляю 20000 строк с помощью метода socket.Send () и получаю их с помощью цикла, который выполняет socket.Receive (). Каждая строка ограничена уникальными символами, которые я использую для подсчета полученного числа (это протокол, если хотите). Протокол доказан тем, что даже с фрагментированными сообщениями каждая строка считается правильно. На моей локальной машине я получаю все 20000, через Интернет я получаю что-нибудь между 17000-20000. Кажется, хуже будет медленное соединение с удаленным компьютером. Чтобы добавить путаницу, включение Wireshark, похоже, уменьшает количество пропущенных сообщений.

Прежде всего, что вызывает это? Это проблема TCP / IP или что-то не так с моим кодом?

Во-вторых, как я могу обойти это? Получение всех 20000 строк жизненно важно.

Код получения розетки:

private static readonly Encoding encoding = new ASCIIEncoding();
///...
while (socket.Connected)
{
    byte[] recvBuffer = new byte[1024];
    int bytesRead = 0;

    try
    {
        bytesRead = socket.Receive(recvBuffer);
    }
    catch (SocketException e)
    {
    if (! socket.Connected)
    {
        return;
    }
    }

    string input = encoding.GetString(recvBuffer, 0, bytesRead);
    CountStringsIn(input);
}

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

private static readonly Encoding encoding = new ASCIIEncoding();
//...
socket.Send(encoding.GetBytes(string));

Ответы [ 4 ]

4 голосов
/ 27 августа 2009

Что ж, в вашем коде есть одна причина, с которой нужно начинать, если вы подсчитываете количество вызовов до Receive, которые завершены: вы, похоже, предполагаете, что вы увидите, что столько вызовов Receive завершено, сколько вы совершил Send звонков.

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

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

3 голосов
/ 27 августа 2009

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

То, что вы видите, скорее всего, является результатом алгоритма Нейгла. Вместо отправки каждого бита данных, когда вы его публикуете, он отправляет один байт, а затем ожидает подтверждения с другой стороны. Пока он ожидает, он объединяет все остальные данные, которые вы хотите отправить, объединяет их в один большой пакет и затем отправляет его. Поскольку максимальный размер для TCP составляет 65 КБ, он может объединить довольно много данных в один пакет, хотя это крайне маловероятно, особенно потому, что размер буфера по умолчанию в Winsock составляет около 10 КБ или около того (я забыл точное количество). Кроме того, если максимальный размер окна получателя меньше 65 КБ, он отправит только столько, сколько было объявлено в последнем объявленном размере окна получателя. Размер окна также влияет на алгоритм Нейгла с точки зрения того, сколько данных он может агрегировать перед отправкой, потому что он не может отправить больше, чем размер окна.

Причина, по которой вы это видите, заключается в том, что в Интернете, в отличие от вашей сети, первое подтверждение требует больше времени для возврата, поэтому алгоритм Naggle объединяет больше ваших данных в один пакет. Локально, возврат фактически мгновенный, поэтому он может отправлять ваши данные так же быстро, как вы можете отправить их в сокет. Вы можете отключить алгоритм Naggle на стороне клиента, используя SetSockOpt (winsock) или Socket.SetSocketOption (.Net), но я настоятельно рекомендую НЕ отключать Naggling на сокете, если вы не уверены на 100%, что знаете, что делаете. Это там по очень веской причине.

3 голосов
/ 27 августа 2009

Это определенно не вина TCP. TCP гарантирует доставку в порядке, точно один раз.

Какие строки «отсутствуют»? Держу пари, что это последние; попробуйте очистить с отправляющего конца.

Более того, ваш «протокол» здесь (я имею в виду протокол прикладного уровня, который вы изобретаете) отсутствует: вам следует рассмотреть возможность отправки # объектов и / или их длины, чтобы получатель знал, когда он на самом деле сделал получать их.

1 голос
/ 27 августа 2009

Как долго каждая из строк? Если они не точно 1024 байта, они будут объединены удаленным стеком TCP / IP в один большой поток, который вы читаете большими блоками в своем вызове Receive.

Например, при использовании трех вызовов Send для отправки «A», «B» и «C», скорее всего, ваш удаленный клиент получит «ABC» (поскольку удаленный стек или ваш собственный стек будут буферизовать байты пока они не прочитаны). Если вам нужно, чтобы каждая строка приходила без слияния с другими строками, посмотрите на добавление «протокола» с идентификатором, чтобы показать начало и конец каждой строки, или, альтернативно, настройте сокет , чтобы избежать буферизации и объединение пакетов.

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