Сидни Фейт - TCP/IP Архитектура, протоколы, реализация (включая IP версии 6 и IP Security)
3. bind(sockMain, &servAddr, sizeof(servAddr)); getsockname(sockMain, &servAddr, &length);
Вызов bind имеет форму:
возвращаемый_код = bind(дескриптор_socket, адресная_структура, длина_адресной_структуры)
Если адресная структура идентифицирует нужный порт, bind попытается получить его на сервере. Если переменная порта имеет значение 0, bind получит один из неиспользованных портов. Функция bind позволяет ввести номер порта и IP-адрес в TCB. Вызов getsockname имеет форму:
возвращаемый_код = getsockname(дескриптор_socket, адресная_структура, длина_адресной_структуры)
Мы запросили у bind выделение порта, но эта функция не сообщает нам, какой именно порт был предоставлен. Для выяснения этого нужно прочитать соответствующие данные из TCB. Функция getsockname() извлекает информацию из TCB и копирует ее в адресную структуру, где можно будет прочитать эти сведения. Номер порта извлекается и выводится следующим оператором:
printf("SERVER: Номер порта %dn", ntohs(servAddr.sin_port));
Функция ntohs() имеет полное название network-to-host-short и служит для преобразования номера порта из порядка следования байт в сети в локальный порядок следования байт на хосте.
4. listen(sockMain, 5);
Вызов listen применяется для ориентированных на соединение серверов и имеет форму:
возвращаемый_код = listen(дескриптор_socket, размер_очереди)
Вызов listen указывает, что это будет пассивный socket, и создает очередь требуемого размера для хранения поступающих запросов на соединения.
5. sockClient = accept(sockMain, 0, 0);
Вызов accept имеет форму:
новым_дескриптор_socket = accept(дескриптор_socket,
клиентская_адресная_структура, длина_клиентской_адресной_структуры)
По умолчанию вызов блокируется до соединения клиента с сервером. Если указана переменная клиентская_адресная_структура, после соединения клиента в эту структуру будут введены IP-адрес и порт клиента. В этом примере программы не проверяются IP-адрес и номер порта клиента, а просто два последних поля параметра заполняются нулями.
6. child = fork();
…
close(sockMain);
В языке С команда fork создает новый дочерний процесс, который наследует все дескрипторы ввода/вывода родительской программы, а также имеет доступ к sockMain и sockClient. Операционная система отслеживает количество процессов, имеющих доступ к socket.
Соединение закрывается, когда последний обращающийся к socket процесс вызывает close(). Когда дочерний процесс закрывает sockMain, родительский процесс все еще имеет доступ к socket.
7. close(sockClient);
Этот вызов выполняется из родительской части программы. Когда родительский процесс закрывает sockClient, дочерний процесс все еще имеет доступ к socket.
8. msgLength = recv(sockClient, buf, BUFLEN, 0));
…
close(sockClient);
Вызов recv имеет форму:
длина_сообщения = recv(дескриптор_socket, буфер, длина_буфера, флаги)
По умолчанию вызов recv блокированный. Функции fcntl() или iocntl() позволяют изменить статус socket на неблокированный режим.
После получения данных дочерним процессом и вывода сообщения на печать, доступ к sockClient закрывается. Это заставит соединение перейти в фазу закрытия.
21.7 Клиентская программа TCP
Клиент соединяется с сервером, посылает одно сообщение, и далее работа программы завершается (фрагменты программы рассматриваются в следующем разделе). Для запуска программы конечный пользователь должен ввести имя хоста сервера, номер порта и сообщение, которое будет послано на этот сервер. Например:
tcpclient pltim.cs.yale.edu 1356 hello
/* tcpclient.с
* Перед запуском клиента должен быть запущен сервер. Производится
* поиск порта сервера. Для запуска клиента нужно ввести:
* tcpclient имя_хоста порт сообщение */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <errno.h>
main(argc, argv) /* Клиентская программа имеет входные аргументы. */
int argc;
char* argv[];
{
int sock;
struct sockaddr_in servAddr;
struct hostent *hp, *gethostbyname();
/* Аргументами будут 0:имя_программы, 1:имя_хоста, 2:порт, 3:сообщение */
if (argc < 4) {
printf("ВВЕСТИ tcpclient имя_хоста порт сообщениеn");
exit(1);
}
/* 1. Создание TCB. */
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("He могу получить socketn");
exit(1);
}
/* 2. Заполнить поля адреса и порта сервера в servAddr.
* Сначала заполнить нулями адресную структуру. Затем получить IP-адрес
* для данного имени хоста и ввести его в адресную структуру.
* Наконец ввести номер порта, взяв его из argv[2]. */
bzero((char *)&servAddr, sizeof(servAddr));
servAddr.sin_family = AF_INET;
hp = gethostbyname (argv[1]);
bcopy(hp->h_addr, &servAddr.sin_addr, hp->h_length);
servAddr.sin_port = htons(atoi(argv[2]));
/* 3. Соединиться с сервером. Вызывать bind не нужно.
* Система присвоит свободный порт во время выполнения соединения. */
if (connect (sock, &servAddr, sizeof(servAddr)) < 0) {
perror("Клиент не может соединиться.n");
exit(1);
}
/* 4. Клиент анонсирует свою готовность послать сообщение.
* Сообщение отправляется, и распечатывается последняя строка. */
printf ("CLIENT: Готов к пересылкеn");
if (send(sock, argv[3], strlen(argv[3]), 0) < 0) {
(perror("Проблемы с пересылкой.n");
exit(1);
}
printf ("CLIENT: Пересылка завершена. Счастливо оставаться.n");
close(sock);
exit(0);
}
21.7.1 Вызовы в клиентской программе TCP
1. sock = socket(AF_INET, SOCK_STREAM, 0);
Клиент создает блок управления пересылкой ("socket") так же, как это делал сервер.
2. Сервер должен инициализировать адресную структуру для использования в bind.
Эта структура содержит локальный IP-адрес и номер порта сервера. Клиент также инициализирует адресную структуру, хранящую те же сведения. Эта структура будет использоваться в вызове connect для указания точки назначения.
Вызов bzero() помещает нули в servAddr — адресную структуру сервера. Еще раз мы трактуем семейство адресов как Интернет.
Затем нужно преобразовать введенное пользователем имя хоста в IP-адрес. Это делает функция gethostbyname, которая возвращает указатель на структуру hostent, содержащую имя сервера и IP-адрес.
Функция bcopy применяется для копирования IP-адреса (который находится в hp->h_addr) в servAddr.
Второй введенный конечным пользователем аргумент определял порт сервера. Он читался как текстовая строка ASCII, поэтому ее сначала нужно преобразовать в целое число через atoi(), а затем изменить порядок следования байт через htons(). Наконец номер порта копируется в адресную переменную из servAddr.
bzero((char *)&servAddr, sizeof(servAddr));
servAddr.sin_family = AF_INET;
hp = gethostbyname(argv[i]);
bcopy(hp->h_addr, &servAddr.sin_addr, hp->h_length);
servAddr.sin_port = htons(atoi(argv[2]));
3. connect(sock, &servAddr, sizeof(servAddr)); Вызов connect имеет форму:
connect(дескриптор_socket, адресная_структура, длина_адресной_структуры)
Клиент откроет соединение с сервером, IP-адрес и порт которого хранятся в адресной структуре.
4. send (sock, argv[3], strlen(argvs[3]), 0); Вызов send имеет форму:
возвращаемый_код = send(дескриптор_socket, буфер, длина_буфера, флаги)
Отметим, что введенный конечным пользователем третий аргумент (который появляется в программе как argv[3]) — это текст отправляемого сообщения. Обычно флаги используются для сообщения о срочных данных. В нашем случае параметры флагов установлены в 0.
5. close(sock);
Клиент выполняет close для закрытия соединения.
21.8 Более простой сервер
Многие серверы разрабатываются как в показанном выше примере. Однако можно использовать более упрощенную модель, когда сервер должен выполнять только простые запросы клиента (см. ниже).
Вместо создания дочернего процесса для каждого клиента сервер может непосредственно выполнять запрос, а затем закрывать соединение. Очередь сервера позволяет нескольким другим клиентам ожидать, пока он не будет готов обработать их запросы.