Онлайн
библиотека книг
Книги онлайн » Разная литература » Компьютерные сети. 6-е изд. - Эндрю Таненбаум

Шрифт:

-
+

Закладка:

Сделать
1 ... 185 186 187 188 189 190 191 192 193 ... 335
Перейти на страницу:
транспортным узлом.) Иногда сокеты используются с транспортными протоколами, в которых вместо байтового потока применяется поток сообщений и которые могут включать (или не включать) контроль перегрузок. К примеру, дейтаграммный протокол с контролем перегрузок (Datagram Congestion Control Protocol, DCCP) является вариантом UDP с управлением перегрузкой (Колер и др.; Kohler et al., 2006). Необходимую службу выбирают сами пользователи.

Тем не менее последнее слово в вопросе транспортных интерфейсов, скорее всего, останется не за сокетами. Довольно часто приложениям приходится работать с группой связанных потоков, например браузер может одновременно запрашивать у сервера несколько объектов. В таком случае применение сокетов обычно означает, что для каждого объекта будет использоваться один поток. В результате управление перегрузкой будет выполняться отдельно для каждого потока (а не для всей группы). Безусловно, это далеко не оптимальный вариант, поскольку управление набором потоков становится задачей приложения. Чтобы более эффективно обрабатывать группы связанных потоков и уменьшить роль приложения в этом процессе, был создан ряд дополнительных протоколов и интерфейсов. В частности, протокол передачи с управлением потоками (Stream Control Transmission Protocol, SCTP), описанный в RFC 4960 (Форд; Ford, 2007), и протокол QUIC (он будет рассмотрен ниже). Эти протоколы слегка изменяют сокет-API для удобства работы с группами потоков, обеспечивая новые возможности, например работу со смешанным трафиком (с установлением соединения и без) и даже поддержку множественных сетевых путей.

6.1.4. Пример программирования сокета: файл-сервер для интернета

Чтобы узнать, как выполняются вызовы для сокета на практике, рассмотрим клиентский и серверный код на илл. 6.6. Имеется примитивный файл-сервер, работающий в интернете, и использующий его клиент. У программы много ограничений (о которых еще будет сказано), но теоретически данный код, описывающий сервер, может быть скомпилирован и запущен на любой UNIX-системе, подключенной к интернету. Код, описывающий клиента, может быть запущен с определенными параметрами. Это позволит ему получить любой файл, к которому у сервера есть доступ. Файл отображается на стандартном устройстве вывода, но, разумеется, может быть перенаправлен на диск или какому-либо процессу.

/* На этой странице содержится клиентская программа, запрашивающая файл у серверной программы, расположенной на следующей странице. Сервер в ответ на запрос высылает файл. */

#include <sys/types.h>

#include <unistd.h>

#include <string.h>

#include <stdio.h>

#include <stdlib.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <netdb.h>

#define SERVER_PORT 8080            /* По договоренности между клиентом и сервером */

#define BUF_SIZE 4096               /* Размер передаваемых блоков */

int main(int argc, char **argv)

