Чтение онлайн

ЖАНРЫ

UNIX: взаимодействие процессов

Стивенс Уильям Ричард

Шрифт:

 □ для неидемпотентных процедур использование UDP без кэша опасно.

В следующем разделе будут рассмотрены дополнительные преимущества использования TCP. 

16.7. Досрочное завершение сервера или клиента

Рассмотрим, что произойдет в случае досрочного завершения клиента или сервера при использовании транспортного протокола TCP. Поскольку протокол UDP не подразумевает установку соединения, при завершении процесса его собеседнику не отсылается никаких сообщений. При завершении работы одного из процессов второй дождется тайм-аута, после чего, возможно, повторно отошлет запрос и наконец прекратит попытки, выдав сообщение об ошибке, как показывалось в предыдущем разделе.

При завершении работы процесса, установившего соединение по TCP, это соединение завершается отправкой пакета FIN [24, с. 36-37], и мы хотим узнать, что делает библиотека RPC при получении этого пакета.

Досрочное завершение сервера

Завершим работу сервера досрочно, в процессе обработки запроса клиента. Единственное изменение в программе-клиенте будет заключаться в удалении аргумента tcp из вызова clnt_call в листинге 16.2 и включении протокола в набор аргументов командной строки, как в листинге 16.9. В процедуру сервера мы добавим вызов abort. Это приведет к завершению работы процесса-сервера и отправке пакета FIN клиенту, что мы можем проверить с помощью tcpdump.

Запустим в системе Solaris клиент для сервера, работающего под BSD/OS:

solaris % client bsdi 22 tcp

bsdi: RPC: Unable to receive; An event requires attention

В момент получения клиентом пакета FIN библиотека RPC находилась в состоянии ожидания ответа сервера. Она получила неожиданный ответ и вернула ошибку в вызове squareproc_1. Ошибка (RPC_CANTRECV) сохраняется библиотекой в дескрипторе клиента, и вызов clnt_sperror (из функции-обертки Clnt_create) при этом печатает сообщение Unable to receive. Оставшаяся часть сообщения об ошибке (An event requires attention) соответствует ошибке XTI, сохраненной библиотекой, которая также выводится clnt_sperror. Вызов удаленной процедуры может вернуть одну из примерно 30 различных ошибок RPC_xxx. Все они перечислены в заголовочном файле <rpc/clnt_stat.h>.

Если мы поменяем клиент и сервер местами, мы увидим то же сообщение об ошибке, возвращаемое библиотекой RPC (RPC_CANTRECV), но при этом будет выведено дополнительное сообщение:

bsdi % client solaris 11 tcp

solaris: RPC: Unable to receive; errno = Connection reset by peer

Сервер в Solaris не был скомпилирован как многопоточный, и когда мы вызвали abort, была завершена работа всего процесса. Если мы запустим многопоточный сервер и завершим работу только одного потока — того, который обслуживает данный запрос клиента, — все изменится. Чтобы продемонстрировать это, заменим вызов abort на pthread_exit, как мы сделали в пpoгрaммe из листинга 15.20. Запустим клиент в BSD/OS, а многопоточный сервер — в Solaris:

bsdi % client solaris 33 tcp

solaris: RPC: Timed out
 

После завершения работы потока сервера соединение с клиентом по TCP не разрывается. Оно остается открытым, поэтому клиенту не отсылается пакет FIN. Клиент выходит по тайм-ауту. Мы увидели бы то же сообщение об ошибке, если бы узел, на котором находится сервер, прекратил работу после получения запроса от клиента и отправки уведомления.

Досрочное завершение клиента

Если клиент, использующий TCP, завершает работу в процессе выполнения процедуры RPC, серверу отправляется пакет FIN. Мы хотим узнать, как библиотека сервера реагирует на этот пакет и уведомляет об этом процедуру сервера. (В разделе 15.11 мы говорили, что поток сервера дверей отменяется при досрочном завершении клиента.)

