Посмотрел тут недавно лайв кодинг одного прикольного чувака из Сибири, который стримит на Твиче и позже выкладывает на Ютубе.
Ниже ссылки на этот стрим на обоих ресурсах:
Этот стрим посвящён проблеме закрытия сокета до того, как все посланные данные на самом деле ушли к получателю. Суть проблемы в том, что функция write
(как и неупомянутая в стриме send
) пишет данные в ядро, а дошли ли эти данные до получателя уже полностью она не знает, хотя сокеты в Linux блокирующие по-умолчанию. Если сразу после write()
или send()
вызвать close(sock)
, у принимающей стороны случится ECONNRESET
(Connection reset by peer). Случиться это может в случае, если и принимающая сторона решила что-то нам послать в самом начале, а мы это не прочитали. Это важное условие, потому что в противном случае всё работает верно и без ухищрений.
Первую половину ролика сибиряк разбирает одну старую статью на эту тему, пробует повторить и исправить по статье данную проблему в своём тестовом коде. Вторая половина ролика посвящена исправлению кода в его старых проектах и в контексте этой темы она не так интересна.
В статье предлагаются следующие шаги для решения данной проблемы:
Во-первых предлагается вызывать shutdown(sock, SHUT_WR)
на отправляющей стороне сразу после последнего write
. Но этого недостаточно, поэтому так же предлагается выставить опцию сокета SO_LINGER
которая уже сама по себе должна решить проблему, но почему-то её не решает и это выглядит так, как будто она просто не работает. Есть и Linux-only вещи, такие как SIOCOUTQ ioctl()
. Но универсальным решением предлагается просто бесконечный цикл, в котором вычитываются все данные и лишь затем можно закрывать сокет.
Я проверил на своём компьютере большинство из выше перечисленного и этот бесконечный цикл действительно работает, а SO_LINGER
с и без shutdown(sock, SHUT_WR)
и даже с shutdown(sock, SHUT_RDWR)
действительно не работает. Но ведь решение с бесконечным чтением в цикле - это дыра уровня DDoS, ведь если ко мне будет идти бесконечный поток данных, сокет я никогда не закрою.
Ещё одно наблюдение. Если перед закрытием сокета вызвать shutdown(sock, SHUT_WR)
и короткий sleep
, это так же решает проблему. Неужели в UNIX/Linux/POSIX нет нормального решения данной проблемы?