Наблюдаемое вами поведение - это стандартное поведение сокета.
Если данные, полученные от однорангового узла, не были прочитаны приложением, и приложение вызывает close
в сокете, то ОС не выполняет обычное завершение соединения TCP. Это сбрасывает соединение сразу вместо завершения. Если приложение действительно хочет изящно закрыть соединение, когда в полученном буфере есть непрочитанные данные, оно должно вызвать shutdown(socket, SHUT_WR)
перед вызовом close
.
А почему API сокетов реализован таким образом? Потому что эта обработка может избежать DOS-атак. Если close
выполняет обычное завершение сеанса TCP, то возможна следующая атака:
- Злонамеренный клиент открывает TCP-соединение
- Сервер принимает соединение и начинает получать данные
- Клиент отправляет непрерывный поток, скажем, случайных данных
- Сервер быстро обнаруживает, что полученные данные неверны, и вызывает
close
в сокете.
close
просто отправляет FIN
и затем ждет закрытия пира. Ресурсы сервера остаются выделенными, потому что обычно FIN
просто закрывают направление к клиенту. Клиент по-прежнему может отправлять данные, и буфер recv не будет освобожден.
Но когда закрытие инициирует сброс соединения, ресурсы, связанные с этим соединением TCP, немедленно освобождаются. Атака дос тогда немного сложнее.
Подробнее см. https://www.spinics.net/lists/linux-c-programming/msg01345.html.