Читаем Linux API. Исчерпывающее руководство онлайн бесплатно
Целые числа могут иметь разное представление на серверном и клиентском компьютерах, поэтому мы переводим их в строки с символом новой строки в конце, а для их чтения применяем функцию readLine() (см. листинг 55.1).
И сервер, и клиент задействуют заголовочный файл, представленный в листинге 55.5, который, в свою очередь, подключает другие файлы и определяет номер TCP-порта для нашего приложения.
Серверная программа, представленная в листинге 55.6, выполняет следующие шаги.
• Начинает последовательность либо с 1, либо с числа, переданного в виде аргумента командной строки .
• Игнорирует сигнал SIGPIPE , возникающий, когда сервер пытается выполнить запись в сокет, другой конец которого был закрыт; вместо этого операция write() завершается ошибкой EPIPE.
• Вызывает функцию getaddrinfo() для получения списка структур с адресами сокетов, подключенных к TCP-порту PORT_NUM (вместо того чтобы задавать фиксированный номер порта, обычно лучше использовать имя службы). Мы указываем флаг AI_PASSIVE
для привязки итогового сокета к универсальному адресу (см. раздел 54.5). Как следствие, сервер сможет принимать запросы на соединение, направленные на любой из его адресов (если их у него несколько).
• Входит в цикл, перебирающий структуры с адресами сокетов, полученными в предыдущем шаге . Цикл завершается, когда программа находит адрес, с помощью которого можно успешно создать и привязать сокет
.
• Устанавливает параметр SO_REUSEADDR для сокета, созданного в предыдущем шаге . Мы вернемся к обсуждению этого параметра в разделе 57.10, где будет отмечено, что TCP-сервер обычно должен устанавливать SO_REUSEADDR для своего слушающего сокета.
• Делает сокет слушающим .
• Входит в бесконечный цикл for , который обслуживает клиентов итерационным способом (см. главу 56). Сервер не принимает очередной клиентский запрос, пока не закончит обслуживание текущего. Для каждого клиента сервер выполняет следующие шаги.
• Принимает новое соединение . Передает второму и третьему аргументу вызова accept() ненулевые указатели, чтобы получить адрес клиента (IP-адрес плюс номер порта) и направить его в стандартный вывод
.
• Считывает клиентское сообщение , которое заканчивается символом новой строки, и определяет, сколько последовательных номеров запрашивает клиент. Сервер переводит эту строку в целое число и сохраняет результат в переменной reqLen
.
• Возвращает клиенту текущий элемент последовательности (seqNum) в виде строки с нулевым символом в конце . Клиент может считать, что для него была выделена последовательность номеров в диапазоне от seqNum до (seqNum + reqLen — 1).
• Обновляет текущий элемент последовательности, добавляя к seqNum значение reqLen .
Листинг 55.5. Заголовочный файл, используемый программами is_seqnum_sv.c и is_seqnum_cl.c
sockets/is_seqnum.h
#include
#include
#include
#include "read_line.h" /* Объявление readLine() */
#include "tlpi_hdr.h"
#define PORT_NUM "50000" /* Номер порта для сервера */
#define INT_LEN 30 /* Размер строки, достаточный для хранения наибольшего
целого числа (включая завершающий символ '\n') */
sockets/is_seqnum.h
Листинг 55.6. Итерационный сервер, который взаимодействует с клиентами с помощью потокового сокета
sockets/is_seqnum_sv.c
#define _BSD_SOURCE /* Получаем определения NI_MAXHOST и NI_MAXSERV
из файла
#include
#include "is_seqnum.h"
#define BACKLOG 50
int
main(int argc, char *argv[])
{
uint32_t seqNum;
char reqLenStr[INT_LEN]; /* Длина запрашиваемой последовательности */
char seqNumStr[INT_LEN]; /* Начало выделенной последовательности */
struct sockaddr_storage claddr;
int lfd, cfd, optval, reqLen;
socklen_t addrlen;
struct addrinfo hints;
struct addrinfo *result, *rp;
#define ADDRSTRLEN (NI_MAXHOST + NI_MAXSERV + 10)
char addrStr[ADDRSTRLEN];
char host[NI_MAXHOST];
char service[NI_MAXSERV];
if (argc > 1 && strcmp(argv[1], "-help") == 0)
usageErr("%s [init-seq-num]\n", argv[0]);
seqNum = (argc > 1)? getInt(argv[1], 0, "init-seq-num"): 0;
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
errExit("signal");
/* Вызываем getaddrinfo(), чтобы получить список адресов,
к которым можем попытаться привязать наш сокет */
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_canonname = NULL;
hints.ai_addr = NULL;
hints.ai_next = NULL;
hints.ai_socktype = SOCK_STREAM;
hints.ai_family = AF_UNSPEC; /* Поддержка IPv4 или IPv6 */
hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV;
/* Универсальный IP-адрес; имя службы имеет числовой формат */
if (getaddrinfo(NULL, PORT_NUM, &hints, &result)!= 0)
errExit("getaddrinfo");
/* Перебираем полученный список, пока не находим структуру с адресом,
подходящую для создания и привязывания сокета */
optval = 1;
for (rp = result; rp!= NULL; rp = rp->ai_next) {
lfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (lfd == -1)




