Мы перемещаем большие объемы данных по локальной сети, и это должно происходить очень быстро и надежно. В настоящее время мы используем Windows TCP, как это реализовано в C ++. Использование больших (синхронных) отправлений перемещает данные намного быстрее, чем набор меньших (синхронных) отправок, но часто приводит к взаимоблокировке в течение больших промежутков времени (0,15 секунды), что приводит к резкому снижению общей скорости передачи. Этот тупик случается в очень специфических обстоятельствах, что заставляет меня верить, что его вообще можно предотвратить. Что еще более важно, если мы на самом деле не знаем причину, то на самом деле мы не знаем, что это случится через какое-то время с меньшими посылками. Кто-нибудь может объяснить этот тупик?
Описание тупика (ОК, заблокирован зомби, он не мертв, но примерно на .15 секунд останавливается, затем снова запускается)
- Принимающая сторона отправляет ACK.
- Отправляющая сторона отправляет пакет, содержащий конец сообщения (установлен push-флаг)
- Для вызова socket.recv требуется около 0,15 секунды (!) Для возврата
- О времени возврата вызова ACK отправляется принимающей стороной
- Наконец отправляется следующий пакет от отправителя (почему он ожидает? Окно tcp достаточно большое)
Странная вещь в (3) состоит в том, что обычно этот вызов вообще не занимает много времени и получает точно такой же объем данных. На машине с частотой 2 ГГц это 300 миллионов инструкций времени. Я предполагаю, что вызов не (не дай бог) дождаться подтверждения полученных данных, прежде чем они вернутся, поэтому подтверждение должно ждать ответа на вызов, или оба должны быть задержаны чем-то другим.
Проблема НИКОГДА не возникает, когда существует второй пакет данных (часть того же сообщения), поступающий между 1 и 2. Эта часть очень ясно показывает, что это связано с тем фактом, что Windows TCP не будет отправлять обратно ACK без данных, пока не прибудет второй пакет или не истечет таймер 200 мс. Однако задержка составляет менее 200 мс (это больше похоже на 150 мс).
Третий непристойный персонаж (и, на мой взгляд, настоящий виновник) - (5). Посыл определенно вызывается задолго до того, как истекут 0,15 секунды, но данные НИКОГДА не попадают в линию до того, как этот ответ вернется. Это самая странная часть этого тупика для меня. Это не блокировка tcp, потому что окно TCP очень большое, так как мы установили для SO_RCVBUF что-то вроде 500 * 1460 (что все еще меньше мегабайта). Данные поступают очень быстро (в основном это цикл, выводящий данные через send), поэтому буфер должен заполняться почти сразу. Msdn упоминает, что существуют различные «эвристики», используемые при принятии решения о том, когда отправка попадает в провод, и что уже ожидающая отправка + полный буфер вызовет отправку, чтобы заблокировать, пока данные не попадут в проводник (в противном случае отправка, по-видимому, действительно просто копирует данные в tcp отправить буфер и вернуть).
В любом случае, почему отправитель не отправляет больше данных во время этой 15-секундной паузы, это самая странная часть для меня. Приведенная выше информация была получена на принимающей стороне через wireshark (за исключением, конечно, времени возврата socket.recv, которое было зарегистрировано в текстовом файле). Мы попытались изменить буфер отправки на ноль и отключить nagel на отправителе (да, я знаю, что nagel не отправляет небольшие пакеты - но мы пытались отключить nagel в случае, если это было частью неустановленной «эвристики», влияющей на то, будет ли сообщение быть отправленным на провод. Технически nagel от Microsoft состоит в том, что небольшой пакет не отправляется, если буфер заполнен и имеется неподтвержденный ACK, так что это представляется вероятным).