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

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

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

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

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

может кто-нибудь, пожалуйста, помогите мне с этой проблемой?

EDIT: добавление кода отправки-похоже, что он обрабатывает правильно частичные записи:

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 ответов


не видя ваш код, я думаю.

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

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

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

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

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

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

посмотреть гнездо(7):

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

ваш основной поток может толкать каждый файловый дескриптор в queue используя, скажем, мьютекс boost для доступа к очереди, затем запустите 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 размер.

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


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

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


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


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

спасибо.