Что я могу сделать, чтобы избежать TCP Zero Window / TCP Window Full на стороне получателя? - PullRequest
8 голосов
/ 08 августа 2010

У меня есть небольшое приложение, которое отправляет файлы по сети агенту, расположенному в ОС Windows.

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

Но, когда это приложение работает в Linux (RedHat 5.3, получатель по-прежнему Windows), я вижу в трассировке сообщений Wireshark TCP Zero Window и TCP Window Full, которые появляются каждые 1-2 секунды. Затем агент закрывает соединение через несколько минут.

Код Windows - Linux почти такой же, и довольно простой. Единственной нетривиальной операцией является setsockopt с SO_SNDBUF и значением 0xFFFF. Удаление этого кода не помогло.

Может кто-нибудь помочь мне с этим вопросом?

РЕДАКТИРОВАТЬ: добавление кода отправки - похоже, он правильно обрабатывает частичные записи:

int totalSent=0;
while(totalSent != dataLen)
{
    int bytesSent 
        = ::send(_socket,(char *)(data+totalSent), dataLen-totalSent, 0);

    if (bytesSent ==0) {
        return totalSent;
    }
    else if(bytesSent == SOCKET_ERROR){
#ifdef __WIN32
        int errcode = WSAGetLastError();
        if( errcode==WSAEWOULDBLOCK ){
#else
            if ((errno == EWOULDBLOCK) || (errno == EAGAIN)) {
#endif
            }
            else{
                if( !totalSent ) {
                    totalSent = SOCKET_ERROR;
                }
                break;
            }
        }
        else{
            totalSent+=bytesSent;
        }
    }
}

Заранее спасибо.

Ответы [ 4 ]

12 голосов
/ 08 августа 2010

Не видя вашего кода, мне придется угадать.

Причина, по которой вы получаете нулевое окно в TCP, заключается в том, что в буфере recv получателя нет места.

Есть несколько способов, которыми это может произойти.Одна из распространенных причин этой проблемы - отправка по локальной сети или другому относительно быстрому сетевому соединению, когда один компьютер работает значительно быстрее, чем другой.В качестве крайнего примера, скажем, у вас есть компьютер с частотой 3 ГГц, который отправляет как можно быстрее по Gigabit Ethernet на другую машину с процессором 1 ГГц.Так как отправитель может отправить намного быстрее, чем получатель может прочитать, то буфер recv получателя заполнится, и стек TCP объявит отправителю нулевое окно.

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

Редактировать

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

Вообще, я бы сказал, что это не очень хорошая идея.В идеале, если вас беспокоит зависание вашего приложения на send(2), вы должны установить длительный тайм-аут для сокета, используя setsockopt и использовать отдельный поток для фактической отправки..

См. socket (7) :

SO_RCVTIMEO и SO_SNDTIMEO Укажите таймауты получения или отправки до сообщения об ошибке.Параметр представляет собой структуру timeval.Если функциональные блоки ввода или вывода на этот период времени и данные были отправлены или получены, возвращаемое значение этой функции будет количеством переданных данных;если данные не были переданы и тайм-аут был достигнут, то возвращается -1 с errno, установленным в EAGAIN или EWOULDBLOCK, как если бы сокет был указан как неблокирующий.Если тайм-аут установлен на ноль (по умолчанию), то операция никогда не будет тайм-аут.

Ваш основной поток может вставить каждый дескриптор файла в queue, используя, скажем, повышениемьютекс для доступа к очереди, затем запустите 1 - N потоков, чтобы выполнить фактическую отправку, используя блокировку ввода-вывода с таймаутами отправки.

Ваша функция отправки должна выглядеть примерно так (при условии, что вы устанавливаете тайм-аут):

// blocking send, timeout is handled by caller reading errno on short send
int doSend(int s, const void *buf, size_t dataLen) {    
    int totalSent=0;

    while(totalSent != dataLen)
    {
        int bytesSent 
            = send(s,((char *)data)+totalSent, dataLen-totalSent, MSG_NOSIGNAL);

        if( bytesSent < 0 && errno != EINTR )
            break;

        totalSent += bytesSent;
    }
    return totalSent;
}

Флаг MSG_NOSIGNAL гарантирует, что ваше приложение не будет уничтожено путем записи в сокет, который был закрыт или сброшен узлом.Иногда операции ввода / вывода прерываются сигналами, и проверка на EINTR позволяет перезапустить send.

Как правило, вы должны вызывать doSend в цикле с порциями данных, которые имеют TCP_MAXSEG size.

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

1 голос
/ 08 августа 2010

Распространенная ошибка при разработке с использованием сокетов TCP связана с неверным предположением о поведении read () / write ().

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

0 голосов
/ 19 сентября 2010

Я пытался отключить алгоритм Нейгла (с TCP_NODELAY), и каким-то образом это помогло.Скорость передачи намного выше, размер окна TCP не заполнен или не сброшен.Странно то, что когда я изменял размер окна, это не оказывало никакого влияния.

Спасибо.

0 голосов
/ 08 августа 2010

Наиболее вероятная проблема заключается в том, что в вашем коде есть ошибка, из-за которой вы неправильно обрабатываете частичное чтение или частичную запись. Известно, что TCP между Linux и Windows работает.

...