Содержание

понедельник, 18 апреля 2011 г.

Потеря сообщений при работе с Socket API и как с этим бороться

Как такое возможно, спросите вы, ведь, TCP/IP - надежный протокол гарантирующий доставку? Не все так просто, TCP/IP гарантирует доставку между буферами, таким образом при установленном соединении вы могли на клиенте что-нибудь записать в буфер и передать управление дальше, а сервер может ничего не зачитывать и просто закрыть соединение, уже после того как вы что-то записали в буфер. Давайте посмотрим как это может выглядеть в java коде и что же нам с этим делать.

Сервер принимает сообщение, ничего из него не зачитывает, ждет некоторое время и закрывает соединение:
Клиент, открывает соединение, пишет туда 256 байт, успешно закрывает соединение.
Таким образом на клиенте мы вызываем синхронный метод записи в сокет, успешно закрываем соединение и идем работать дальше, будучи уверенными, что наше сообщение доставлено. Сервер, кстати, в примере выше, тоже не получит никакой ошибки, так и не считав отправленное ему сообщение. Т.е. как мы видим никакой гарантии, что сервер зачитал наше сообщение, а тем более корректно его обработал нет.

Как же писать приложение правильно?

Чтобы таких проблем не возникало, необходимо работать по схеме request-response. Т.е. после оправки сообщения клиентом, надо дождаться некоторого ответа от сервера, который должен быть послан программно.

Если же вас интересует максимальная пропускная способность (throughput), то в этом случае тоже можно что-нибудь сделать, только уже придется пописать кода немного побольше. Например, можно все сообщения нумеровать, а при восстановлении соединения после разрыва, запрашивать у сервера последний обработанный sequence и выслать все не обработанные сообщения.

Если же у вас нет острой необходимости программировать на сокетах, то я бы все-таки рекомендовал взять какой-нибудь JMS и забыть о всех этих проблемах. Но опять же будьте аккуратнее, если вы возьмете, например, netty, то это не решит все ваши проблемы, и вам все равно придется думать, как же быть с возможной потерей сообщений.

Добавим firewall

При работе с firewall, все может быть еще печальнее. После того, как ваше соединение простояло без дела определенное время, оно может быть закрыто firewall. Причем оно может быть закрыто настолько нежно, что ни сервер, ни клиент не узнает о случившемся. Клиент будет как ни в чем не бывало писать в свой буфер, а сервер будет до бесконечности висеть в методе read. Хуже того различные методы проверки состояния соединения вроде Socket.isConnected() даже не намекнут на то, что соединение обрезано. Говорят, что иногда firewall даже может делать black hole, когда клиент пишет и пишет в сокет (даже больше размера буфера), а сервер ничего из этого не получает.

Чтобы решить эту проблему, на стороне сервера можно выставить read-timeout (Socket.setSoTimeout(int)), а на клиенте перед отправкой значимого сообщения, можно посылать короткий пинг и дожидаться его возвращения, чтобы удостовериться в том, что соединение все еще живо. И если что-то где-то обломалось, то открывать новое соединение.

Классически, же эта проблема решается путем посылки периодического сообщения (heartbeat). Тут сразу можно убить двух зайцев: проводить мониторинг, что соединение установлено и работает, а так же постоянно его прогревать, не давая firewall его закрыть.