У меня есть небольшое приложение, которое отправляет файлы по сети агенту, расположенному в ОС 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;
}
}
}
Заранее спасибо.
Распространенная ошибка при разработке с использованием сокетов TCP связана с неверным предположением о поведении read () / write ().
Когда вы выполняете операцию чтения / записи, вы должны проверить возвращаемое значение, они могут не иметь чтения / записи запрошенных байтов, вам обычно нужен цикл, чтобы отслеживать и гарантировать, что все данные были переданы.
Не видя вашего кода, я должен угадать.
Причина, по которой вы получаете нулевое окно в TCP, заключается в том, что в буфере recv получателя нет места.
Это может произойти несколькими способами. Одна из распространенных причин этой проблемы - когда вы отправляете сообщения по локальной сети или другому относительно быстрому сетевому соединению, и один компьютер работает значительно быстрее, чем другой. В качестве крайнего примера предположим, что у вас есть компьютер с частотой 3 ГГц, который как можно быстрее отправляет сообщения через Gigabit Ethernet на другой компьютер, на котором работает процессор с частотой 1 ГГц. Поскольку отправитель может отправлять намного быстрее, чем может прочитать получатель, буфер recv получателя будет заполнен, в результате чего стек TCP объявит отправителю нулевое окно.
Теперь это может вызвать проблемы как на отправляющей, так и на принимающей сторонах, если они обе не готовы с этим справиться. На стороне отправки это может привести к заполнению буфера отправки и вызовам на отправку для блокировки или отказа, если вы используете неблокирующий ввод-вывод. На принимающей стороне вы можете тратить так много времени на ввод-вывод, что у приложения не будет возможности обработать какие-либо данные, и это будет выглядеть заблокированным.
Редактировать
Судя по некоторым из ваших ответов и кода, ваше приложение однопоточное и вы по какой-то причине пытаетесь выполнить неблокирующую отправку.Я предполагаю, что вы устанавливаете сокет в режим неблокирования в какой-то другой части кода.
В общем, я бы сказал, что это плохая идея. В идеале, если вы беспокоитесь о том, что ваше приложение зависает на send (2)
, вам следует установить длительный тайм-аут для сокета с помощью setsockopt
и использовать отдельный поток для фактической отправки.
См. socket (7) :
SO_RCVTIMEO и SO_SNDTIMEO Укажите тайм-ауты приема или отправки до сообщения об ошибке. В параметр - это время структуры. Если функциональные блоки ввода или вывода для этот период времени, и данные были отправлено или получено, возвращаемое значение эта функция будет количеством данные переданы; если не было данных передан и тайм-аут был достигнуто, то -1 возвращается с ошибкой установите EAGAIN или EWOULDBLOCK так же, как если сокет был указан как неблокирующий. Если для тайм-аута установлено значение ноль (по умолчанию), затем операция никогда не будет тайм-аута.
Ваш основной поток может помещать каждый дескриптор файла в очередь
, используя, скажем, мьютекс повышения для доступа к очереди, а затем запускать потоки от 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 между Linux и Windows работает.