Скачиваний:
56
Добавлен:
08.01.2014
Размер:
2.6 Mб
Скачать

10.5.6. Закрытие tcp-соединения

При работе с сокетами важно корректно реагировать на завершение работы абонентского процесса. Так как сокет является двусторонним механизмом связи, то нельзя предсказать заранее, когда произойдет разрыв соединения – во время чтения или записи. Поэтому нужно учитывать оба возможных варианта.

Если процесс пытается записать данные в оборванный сокет при помощи вызова fdwrite или send, то он получит сигнал SIGPIPE, который может быть перехвачен соответствующим обработчиком сигнала. При чтении обрыв диагностируется проще.

В случае разорванной связи вызов fdread или recv возвращает нулевое значение. Поэтому для вызовов fdread и recv необходимо всегда проверять возвращаемое значение, чтобы не зациклиться при приеме данных.

Закрываются сокеты так же, как и обычные дескрипторы файлового ввода/вывода, – при помощи системного вызова fdclose. Для сокета типа SOCK_STREAM ядро гарантирует, что все записанные в сокет данные будут переданы принимающему процессу. Это может вызвать блокирование операции закрытия сокета до тех пор, пока данные не будут доставлены. (Если сокет имеет тип SOCK_DGRAM, то сокет закрывается немедленно.)

Теперь можно привести полный текст примера клиента и сервера, добавив в серверный процесс обработку сигналов и вызов fdclose в обе программы. В данном случае эти меры могут показаться излишними, но в реальном клиент/серверном приложении обязательна надежная обработка всех исключительных ситуаций. Приведем окончательный текст программы сервера:

(* Серверный процесс *)

uses sockets,stdio,linux;

const

SIZE=sizeof(tinetsockaddr);

server:tinetsockaddr = (family:AF_INET; port:7000; addr:INADDR_ANY);

var

newsockfd:longint;

procedure catcher (sig:integer);cdecl;

begin

fdclose (newsockfd);

halt (0);

end;

var

sockfd:longint;

c:char;

act:sigactionrec;

mask:sigset_t;

client:tinetsockaddr;

clientaddrlen:longint;

begin

act.handler.sh := @catcher;

sigfillset (@mask);

act.sa_mask:=mask.__val[0];

sigaction (SIGPIPE, @act, nil);

(* Установить абонентскую точку сокета *)

sockfd := socket (AF_INET, SOCK_STREAM, 0);

if sockfd = -1 then

begin

perror ('Ошибка вызова socket');

halt (1);

end;

(* Связать адрес с абонентской точкой *)

if not bind (sockfd, server, SIZE) then

begin

perror ('Ошибка вызова bind');

halt (1);

end;

(* Включить прием соединений *)

if not listen (sockfd, 5) then

begin

perror ('ошибка вызова listen');

halt (1);

end;

while true do

begin

(* Прием запроса на соединение *)

newsockfd := accept (sockfd, client, clientaddrlen);

if newsockfd = -1 then

begin

perror ('Ошибка вызова accept');

continue;

end;

(* Создать дочерний процесс для работы с соединением *)

if fork = 0 then

begin

while recv (newsockfd, c, 1, 0) > 0 do

begin

c := upcase (c);

send (newsockfd, c, 1, 0);

end;

(* После того, как клиент прекратит передачу данных,

* сокет может быть закрыт и дочерний процесс

* завершает работу *)

fdclose (newsockfd);

halt (0);

end;

(* В родительском процессе newsockfd не нужен *)

fdclose (newsockfd);

end;

end.

И клиента:

(* Клиентский процесс *)

uses sockets,stdio,linux;

const

SIZE=sizeof(tinetsockaddr);

server:tinetsockaddr=(family:AF_INET; port:7000);

var

sockfd:longint;

c,rc:char;

begin

(* Преобразовать и сохранить IP address сервера *)

server.addr := inet_addr ('127.0.0.1');

(* Установить абонентскую точку сокета *)

sockfd := socket (AF_INET, SOCK_STREAM, 0);

if sockfd = -1 then

begin

perror ('Ошибка вызова socket');

halt (1);

end;

(* Подключить сокет к адресу сервера *)

if not connect (sockfd, server, SIZE) then

begin

perror ('Ошибка вызова connect');

halt (1);

end;

(* Обмен данными с сервером *)

rc := #$a;

while true do

begin

if rc = #$a then

writeln ('Введите строчный символ');

c:=char(getchar);

send (sockfd, c, 1, 0);

if recv (sockfd, rc, 1, 0) > 0 then

write (rc)

else

begin

writeln ('Сервер не отвечает');

fdclose (sockfd);

halt (1);

end;

end;

end.

Упражнение 10.1. Запустите приведенную программу сервера и несколько клиентских процессов. Что произойдет после того, как все клиентские процессы завершат работу?

Упражнение 10.2. Измените код программ так, чтобы после того, как все клиентские процессы завершат свою работу, сервер также завершал работу после заданного промежутка времени, если не поступят новые запросы на соединение.

Упражнение 10.3. Измените код программ так, чтобы два взаимодействующих процесса выполнялись на одном и том же компьютере. В этом случае сокет должен иметь коммуникационный домен AF_UNIX.

Соседние файлы в папке Полищук, Семериков. Системное программирование в UNIX средствами Free Pascal