Чтобы сымитировать такую ситуацию, клиент вызывает alarm(3) непосредственно перед вызовом процедуры сервера, а процедура сервера вызывает slеер (6). Так же мы поступили и в нашем примере с дверьми в листингах 15.25 и 15.26. Поскольку клиент не перехватывает

сигнал SIGALRM, процесс завершается ядром примерно за 3 секунды до отправки ответа серверу. Запустим клиент в BSD/OS, а сервер в Solaris:

bsdi % client solaris 44 tcp

Alarm call

Случилось то, что мы и ожидали. А вот на сервере не происходит ничего необычного. Процедура сервера благополучно заканчивает 6-секундную паузу и возвращается. Если мы взглянем на передаваемую по сети информацию с помощью tcpdump, мы увидим следующее:

■ при завершении работы клиента (через 3 секунды после запуска) серверу отправляется пакет FIN, и сервер высылает уведомление о его приеме. В TCP для этого используется термин half-close (наполовину закрытое соединение, раздел 18.5 [22]);

■ через 6 секунд после запуска клиента сервер отсылает ответ, который переправляется клиенту протоколом TCP. По соединению TCP можно отправлять данные после получения FIN, поскольку соединения TCP являются двусторонними, о чем говорится в книге [24, с. 130-132]. Клиент отвечает пакетом RST, поскольку он уже завершил работу. Он будет получен сервером при следующем открытии этого соединения, но это ни к чему не приведет.

Подведем итоги.

■ При использовании UDP клиенты и серверы RPC не имеют возможности узнать о досрочном завершении одного из них. Они могут выходить по тайм-ауту, если ответ не приходит, но тип ошибки при этом определить не удастся: причина может быть в досрочном завершении процесса, сбое узла, недоступности сети и т. д.

■ Клиент RPC, использующий TCP, может узнать о возникших на сервере проблемах, поскольку при досрочном завершении сервера его конец соединения автоматически закрывается. Это, однако, не помогает, если сервер является многопоточным, поскольку такой сервер не закрывает соединение в случае отмены потока с процедурой сервера. Мы также не получаем информации в случае сбоя узла, поскольку при этом соединение TCP не закрывается. Во всех этих случаях следует использовать выход по тайм-ауту.

16.8. XDR: представление внешних данных

В предыдущей главе мы использовали двери для вызова процедуры одного процесса из другого процесса. При этом оба процесса выполнялись на одном узле, поэтому необходимости в преобразовании данных не возникало. Однако RPC используется для вызова процедур на разных узлах, которые могут иметь различный формат хранения данных. Прежде всего могут отличаться размеры фундаментальных типов (в некоторых системах long имеет длину 32 бита, а в других — 64). Кроме того, может отличаться порядок битов (big-endian и little-endian, о чем говорится в книге [24, с. 66-69 и 137-140]. Мы уже столкнулись с этой проблемой, когда обсуждали листинг 16.3. Сервер у нас работал на компьютере с little-endian х86, а клиент — на big-endian Sparc, но мы могли без проблем обмениваться данными (в нашем примере — одно длинное целое).

Sun RPC использует стандарт XDR (External Data Representation — представление внешних данных) для описания и кодирования данных (RFC 1832 [19]). XDR является одновременно языком описания данных и набором правил для их кодирования. В XDR используется скрытая типизация (implicit typing), то есть отправитель и получатель должны заранее знать тип и порядок данных. Например, два 32-разрядных целых, одно число с плавающей точкой и одинарной точностью и строка символов.

ПРИМЕЧАНИЕ

Приведем сравнение из мира OSI. Для описания данных обычно используется нотация ASN.1 (Abstract Syntax Notation one), а для кодирования — BER (Basic Encoding Rules). Эта схема также использует явную типизацию, то есть перед каждым значением указывается его тип. В нашем примере поток байтов содержал бы: спецификатор типа целого, целое, спецификатор типа целого, целое, спецификатор типа single, число с плавающей точкой и одинарной точностью, спецификатор типа строки символов, строку символов.

Поделиться с друзьями: