4. Когда узел сервера перезагружается после сбоя, его TCP теряет информацию о существовавших до сбоя соединениях. Следовательно, TCP сервера отвечает на полученный от клиента сегмент данных, посылая RST.
5. Наш клиент блокирован в вызове функции
readline
, когда приходит сегмент RST, заставляющий функцию
readline
возвратить ошибку
ECONNRESET
.
Если для нашего клиента важно диагностировать выход из строя узла сервера, даже если клиент активно не посылает данные, то требуется другая технология (с использованием параметра сокета
SO_KEEPALIVE
или некоторых функций, проверяющих наличие связи в клиент-серверном соединении).
5.16. Выключение узла сервера
В двух предыдущих разделах рассматривался выход из строя узла сервера или недоступность узла сервера в сети. Теперь мы рассмотрим, что происходит, если узел сервера выключается оператором в то время, когда на этом узле выполняется наш серверный процесс.
Когда система Unix выключается, процесс
init
обычно посылает всем процессам сигнал
SIGTERM
(мы можем перехватить этот сигнал), ждет в течение некоторого фиксированного времени (часто от 5 до 20 с), а затем посылает сигнал
SIGKILL
(который мы перехватить не можем) всем еще выполняемым процессам. Это дает всем выполняемым процессам короткое время для завершения работы. Если мы не завершили выполнение процесса, это сделает сигнал
SIGKILL
. При завершении процесса закрываются все открытые дескрипторы, а затем мы проходим ту же последовательность шагов, что описывалась в разделе 5.12. Там же было отмечено, что в нашем клиенте следует использовать функцию
select
или
poll
, чтобы клиент определил завершение процесса сервера, как только оно произойдет.
5.17. Итоговый пример TCP
Прежде чем клиент и сервер TCP смогут взаимодействовать друг с другом, каждый из них должен определить пару сокетов для соединения: локальный IP-адрес, локальный порт, удаленный IP-адрес, удаленный порт. На рис. 5.5 мы схематически изображаем эти значения черными кружками. На этом рисунке ситуация представлена с точки зрения клиента. Удаленный IP-адрес и удаленный порт должны быть заданы клиентом при вызове функции
connect
. Два локальных значения обычно выбираются ядром тоже при вызове функции
connect
. У клиента есть выбор: он может задать только одно из локальных значений или оба, вызвав функцию
bind
перед вызовом функции
connect
, однако второй подход используется редко.
Рис. 5.5. TCP-соединение клиент-сервер с точки зрения клиента
Как мы отмечали в разделе 4.10, клиент может получить два локальных значения, выбранных ядром, вызвав функцию
getsockname
после установления соединения.
На рис. 5.6 показаны те же четыре значения, но с точки зрения сервера.
Рис. 5.6. TCP-соединение клиент-сервер с точки зрения сервера
Локальный порт (заранее известный порт сервера) задается функцией
bind
. Обычно сервер также задает в этом вызове универсальный IP-адрес, хотя может и ограничиться получением соединений, предназначенных для одного определенного локального интерфейса путем связывания с IP-адресом, записанным без символов подстановки (то есть не универсального). Если сервер связывается с универсальным IP-адресом на узле с несколькими сетевыми интерфейсами, он может определить локальный IP-адрес (указываемый как адрес отправителя в исходящих пакетах) при помощи вызова функции
getsockname
после установления соединения (см. раздел 4.10). Два значения удаленного адреса возвращаются серверу при вызове функции accept. Как мы отмечали в разделе 4.10, если сервером, вызывающим функцию accept, выполняется с помощью функции exec другая программа, то эта программа может вызвать функцию
getpeername
, чтобы при необходимости определить IP-адрес и порт клиента.
5.18. Формат данных
В нашем примере сервер никогда не исследует запрос, который он получает от клиента. Сервер лишь читает все данные, включая символ перевода строки, и отправляет их обратно клиенту, отслеживая только разделитель строк. Это исключение, а не правило, так как обычно необходимо принимать во внимание формат данных, которыми обмениваются клиент и сервер.
Пример: передача текстовых строк между клиентом и сервером
Изменим наш сервер так, чтобы он, по-прежнему принимая текстовую строку от клиента, предполагал, что строка содержит два целых числа, разделенных пробелом, и возвращал сумму этих чисел. Функции
main
наших клиента и сервера остаются прежними, как и функция
str_cli
. Меняется только функция
str_echo
, что мы показываем в листинге 5.11.
Листинг 5.11. Функция str_echo, суммирующая два числа
//tcpcliserv/str_echo08.c
1 #include "unp.h"
2 void
3 str_echo(int sockfd)
4 {
5 long arg1, arg2;
6 ssize_t n;
7 char line[MAXLINE];