Я отвечаю на это сам после выполнения тяжелой работы, чтобы найти ответ.
Сокет, прослушивающий события epoll, обычно получает флаг события EPOLLRDHUP (в дополнение к EPOLLIN) при удаленном одноранговом соединении, вызывающем закрытие или отключение (SHUT_WR). Это не обязательно означает, что сокет мертв. Последующие вызовы recv () вернут все непрочитанные данные на сокете, и в конечном итоге будет возвращено «0» для обозначения EOF. Возможно даже отправить данные обратно, если удаленный узел только наполовину закроет свой сокет.
Единственное заметное исключение - если удаленный узел использует опцию SO_LINGER, включенную в его сокете с задержанным значением "0". Результат закрытия такого сокета может привести к отправке TCP RST вместо FIN. Из того, что я прочитал, событие сброса соединения будет генерировать либо EPOLLHUP, либо EPOLLERR. (У меня не было времени для подтверждения, но это имеет смысл).
Существует некоторая документация, позволяющая предположить, что есть более старые реализации Linux, которые не поддерживают EPOLLRDHUP, так как вместо этого создается EPOLLHUP.
И для чего это стоит, в моем конкретном случае я обнаружил, что не слишком интересно иметь код, который является особым случаем событий EPOLLHUP или EPOLLRDHUP. Вместо этого просто обработайте эти события так же, как EPOLLIN / EPOLLOUT, и вызовите recv () (или send () в зависимости от ситуации). Но обратите пристальное внимание на коды возврата, возвращаемые из recv () и send ().