{

int c, s, bytes;

char buf[BUF_SIZE];                /* буфер для входящего файла */

struct hostent *h;                 /* информация о сервере */

struct sockaddr_in channel;        /* содержит IP-адрес */

if (argc != 3) {printf("Для запуска введите: client имя_сервера имя_файла");                          exit(-1);}

h = gethostbyname(argv[1]);        /* поиск IP-адреса хоста */

if (!h) {printf("gethostbyname не удалось найти %s", argv[1]); exit(-1;}

s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

if (s <0) {printf("сбой вызова сокета"); exit(-1);}

memset(&channel, 0, sizeof(channel));

channel.sin_family= AF_INET;

memcpy(&channel.sin_addr.s_addr, h->h_addr, h->h_length);

channel.sin_port= htons(SERVER_PORT);

c = connect(s, (struct sockaddr *) &channel, sizeof(channel));

if (c < 0) {printf("сбой соединения"); exit(-1);}

/* Соединение установлено. Отправляется имя файла с нулевым байтом на конце */

write(s, argv[2], strlen(argv[2])+1);

/* Получить файл, записать на стандартное устройство вывода */

while (1) {

    bytes = read(s, buf, BUF_SIZE); /* Читать из сокета */

    if (bytes <= 0) exit(0);        /* Проверка конца файла */

    write(1, buf, bytes);           /* Записать на стандартное устройство вывода */

}

}

Илл. 6.6. Клиентская программа для использования сокетов. Серверная программа представлена на следующей странице

#include <sys/types.h>                   /* Серверная программа */

#include <string.h>

#include <stdio.h>

#include <stdlib.h>

#include <sys/fcntl.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <netdb.h>

#define SERVER_PORT 8080                 /* По договоренности между клиентом и сервером */

#define BUF_SIZE 4096                    /* Размер передаваемых блоков */

#define QUEUE_SIZE 10

int main(int argc, char *argv[])

{ int s, b, l, fd, sa, bytes, on = 1;

char buf[BUF_SIZE];                     /* буфер для исходящего файла */

struct sockaddr_in channel;             /* содержит IP-адрес */

/* Создать структуру адреса для привязки к сокету */

memset(&channel, 0, sizeof(channel));   /* Обнуление channel */

channel.sin_family = AF_INET;

channel.sin_addr.s_addr = htonl(INADDR_ANY);

channel.sin_port = htons(SERVER_PORT);

/* Пассивный режим. Ожидание соединения */

s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);  /* Создать сокет */

if (s <0) {printf("сбой вызова сокета"); exit(-1);}

setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on));

b = bind(s, (struct sockaddr *) &channel, sizeof(channel));

if (b < 0) {printf("сбой связывания"); exit(-1);}

l = listen(s, QUEUE_SIZE);              /* Определение размера очереди */

if (l < 0) {printf("сбой ожидания"); exit(-1);}

/* Теперь сокет установлен и привязан. Ожидание и обработка соединения. */

while (1) {

    sa = accept(s, 0, 0);                /* Блокировка в ожидании запроса соединения */

    if (sa < 0) {printf("сбой доступа"); exit(-1);}

    read(sa, buf, BUF_SIZE);             /* Считать имя файла из сокета */

    /* Получить и возвратить файл. */

    fd = open(buf, O_RDONLY);            /* Открыть файл для обратной отправки */

    if (fd < 0) {printf("сбой открытия файла");

    while (1) {

        bytes = read(fd, buf, BUF_SIZE); /* Читать из файла */

        if (bytes <= 0) break;           /* Проверка конца файла */

        write(sa, buf, bytes);           /* Записать байты в сокет */

    }

    close(fd);                           /* Закрыть файл */

    close(sa);                           /* Разорвать соединение */

}

}

Сначала рассмотрим ту часть программы, которая описывает сервер. Она начинается с включения некоторых стандартных заголовков, последние три из которых содержат основные структуры данных и определения, относящиеся к интернету. Затем SERVER_PORT определяется как 8080. Значение выбрано случайным образом. Любое число от 1024 до 65 535 также подойдет, если только оно не используется другим процессом; порты с номерами 1023 и ниже зарезервированы для привилегированных пользователей.

В последующих двух строках определяются две необходимые серверу константы. Первая из них задает размер блока данных для передачи файлов (в байтах). Вторая определяет максимальное количество незавершенных соединений, после установки которых новые соединения будут отвергаться.

После объявления локальных переменных начинается сама программа сервера. Вначале она инициирует структуру данных, которая будет содержать IP-адрес сервера. Эта структура вскоре будет привязана к серверному сокету. Вызов memset полностью обнуляет структуру данных. Последующие три присваивания заполняют три поля этой структуры. Последнее содержит порт сервера. Функции htonl и htons преобразуют значения в стандартный формат, что позволяет программе нормально выполняться на устройствах с представлением числовых разрядов little-endian (например, Intel x86) и big-endian (например, SPARC).

После этого сервер создает и проверяет

1 ... 185 186 187 188 189 190 191 192 193 ... 335
Перейти на страницу:

Еще книги автора «Эндрю Таненбаум»: