Книга: Операционная система UNIX

Программный интерфейс сокетов

Программный интерфейс сокетов

Вы уже познакомились с интерфейсом сокетов при обсуждении реализации межпроцессного взаимодействия в BSD UNIX. Поскольку сетевая поддержка впервые была разработана именно для BSD UNIX, интерфейс сокетов и сегодня является весьма распространенным при создании сетевых приложений. В разделе "Поддержка сети в BSD UNIX" мы вновь вернемся к сокетам, когда будем рассматривать внутреннюю архитектуру сетевой подсистемы в UNIX ветви BSD. Сейчас же рассмотрим простой пример приложения клиент-сервер, который демонстрирует возможности сокетов при обеспечении взаимодействия между удаленными процессами. Несмотря на то что взаимодействие затрагивает передачу данных по сети, приведенная программа мало отличается от примера, рассмотренного в разделе "Межпроцессное взаимодействие в BSD UNIX. Сокеты" главы 3. Логика приложения сохранена — клиент отправляет серверу сообщение, сервер передает его обратно, а клиент, в свою очередь, выводит полученное сообщение на экран. Наиболее существенным отличием является коммуникационный домен сокетов — в данном случае AF_INET. Соответственно изменилась и схема адресации коммуникационного узла. Согласно схеме адресации TCP/IP, коммуникационный узел однозначно идентифицируется двумя значениями: адресом хоста (IP-адрес) и адресом процесса (адрес порта). Это отражает и структура sockaddr_in, которая является конкретным видом общей структуры адреса сокета sockaddr. Структура sockaddr_in имеет следующий вид:

struct sockaddr_in {
 short sin_family;      
 Коммуникационный домен — AF_INET
 u_short sin_port;      
 Номер порта

 struct in_addr sin_addr; IP-адрес хоста

 char sin_zero[8];
};

Адрес порта должен быть предварительно оговорен между клиентом и сервером.

В заключение, прежде чем перейти непосредственно к текстам программы, заметим, что интерфейс сокетов также поддерживается и в UNIX System V, наряду с другим программным интерфейсом — TLI, который будет рассмотрен в следующем разделе.

Приведенный пример в качестве транспортного протокола использует TCP. Это значит, что перед передачей прикладных данных клиент должен установить соединение с сервером. Эта схема, приведенная на рис. 6.17, несколько отличается от рассмотренной в разделе "Межпроцессное взаимодействие в BSD UNIX. Сокеты", где передача данных осуществлялась без предварительного установления связи и в данном случае соответствовала бы использованию протокола UDP.


Рис. 6.17. Схема установления связи и передачи данных между клиентом и сервером

В соответствии с этой схемой сервер производит связывание с портом, номер которого предполагается известным для клиентов bind(2), и сообщает о готовности приема запросов listen(2)). При получении запроса он с помощью функции accept(2) создает новый сокет, который и обслуживает обмен данными между клиентом и сервером. Для того чтобы сервер мог продолжать обрабатывать поступающие запросы, он порождает отдельный процесс на каждый поступивший запрос. Дочерний процесс, в свою очередь, принимает сообщения от клиента (recv(2)) и передает их обратно (send(2)).

Клиент не выполняет связывания, поскольку ему безразлично, какой адрес будет иметь его коммуникационный узел. Эту операцию выполняет система, выбирая свободный адрес порта и установленный адрес хоста. Далее клиент направляет запрос на установление соединения (connect(2)), указывая адрес сервера (IP-адрес и номер порта). После установления соединения ("тройное рукопожатие") клиент передает сообщение (send(2)), принимает от сервера ответ recv(2)) и выводит его на экран.

В программе используются несколько функций, которые не рассматривались. Эти функции значительно облегчают жизнь программисту, выполняя, например, такие действия, как трансляцию доменного имени хоста в его IP-адрес (gethostbyname(3N)), приведение в соответствие порядка следования байтов в структурах данных, который может различаться для хоста и сети (htons(3N)), а также преобразование IP-адресов и их составных частей в соответствии с привычной "человеческой" нотацией, например 127.0.0.1 (inet_ntoa(3N)). Мы не будем подробнее останавливаться на этих функциях, предоставляя читателю самостоятельно обратиться к соответствующим разделам электронного справочника man(1).

Ниже приведены тексты программ сервера и клиента.

Сервер

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <fcntl.h>
#include <netdb.h>
/* Номер порта сервера, известный клиентам */
#define PORTNUM 1500
main(argc, argv)
int argc;
char *argv[];
{
 int s, ns;
 int pid;
 int nport;
 struct sockaddr_in serv_addr, clnt_addr;
 struct hostent* hp;
 char buf[80], hname[80];
 /* Преобразуем порядок следования байтов
    к сетевому формату */
 nport = PORTNUM;
 nport = htons((u_short)nport);
 /* Создадим сокет, использующий протокол TCP */
 if ((s=socket(AF_INET, SOCK_STREAM, 0))==-1) {
  perror("Ошибка вызова socket()");
  exit(1);
 }
 /* Зададим адрес коммуникационного узла */
 bzero(&serv_addr, sizeof(serv_addr));
 serv_addr.sin_family = AF_INET;
 serv_addr.sin_addr.s_addr = INADDR_ANY;
 serv.addr.sin_port = nport;
 /* Свяжем сокет с этим адресом */
 if (bind(s, struct sockaddr*)&serv_addr,
  sizeof(serv_addr))==-1) {
  perror("Ошибка вызова bind()");
  exit(1);
 }
 /* Выведем сообщение с указанием адреса сервера */
 fprintf(stderr, "Сервер готов: %sn",
  inet_ntoa(serv_addr.sin_addr));
 /* Сервер готов принимать запросы
    на установление соединения.
    Максимальное число запросов, ожидающих обработки – 5.
    Как правило, этого числа достаточно, чтобы успеть
    выполнить accept(2) и породить дочерний процесс */
 if (listen(s, 5)==-1) {
  perror("Ошибка вызова listen()");
  exit(1);
 }
 /* Бесконечный цикл получения запросов и их обработки */
 while (1) {
  int addrlen;
  bzero(&clnt_addr, sizeof(clnt_addr));
  addrlen = sizeof(clnt_addr);
  /* Примем запрос. Новый сокет ns становится
     коммуникационным узлом созданного виртуального канала */
  if ((ns=accept(s, (struct sockaddr*)&clnt_addr,
   &addrlen))==-1) {
   perror("Ошибка вызова accept()");
   exit(1);
  }
  /* Выведем информацию о клиенте */
  fprintf(stderr, "Клиент = %sn",
   inet_ntoa(clnt_addr.sin_addr));
  /* Создадим процесс для работы с клиентом */
  if ((pid=fork())==-1) {
   perror("Ошибка вызова fork()");
   exit(1);
  }
  if (pid==0) {
   int nbytes;
   int fout;
   /* Дочерний процесс: этот сокет нам не нужен. Он
      по-прежнему используется для получения запросов */
   close(s);
   /* Получим сообщение от клиента и передадим его обратно */
   while ((nbytes = recv(ns, buf, sizeof(buf), 0)) !=0) {
    send(ns, buf, sizeof(buf), 0);
   }
   close(ns);
   exit(0);
  }
  /* Родительский процесс: этот сокет нам не нужен. Он
     используется дочерним процессом для обмена данными */
  close(ns);
 }
}

Клиент

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <fcntl.h>
#include <netdb.h>
/* Номер порта, который обслуживается сервером */
#define PORTNUM 1500
main (argc, argv)
char *argv[];
int argc;
{
 int s;
 int pid;
 int i, j;
 struct sockaddr_in serv_addr;
 struct hostent *hp;
 char buf[80]="Hello, World!";
 /* В качестве аргумента клиенту передается доменное имя
    хоста, на котором запущен сервер. Произведем трансляцию
    доменного имени в адрес */
 if ((hp = gethostbyname(argv[1])) == 0) {
  perror("Ошибка вызова gethostbyname()");
  exit(3);
 }
 bzero(&serv_addr, sizeof(serv_addr));
 bcopy(hp->h_addr, &serv_addr.sin_addr, hp->h_length);
 serv_addr.sin_family = hp->h_addrtype;
 serv_addr.sin_port = htons(PORTNUM);
 /* Создадим сокет */
 if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
  perror("Ошибка вызова socket!)");
  exit(1);
 }
 fprintf(stderr, "Адрес клиента: %sn",
  inet_ntoa(serv_addr.sin_addr));
 /* Создадим виртуальный канал */
 if (connect (s, (struct sockaddr*)&serv_addr,
  sizeof(serv_addr)) == -1) {
  perror("Ошибка вызова connect()");
  exit(1);
 }
 /* Отправим серверу сообщение и получим его обратно */
 send(s, buf, sizeof(buf), 0);
 if (recv(s, buf, sizeof(buf) , 0) < 0) {
  perror("Ошибка вызова recv()");
  exit(1);
 }
 /* Выведем полученное сообщение на экран */
 printf("Получено от сервера: %sn", buf);
 close(s);
 printf("Клиент завершил работу nn");
}

Оглавление книги


Генерация: 0.033. Запросов К БД/Cache: 0 / 0
поделиться
Вверх Вниз