Системное программирование в UNIX средствами Free Pascal

       

Закрытие 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.


Содержание раздела