Обычно применяется синхронная форма приема запросов, чтобы при пустой очереди accept() ожидал появления следующего клиента до ответа на полученный запрос.
21.5.3 Активное открытие клиента TCP
Открытый клиент активно запрашивает соединение через два запроса:
socket() | Клиент идентифицирует тип связи (в данном случае TCP). Локальная система создает соответствующую структуру данных TCB для соединения и возвращает локальный дескриптор socket. |
connect() | Клиент указывает IP-адрес и порт сервера. TCP попытается установить соединение с сервером. |
Если клиент желает явно определить применяемый далее локальный порт, он должен вызвать bind() перед выдачей запроса connect(). Если порт доступен, он присваивается клиенту.
Если клиент запросил порт не через bind(), ему присваивается один из неиспользованных портов. Номер порта вводится в TCB.
21.5.4 Другие запросы
Оставшиеся запросы используются клиентом и сервером аналогичным способом. Данные могут быть переданы и получены через обычные запросы записи и чтения. Соединение может быть закрыто по запросу close. Существуют также специальные запросы send и recv, поддерживающие отправку и получение как срочных, так и обычных данных:
send() | Запись буфера данных в socket. Как альтернативу можно применить write(). |
sendv() | Пересылка в socket последовательности буферов. Как альтернативу можно применить writev(). |
recu() | Получение буфера данных из socket либо из read(). |
recvmsg() | Получение последовательности буферов из socket либо из readv(). |
Иногда программе нужна информация, хранящаяся в TCB:
getsockopt() | Чтение выбранной информации из TCB. Иногда система обеспечивает необязательные системные запросы ввода/вывода, которые позволяют читать различные части TCB. |
Проверка входных параметров запросов на открытие, отправку или получение показывает, что этих параметров очень мало. Причина в том, что обычно для большинства параметров TCB используются значения по умолчанию, содержащие важную информацию об окружении, например о размере приемного буфера, разрешении регистрации событий либо об использовании блокированной или неблокированной обработки в запросах, подобных recv. Некоторые значения по умолчанию можно изменить с помощью функций:
setsockopt() | Устанавливает значения нескольких параметров TCB, например размеры приемного и выходного буферов, пересылку срочных данных в общем порядке оправки информации либо блокировку закрытия соединения до благополучной отправки всех данных. |
iocntl() | Устанавливает ввод/вывод в socket в режим блокирования |
или fcntl() | или снимает блокирование. |
На рис. 21.2 демонстрируется последовательность вызовов в типичном сеансе TCP. Вызовы socket(), bind() и listen() обрабатываются очень быстро, и на них немедленно возвращается ответ.
Рис. 21.2. Последовательность программных вызовов в socket TCP
Вызовы accept(), send() и recv() предполагаются в режиме блокирования (что является их обычным значением по умолчанию). Вызов send блокируется и при переполнении выходного буфера TCP. Вызовы write() и read() можно использовать вместо send() и recv().
21.6 Серверная программа TCP
Рассмотрим подробно пример серверной программы. Сервер предназначен для непрерывной работы. Он будет выполнять следующие действия:
1. Запрашивать у socket создание главного TCB и возвращать значение дескриптора socket, который будет идентифицировать этот TCB в последующих вызовах.
2. Вводить локальный адрес сервера socket в структуру данных программы.
3. Запрашивать связывание, при котором в TCB копируется локальный адрес socket.
4. Создавать очередь, которая сможет хранить сведения о пяти клиентах. Оставшиеся шаги повторяются многократно:
5. Ожидать запросов от клиентов. Когда появляется клиент, создавать для него новый TCB на основе копии главного TCB и записи в него адреса socket клиента и других параметров.
6. Создавать дочерний процесс для обслуживания клиента. Дочерний процесс будет наследовать новый TCB и обрабатывать все дальнейшие операции по связи с клиентом
(ожидать сообщений от клиента, записывать их и завершать работу).
Каждый шаг в программе объясняется в следующем разделе.
/* tcpserv.c
* Для запуска программ ввести "tcpserv". */
/* Сначала включить набор стандартных заголовочных файлов. */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <netdb.h>
#include <errno.h>
main() {
int sockMain, sockClient, length, child;
struct sockaddr_in servAddr;
/* 1. Создать главный блок управления пересылкой. */
if ((sockMain = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Сервер не может открыть главный socket.");
exit(1);
}
/* 2. Создать структуру данных для хранения локальных IP-адресов
* и портов, которые будут использованы. Предполагается прием
* клиентских соединений от любых локальных IP-адресов
* (INADDR_ANY). Поскольку данный сервер не применяет