Стандарт POSIX-2001 определяет
Данные о хостах как узлах
Обычно
Поддерживается
Данные передаются по
Для преобразования значений типов uint16_t и uint32_t из htons() и htonl() ; функции ntohs() и ntohl() осуществляют обратную операцию.
При взаимодействии процессов
Основные описания, относящиеся к <sys/socket.h>. Фигурирует в нем и упомянутая выше структура sockaddr для
sa_family_t sa_family; /* Адресное семейство */ char sa_data []; /* Адрес сокета (данные переменной длины) */
AF_UNIX
<sys/un.h>.
AF_INET
IPv4. Специфичные
для него описания располагаются в заголовочном файле <netinet/in.h>.
AF_INET6
Взаимодействие по IPv6 (необязательная возможность). За счет поддержки
адресов IPv6, отображенных на IPv4, обеспечивается совместимость с
приложениями, использующими IPv4. Применяемые эти <netinet/in.h>, < и <netdb.h>.
В пределах каждого
SOCK_STREAM
SOCK_SEQPACKET
Аналог SOCK_STREAM с дополнительным сохранением границ между записями.
SOCK_DGRAM
Передача данных в виде
SOCK_RAW
(Необязательная возможность). Аналог SOCK_DGRAM с дополнительной возможностью
доступа к протокольным заголовкам и другой информации нижнего уровня. Создавать
Для каждого AF_INET для SOCK_STREAM
подразумеваемым является IPPROTO_TCP, а для SOCK_DGRAM - IPPROTO_UDP ; посредством "прозрачных" SOCK_RAW ) можно
воспользоваться ICMP, задав имя IPPROTO_ICMP, и т.д.
Общая логика работы с socket(), которой в качестве аргументов передают bind() listen(), его следует пометить как готового принимать
соединения. Реальный accept(), создающая для
каждого из них новый connect(). (В connect() позволяет
специфицировать адрес отправляемых через
Для read() или специализированным семейством функций recv*(), а для передачи - функцией write() или семейством send*().
Кроме того, функции select() и/или poll() помогут проверить наличие
данных для
Обращение к функции shutdown() завершает взаимодействие между партнерами.
Данные о хостах как узлах sethostent(), gethostent() и endhostent() (см. листинг 11.1).
#include <netdb.h> void sethostent (int stayopen); struct hostent *gethostent (void); void endhostent (void);
Функция sethostent() устанавливает gethostent(), если значение аргумента stayopen отлично от нуля.
Функция gethostent() последовательно читает элементы базы, возвращая результат
в структуре типа hostent, содержащей по крайней мере следующие поля.
char *h_name; /* Официальное имя хоста */ char **h_aliases; /* Массив указателей на альтернативные */ /* имена хоста, завершаемый пустым */ /* указателем */ int h_addrtype; /* Тип адреса хоста */ int h_length; /* Длина в байтах адреса данного типа */ char **h_addr_list; /* Массив указателей на сетевые адреса */ /* хоста, завершаемый пустым указателем */
Функция endhostent() закрывает
В листинге 11.2 показана программа, осуществляющая последовательный просмотр сетевой
#include <stdio.h>
#include <netdb.h>
int main (void) {
struct hostent *pht;
char *pct;
int i, j;
sethostent (1);
while ((pht = gethostent ()) != NULL) {
printf ("Официальное имя хоста: %s\n", pht->h_name);
printf ("Альтернативные имена:\n");
for (i = 0; (pct = pht->h_aliases [i]) != NULL; i++) {
printf (" %s\n", pct);
}
printf ("Тип адреса хоста: %d\n", pht->h_addrtype);
printf ("Длина адреса хоста: %d\n", pht->h_length);
printf ("Сетевые адреса хоста:\n");
for (i = 0; (pct = pht->h_addr_list [i]) != NULL; i++) {
for (j = 0; j < pht->h_length; j++) {
printf (" %d", (unsigned char) pct [j]);
}
printf ("\n");
}
}
endhostent ();
return 0;
}
Официальное имя хоста: localhost Альтернативные имена: Тип адреса хоста: 2 Длина адреса хоста: 4 Сетевые адреса хоста: 127 0 0 1 . . . Официальное имя хоста: t01 Альтернативные имена: niisi.msk.ru t01.niisi.msk.ru mail mailhost loghost server server3 server3.systud.msk.su www www.systud.msk.su t01.systud.msk.su Тип адреса хоста: 2 Длина адреса хоста: 4 Сетевые адреса хоста: 193 232 173 1 . . . Официальное имя хоста: t17 Альтернативные имена: galatenko Тип адреса хоста: 2 Длина адреса хоста: 4 Сетевые адреса хоста: 193 232 173 17 . . .
К рассматриваемой базе возможен и случайный доступ по ключам - именам и адресам
хостов с помощью функций gethostbyname() и gethostbyaddr(), однако они считаются
устаревшими и из новой версии стандарта POSIX могут быть исключены. Вместо них
предлагается использовать функции getnameinfo() и getaddrinfo() (см. листинг 11.4).
#include <sys/socket.h>
#include <netdb.h>
void freeaddrinfo (struct addrinfo *ai);
int getaddrinfo
(const char *restrict nodename,
const char *restrict servname,
const struct addrinfo *restrict hints,
struct addrinfo **restrict res);
int getnameinfo
(const struct sockaddr *restrict sa,
socklen_t salen, char *restrict node,
socklen_t nodelen, char *restrict service,
socklen_t servicelen, int flags);
Функция getaddrinfo() позволяет по имени nodename ) и/или имени сетевого servname )
получить набор
Если аргумент nodename отличен от пустого указателя, он способен задавать описательное
имя или адресную цепочку. Для AF_INET и AF_UNSPEC
(см. ниже описание аргумента hints ) именем может служить имя хоста,
а адресной цепочкой - стандартные для Internet адреса в точечных
обозначениях (например, 193.232.173.17 ).
При пустом значении nodename подразумевается хост, локальный для вызывающего процесса.
Аргумент servname может задавать имя AF_INET и AF_UNSPEC ) десятичный servname
означает запрос
Аргумент hints позволяет передать дополнительную информацию об опрашиваемом addrinfo,
описанная в заголовочном файле <netdb.h>, должна содержать по крайней мере следующие поля.
int ai_flags; /* Входные флаги */ int ai_family; /* Адресное семейство сокета */ int ai_socktype; /* Тип сокета */ int ai_protocol; /* Протокол сокета */ socklen_t ai_addrlen; /* Длина адреса сокета */ struct sockaddr *ai_addr; /* Адрес сокета */ char *ai_canonname; /* Официальное имя узла сети */ struct addrinfo *ai_next; /* Указатель на следующий элемент списка */
При обращении к функции getaddrinfo() все поля структуры addrinfo,
на которую указывает аргумент hints, кроме первых четырех
( ai_flags, ai_family, ai_socktype, ai_protocol ),
должны быть нулевыми или равными NULL. Значение AF_UNSPEC
в поле ai_family подразумевает, что вызывающего устроит любое ai_socktype и ai_protocol. При hints, равном NULL,
подразумевается AF_UNSPEC для ai_family и нулевые значения для других полей.
Из флагов, которые могут быть установлены в поле ai_flags, упомянем следующие.
AI_PASSIVE
Если значение аргумента nodename равно NULL,
этот флаг игнорируется. В противном случае, если он указан, будет возвращен
AI_CANONNAME
Данный флаг предписывает выяснить официальное имя
AI_NUMERICHOST
Флаг означает, что хост задан адресной цепочкой, и не допускает использования
какого-либо
AI_NUMERICSERV
Флаг помечает, что servname )
задан
Признаком успешного завершения функции getaddrinfo() является нулевой результат.
В таком случае res будет ссылаться на указатель на список
структур типа addrinfo (связанных полем ai_next ) - принадлежащие им значения
полей ai_family, ai_socktype, ai_protocol пригодны для socket(), а значения ai_addr и ai_addrlen, в зависимости
от флага AI_PASSIVE, могут служить аргументами функций connect() или bind(),
применяемых к созданному
Функция freeaddrinfo() позволяет освободить память, занятую списком структур типа addrinfo и ассоциированными данными.
Функцию getnameinfo() можно считать обратной по отношению к getaddrinfo().
Аргумент sa - входной, он задает транслируемый salen - размер структуры sockaddr ). Аргументы node и service - выходные, задающие
адреса областей памяти, куда помещаются, соответственно, имя узла и nodelen и servicelen.
По умолчанию предполагается, что SOCK_STREAM, а кроме того, возвращается
полное доменное имя хоста. Аргумент flags позволяет изменить подразумеваемое поведение.
Если задан флаг NI_DGRAM, NI_NOFQDN возвращается короткое имя узла. Флаги NI_NUMERICHOST и NI_NUMERICSERV предписывают возвращать числовые цепочки для адресов хоста и
Обратим внимание на несколько технических деталей. При работе с sockaddr обычно используются более специализированные (описанные в заголовочном
файле <netinet/in.h> ) - sockaddr_in для AF_INET
( IPv4 ) и sockaddr_in6 для AF_INET6 ( IPv6 ).
Первая из них, согласно стандарту POSIX-2001, должна содержать по крайней мере следующие поля
(все с
sa_family_t sin_family; /* AF_INET */ in_port_t sin_port; /* Номер порта */ struct in_addr sin_addr; /* IP-адрес */
Структура типа sockaddr_in6 устроена несколько сложнее; мы не будем ее рассматривать.
Структуры типов sockaddr и sockaddr_in (или sockaddr_in6 )
мысленно накладываются друг на друга, а преобразование типов между ними выполняется по мере
необходимости. Отметим также, что тип in_port_t эквивалентен uint16_t,
структура типа in_addr содержит по крайней мере одно поле:
in_addr_t s_addr; тип in_addr_t эквивалентен uint32_t.
Техническую роль играют и функции преобразования IP-адресов из текстового представления в числовое и наоборот (см. листинг 11.5).
#include <arpa/inet.h>
in_addr_t inet_addr (const char *cp);
char *inet_ntoa (struct in_addr in);
int inet_pton (int af, const char
*restrict src, void *restrict dst);
const char *inet_ntop (int af, const void
*restrict src, char *restrict dst,
socklen_t size);
Первые две функции манипулируют только адресами IPv4: inet_addr() преобразует
текстовую цепочку cp (адрес в стандартных точечных обозначениях) в пригодное
для использования в качестве IP-адреса целочисленное значение, inet_ntoa()
выполняет обратное преобразование.
Вторая IPv6. Первый аргумент этих
функций, af, задает AF_INET для IPv4
и AF_INET6 для IPv6. Буфер, на который указывает аргумент
функции inet_pton() (в него помещается результат преобразования - IP-адрес в числовой
двоичной форме с IPv4
и 128 бит для IPv6.
Аргумент src функции inet_ntop(), возвращающей текстовое представление,
указывает на буфер с IP-адресом в числовой форме с size
задает длину выходного буфера, на него указывает аргумент . Подходящими значениями,
в зависимости от INET_ADDRSTRLEN или INET6_ADDRSTRLEN.
Выше было отмечено, что преобразование значений типов uint16_t и uint32_t
из htons() и htonl() ; функции ntohs() и ntohl() осуществляют обратную
операцию (см. листинг 11.6).
#include <arpa/inet.h> uint32_t htonl (uint32_t hostlong); uint16_t htons (uint16_t hostshort); uint32_t ntohl (uint32_t netlong); uint16_t ntohs (uint16_t netshort);
Использование функции getaddrinfo() вместе с сопутствующими техническими
деталями проиллюстрируем программой, показанной в листинге 11.7. Возможные результаты ее
выполнения приведены в листинге 11.8.
#include <stdio.h>
#include <netdb.h>
#include <arpa/inet.h>
int main (void) {
struct addrinfo hints = {AI_CANONNAME, AF_INET, SOCK_STREAM, IPPROTO_TCP,
0, NULL, NULL, NULL};
struct addrinfo *addr_res;
if (getaddrinfo ("www", "http", hints, addr_res) != 0) {
perror ("GETADDRINFO");
} else {
printf ("Результаты для сервиса http\n");
/* Пройдем по списку возвращенных структур */
do {
printf ("Адрес сокета: Порт: %d IP-адрес: %s\n",
ntohs (((struct sockaddr_in *) addr_res->ai_addr)->sin_port),
inet_ntoa (((struct sockaddr_in *) addr_res->ai_addr)->sin_addr));
printf ("Официальное имя хоста: %s\n", addr_res->ai_canonname);
} while ((addr_res = addr_res->ai_next) != NULL);
}
return 0;
}
Результаты для сервиса http Адрес сокета: Порт: 80 IP-адрес: 193.232.173.1 Официальное имя хоста: t01
Завершая изложение серии технических моментов, укажем, что полезным дополнением к функциям getaddrinfo() и getnameinfo() является функция gai_strerror()
(см. листинг 11.9). Она возвращает текстовую цепочку, расшифровывающую коды ошибок, перечисленные
в заголовочном файле <netdb.h>. Стандарт POSIX-2001 специфицирует следующие коды
ошибок, имена которых говорят сами за себя: EAI_AGAIN, EAI_BADFLAGS, EAI_FAIL, EAI_FAMILY, EAI_MEMORY, EAI_NONAME, EAI_OVERFLOW, EAI_SERVICE, EAI_SOCKTYPE, EAI_SYSTEM.
#include <netdb.h> const char *gai_strerror (int ecode);
Если немного модифицировать приведенную выше программу (в листинге 11.10 показан измененный
фрагмент, где имя "HTTP" - задано большими буквами), то в стандартный gai_strerror() будет выдано содержательное диагностическое perror() в данном случае
невразумительна.
. . .
int res;
if ((res = getaddrinfo ("www", "HTTP", hints, addr_res)) != 0) {
perror ("GETADDRINFO");
fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res));
} else {
printf ("Результаты для сервиса HTTP\n");
. . .
GETADDRINFO: No such file or directory GETADDRINFO: Servname not supported for ai_socktype
Наряду с базой данных хостов (
#include <netdb.h>
void setnetent (int stayopen);
struct netent *getnetent (void);
struct netent *getnetbyaddr (uint32_t net,
int type);
struct netent *getnetbyname (const char *name);
void endnetent (void);
Функция getnetent() обслуживает последовательный доступ к базе, getnetbyaddr()
осуществляет поиск по type ) и номеру net getnetbyname() выбирает netent,
указатель на которую возвращается в качестве результата этих функций, согласно стандарту POSIX-2001, должна
содержать по крайней мере следующие поля.
char *n_name; /* Официальное имя сети */ char **n_aliases; /* Массив указателей на альтернативные */ /* имена сети, завершаемый пустым указателем */ int n_addrtype; /* Адресное семейство (тип адресов) сети */ uint32_t n_net; /* Номер сети (в хостовом порядке байт) */
Точно такой же программный интерфейс предоставляет
#include <netdb.h>
void setprotoent (int stayopen);
struct protoent *getprotoent (void);
struct protoent *getprotobyname
(const char *name);
struct protoent *getprotobynumber (int proto);
void endprotoent (void);
Структура типа protoent содержит по крайней мере следующие поля.
char *p_name; /* Официальное имя протокола */ char **p_aliases; /* Массив указателей на альтернативные */ /* имена протокола, завершаемый пустым */ /* указателем */ int p_proto; /* Номер протокола */
В листинге 11.14 показан пример программы, осуществляющей последовательный и случайный доступ
к
#include <stdio.h>
#include <netdb.h>
int main (void) {
struct protoent *pht;
char *pct;
int i;
setprotoent (1);
while ((pht = getprotoent ()) != NULL) {
printf ("Официальное имя протокола: %s\n", pht->p_name);
printf ("Альтернативные имена:\n");
for (i = 0; (pct = pht->p_aliases [i]) != NULL; i++) {
printf (" %s\n", pct);
}
printf ("Номер протокола: %d\n\n", pht->p_proto);
}
if ((pht = getprotobyname ("ipv6")) != NULL) {
printf ("Номер протокола ipv6: %d\n\n", pht->p_proto);
} else {
fprintf (stderr, "Протокол ip в базе не найден\n");
}
if ((pht = getprotobyname ("IPV6")) != NULL) {
printf ("Номер протокола IPV6: %d\n\n", pht->p_proto);
} else {
fprintf (stderr, "Протокол IPV6 в базе не найден\n");
}
endprotoent ();
return 0;
}
Официальное имя протокола: ip Альтернативные имена: IP Номер протокола: 0 Официальное имя протокола: icmp Альтернативные имена: ICMP Номер протокола: 1 . . . Официальное имя протокола: tcp Альтернативные имена: TCP Номер протокола: 6 . . . Официальное имя протокола: udp Альтернативные имена: UDP Номер протокола: 17 . . . Официальное имя протокола: ipv6 Альтернативные имена: IPv6 Номер протокола: 41 . . . Официальное имя протокола: ipv6-crypt Альтернативные имена: IPv6-Crypt Номер протокола: 50 . . . Официальное имя протокола: visa Альтернативные имена: VISA Номер протокола: 70 . . . Официальное имя протокола: iso-ip Альтернативные имена: ISO-IP Номер протокола: 80 . . . Официальное имя протокола: sprite-rpc Альтернативные имена: Sprite-RPC Номер протокола: 90 . . . Официальное имя протокола: ipx-in-ip Альтернативные имена: IPX-in-IP Номер протокола: 111 . . . Официальное имя протокола: fc Альтернативные имена: FC Номер протокола: 133 Номер протокола ipv6: 41 Протокол IPV6 в базе не найден
Еще одно проявление той же логики работы -
#include <netdb.h>
void setservent (int stayopen);
struct servent *getservent (void);
struct servent *getservbyname
(const char *name, const char *proto);
struct servent *getservbyport
(int port, const char *proto);
void endservent (void);
Обратим внимание на то, что в данном случае можно указывать второй аргумент поиска - имя proto может быть пустым указателем, и тогда поиск производится
только по имени getservbyname() ) или getservbyport() ),
который должен быть задан с
Структура типа servent содержит по крайней мере следующие поля.
char *s_name; /* Официальное имя сервиса */ char **s_aliases; /* Массив указателей на альтернативные */ /* имена сервиса, завершаемый пустым */ /* указателем */ int s_port; /* Номер порта, соответствующий сервису */ /* (в сетевом порядке байт) */ char *s_proto; /* Имя протокола для взаимодействия с */ /* сервисом */
В листинге 11.17 приведен пример программы, использующей функции доступа к
#include <stdio.h>
#include <netdb.h>
int main (void) {
struct servent *pht;
char *pct;
int i;
setservent (1);
while ((pht = getservent ()) != NULL) {
printf ("Официальное имя сервиса: %s\n", pht->s_name);
printf ("Альтернативные имена:\n");
for (i = 0; (pct = pht->s_aliases [i]) != NULL; i++) {
printf (" %s\n", pct);
}
printf ("Номер порта: %d\n", ntohs ((in_port_t) pht->s_port));
printf ("Имя протокола: %s\n\n", pht->s_proto);
}
if ((pht = getservbyport (htons ((in_port_t) 21), "udp")) != NULL) {
printf ("Официальное имя сервиса: %s\n", pht->s_name);
printf ("Альтернативные имена:\n");
for (i = 0; (pct = pht->s_aliases [i]) != NULL; i++) {
printf (" %s\n", pct);
}
printf ("Номер порта: %d\n", ntohs ((in_port_t) pht->s_port));
printf ("Имя протокола: %s\n\n", pht->s_proto);
} else {
perror ("GETSERVBYPORT");
}
if ((pht = getservbyport (htons ((in_port_t) 21), (char *) NULL)) != NULL) {
printf ("Официальное имя сервиса: %s\n", pht->s_name);
printf ("Альтернативные имена:\n");
for (i = 0; (pct = pht->s_aliases [i]) != NULL; i++) {
printf (" %s\n", pct);
}
printf ("Номер порта: %d\n", ntohs ((in_port_t) pht->s_port));
printf ("Имя протокола: %s\n\n", pht->s_proto);
} else {
perror ("GETSERVBYPORT");
}
endservent ();
return 0;
}
. . . Официальное имя сервиса: ftp-data Альтернативные имена: Номер порта: 20 Имя протокола: tcp Официальное имя сервиса: ftp-data Альтернативные имена: Номер порта: 20 Имя протокола: udp Официальное имя сервиса: ftp Альтернативные имена: Номер порта: 21 Имя протокола: tcp Официальное имя сервиса: ftp Альтернативные имена: fsp fspd Номер порта: 21 Имя протокола: udp . . . Официальное имя сервиса: kerberos Альтернативные имена: kerberos5 krb5 Номер порта: 88 Имя протокола: tcp Официальное имя сервиса: kerberos Альтернативные имена: kerberos5 krb5 Номер порта: 88 Имя протокола: udp . . . Официальное имя сервиса: auth Альтернативные имена: authentication tap ident Номер порта: 113 Имя протокола: tcp Официальное имя сервиса: auth Альтернативные имена: authentication tap ident Номер порта: 113 Имя протокола: udp . . . Официальное имя сервиса: printer Альтернативные имена: spooler Номер порта: 515 Имя протокола: tcp Официальное имя сервиса: printer Альтернативные имена: spooler Номер порта: 515 Имя протокола: udp . . . Официальное имя сервиса: fido Альтернативные имена: Номер порта: 60179 Имя протокола: tcp Официальное имя сервиса: fido Альтернативные имена: Номер порта: 60179 Имя протокола: udp Официальное имя сервиса: ftp Альтернативные имена: fsp fspd Номер порта: 21 Имя протокола: udp Официальное имя сервиса: ftp Альтернативные имена: Номер порта: 21 Имя протокола: tcp
Отметим, что при поиске по ключу возвращается первый подходящий элемент базы данных.
По этой причине, когда не был задан getservbyport() ), в качестве результата был возвращен элемент с tcp.
Работа с socket() (см. листинг 11.19). Она возвращает открытый
файловый дескриптор, который может быть использован в последующих вызовах функций, оперирующих с socketpair(), создающей пару
#include <sys/socket.h>
int socket (int af, int type, int protocol);
int socketpair (int af, int type,
int protocol, int sds [2]);
Аргумент af задает type - protocol - конкретный 0 обозначает
неспецифицированный подразумеваемый af, type и protocol можно
получить с помощью описанной ранее функции getaddrinfo().
Функция socketpair() по назначению аналогична pipe(), только организуется не
безымянный канал, а sds. Обычно она
используется для AF_UNIX ; поддержка для других семейств не гарантируется.
После того как посредством функции bind() (см. листинг 11.20) sd, ему присваивают address ( address_len - длина структуры sockaddr, на которую
указывает address ). Источником локальных адресов для getaddrinfo().
#include <sys/socket.h>
int bind (int sd, const struct sockaddr
*address, socklen_t address_len);
getsockname() (см. листинг 11.21): она помещает его в структуру sockaddr,
на которую указывает аргумент address, а длину адреса записывает по указателю address_len.
#include <sys/socket.h>
int getsockname
(int sd, struct sockaddr *restrict address,
socklen_t *restrict address_len);
Если SOCK_STREAM ), то,
воспользовавшись функцией listen() (см. листинг 11.22), его следует пометить как готового
принимать соединения ("
#include <sys/socket.h> int listen (int sd, int backlog);
Аргумент backlog сообщает операционной системе рекомендуемую длину backlog вплоть до SOMAXCONN, определенной в
заголовочном файле <sys/socket.h>. ОС имеет право установить длину backlog
accept() (см. листинг 11.23). Она выбирает первое
соединение из sd
#include <sys/socket.h>
int accept
(int sd, struct sockaddr *restrict address,
socklen_t *restrict address_len);
Если значение аргумента address отлично от пустого указателя, то в структуру sockaddr, на которую указывает address, помещается address_len при обращении к функции accept() должна быть задана длина переданной структуры sockaddr, а на выходе
туда записывается длина адреса партнера по взаимодействию.
Если sd
не установлен флаг O_NONBLOCK, то вызвавший функцию accept() процесс
(поток управления) блокируется до появления подобного соединения. При непустой очереди функция select() сообщит о готовности дескриптора sd к чтению.
Другая сторона, т. е. потенциальный партнер по взаимодействию, инициирует соединение с помощью
функции connect() (см. листинг 11.24). Аргументы address и address_len стандартным образом задают
#include <sys/socket.h>
int connect (int sd, const struct sockaddr
*address, socklen_t address_len);
Если для sd (запрашивающего connect() сама осуществит AF_UNIX ).
Функция connect() ограничится фиксацией sd, если SOCK_DGRAM таким способом можно специфицировать адреса отправляемых
(с помощью функции send() ) и принимаемых (посредством обращения к функции recv() )
Когда в качестве аргумента address передается пустой указатель, адрес взаимодействующего
Попытка O_NONBLOCK ). Если по истечении этого промежутка соединение connect(), равно как и попытка connect() завершается
неудачей (переменной errno присваивается значение EINTR ), но
Если для дескриптора sd задан флаг O_NONBLOCK, а соединение не может быть connect() завершается неудачей со значением errno,
равным EINPROGRESS, но connect() с тем же EALREADY ).
connect() завершается
неудачей по другим причинам. Приложения, соответствующие стандарту POSIX-2001, должны закрыть файловый
дескриптор sd и создать новый
После select() и poll() сообщат,
что файловый дескриптор sd готов к записи.
Функция poll(), позволяющая select() и ее недавно введенный аналог pselect() представлены в листинге 11.25.
#include <sys/select.h>
int pselect (int nfds, fd_set *restrict readfds, fd_set *restrict writefds,
fd_set *restrict errorfds, const struct timespec *restrict timeout,
const sigset_t *restrict sigmask);
int select (int nfds, fd_set *restrict readfds, fd_set *restrict writefds,
fd_set *restrict errorfds, struct timeval *restrict timeout);
void FD_CLR (int fd, fd_set *fdset);
int FD_ISSET (int fd, fd_set *fdset);
void FD_SET (int fd, fd_set *fdset);
void FD_ZERO (fd_set *fdset);
Если значение аргумента readfds ( writefds, errorfds )
функции pselect() отлично от NULL, оно ссылается на объект типа fd_set, который на входе специфицирует набор файловых дескрипторов, проверяемых
на готовность к чтению (или к записи, или к обработке исключительных ситуаций), а на выходе
указывает, какие из них успешно прошли проверку. Аргумент nfds задает границу
проверяемых дескрипторов (они являются небольшими целыми числами): дескриптор подлежит проверке,
если его значение находится в пределах от 0 до (nfds - 1) включительно.
Стандарт POSIX-2001 определяет тип fd_set как абстрактный. Для работы со значениями
этого типа служат макросы FD_ZERO() (сделать набор пустым), FD_SET()
(добавить дескриптор к набору), FD_CLR() (удалить дескриптор из набора), FD_ISSET() (проверить принадлежность дескриптора набору). Значение именованной
константы FD_SETSIZE равно максимально допустимому числу дескрипторов в наборе.
В качестве результата pselect() возвращается общее количество дескрипторов во всех
трех наборах, успешно прошедших проверку.
При отсутствии готовых дескрипторов вызывающий процесс блокируется, пока таковые не появятся,
или пока не истечет заданное аргументом timeout timeout означает бесконечное ожидание,
а нулевые значения полей структуры timespec - отсутствие блокировки. Реализация может накладывать
ограничение на максимальное
Если значение аргумента sigmask отлично от пустого указателя, функция pselect()
на входе заменяет маску сигналов процесса на заданную, а на выходе восстанавливает прежнюю маску.
Функция pselect() поддерживает обычные файлы, терминалы и псевдотерминалы, каналы и
Функция select() эквивалентна pselect() со следующими оговорками.
select() timeval, а для pselect() - в секундах и наносекундах как аргумент типа struct timespec.select() нет аргумента - sigmask функции pselect().select() может модифицировать структуру, на которую
указывает аргумент timeout.С getsockopt() и setsockopt()
(см. листинг 11.26).
#include <sys/socket.h>
int getsockopt (int sd, int level,
int option_name,
void *restrict option_value,
socklen_t *restrict option_len);
int setsockopt (int sd, int level,
int option_name,
const void *option_value,
socklen_t option_len);
option_name ), которые определены в
заголовочном файле <sys/socket.h>. Выделим среди них наиболее употребительные и
разделим на несколько групп. К первой отнесем
SO_ERROR
SO_TYPE
Ко второй группе отнесем булевы
SO_DEBUG
Сообщает, записывается ли отладочная информация о работе
SO_ACCEPTCONN
Указывает, является ли
В третью группу включены
SO_SNDBUF
Размер буфера для передаваемых данных (выходного буфера).
SO_RCVBUF
Размер входного буфера.
SO_RCVLOWATM
Минимальное число байт, обрабатываемых при вводе. Подразумеваемое значение равно единице.
Большие значения могут вызвать блокировку принимающего процесса до поступления в
SO_SNDLOWAT
Минимальное число байт, обрабатываемых при выводе.
В четвертую группу входят
SO_LINGER
Определяет, блокировать ли процесс при закрытии дескриптора sd до передачи буферизованных
данных, и если блокировать, то на какой срок. linger,
определенная в заголовочном файле <sys/socket.h> и содержащая, согласно стандарту
POSIX-2001, по крайней мере следующие поля.
int l_onoff; /* Признак, включена ли опция блокирования */ /* при закрытии */ int l_linger; /* Длительность блокирования в секундах */
SO_RCVTIMEO
timeval. Подразумевая длительность равна нулю. Если в течение специфицированного
промежутка времени новых данных не поступило, операция ввода вернет число байт, меньше запрошенного,
или завершится ошибкой EAGAIN.
SO_SNDTIMEO
Не все из перечисленных setsockopt().
Исключение по понятным причинам составляют SO_ERROR, SO_TYPE, SO_ACCEPTCONN.
Аргумент level задает SOL_SOCKET, уровню TCP - IPPROTO_TCP.
Функция getpeername() (см. листинг 11.27), во многом аналогичная рассмотренной выше
функции getsockname(), позволяет опросить еще одну характеристику -
#include <sys/socket.h>
int getpeername (int sd,
struct sockaddr *restrict address,
socklen_t *restrict address_len);
После привязки
#include <sys/socket.h>
ssize_t recvfrom
(int sd, void *restrict buffer,
size_t length, int flags,
struct sockaddr *restrict address,
socklen_t *restrict address_len);
ssize_t recv (int sd, void *buffer,
size_t length, int flags);
ssize_t recvmsg (int sd, struct msghdr
*message, int flags);
ssize_t sendto
(int sd, const void *message,
size_t length, int flags,
const struct sockaddr *dest_addr,
socklen_t dest_len);
ssize_t send (int sd, const void *buffer,
size_t length, int flags);
ssize_t sendmsg (int sd, const struct msghdr
*message, int flags);
Функция recvfrom() позволяет прочитать данные (в рассматриваемом контексте называемые
также sd и поместить их в буфер buffer
длины length. Обычно функцию recvfrom() применяют к sockaddr.
В число возможных составляющих значения аргумента flags входят следующие флаги.
MSG_PEEK
Не удалять прочитанные данные. Следующий вызов recvfrom() или другой функции ввода снова вернет их.
MSG_WAITALL
Для SOCK_STREAM флаг означает, что вызывающий процесс блокируется до получения
всего запрошенного объема данных (а не до прихода первого
MSG_OOB
Запрашиваются
Как и положено функции ввода, в результате recvfrom() возвращает количество прочитанных
и помещенных в буфер данных. Для SOCK_RAW, SOCK_DGRAM, SOCK_SEQPACKET ), за одно обращение читается все MSG_PEEK ) отбрасываются.
Формально можно считать, что функция recv() эквивалентна recvfrom() с
нулевым значением аргумента address_len. Поскольку она не позволяет узнать recv() эквивалентна read().)
По сравнению с recv(), более содержательным аналогом функции recvfrom()
является recvmsg(). Отличия в данном случае носят скорее синтаксический характер и по
сути сводятся к способу передачи входных значений и возврата результатов: для минимизации числа
аргументов функция recvmsg() использует структуру типа msghdr, которая,
согласно стандарту POSIX-2001, должна содержать по крайней мере следующие поля.
void *msg_name; /* Указатель на буфер для адреса */ /* (исходного или целевого) */ socklen_t msg_namelen; /* Размер буфера для адреса */ struct iovec *msg_iov; /* Массив для разнесения/сборки сообщений */ int msg_iovlen; /* Число элементов в массиве */ /* для разнесения/сборки сообщений */ void *msg_control; /* Указатель на буфер */ /* для вспомогательных данных */ socklen_t msg_controllen; /* Размер буфера для вспомогательных данных */ int msg_flags; /* Флаги принятого сообщения */
Если для sd, соединение не msg_name буфер длины msg_namelen помещается msg_name можно сделать пустым.
При
Массив msg_iov совместно с полем msg_iovlen задает набор буферов для
размещения принимаемых данных. Структура типа iovec определяется в заголовочном файле <sys/uio.h> и содержит по крайней мере два поля.
void *iov_base; /* Адрес области памяти (буфера) */ size_t iov_len; /* Размер области памяти (в байтах) */
Заданные полем msg_iov области памяти по очереди заполняются поступающими данными,
пока не будут размещены все принятые данные или не заполнятся все буфера.
Трактовка msg_control и msg_controllen
структуры типа msghdr ) в стандарте POSIX-2001 весьма туманна. Описаны лишь средства
доступа к ним. Мы, однако, не будем на этом останавливаться.
В случае успешного завершения функции recvmsg() в поле msg_flags могут быть
установлены следующие флаги.
MSG_EOR
Достигнута
MSG_OOB
Получены
MSG_TRUNC
Пришлось урезать обычные данные.
MSG_CTRUNC
Пришлось урезать управляющие данные.
Подобно тому, как write() составляет пару read(), функцию sendto()
можно считать парной по отношению к recvfrom(). Она предназначена для отправки через sd message и length, по адресу dest_addr длины dest_len. Если для dest_addr игнорируется.
В число возможных составляющих значения аргумента flags входят следующие флаги.
MSG_EOR
Обозначает
MSG_OOB
Разумеется, успешное завершение функции sendto() не гарантирует доставку -1, указывает только на локально выявляемые ошибки.
Функция send() - пара для recv() со всеми следующими из этого обстоятельства
эквивалентностями и аналогиями.
Для функции sendmsg() структура типа msghdr, на которую указывает аргумент message, является входной (что показывает спецификатор const ). В поле msg_name задается целевой адрес. Поле msg_flags игнорируется.
Для shutdown()
(см. листинг 11.29).
#include <sys/socket.h> int shutdown (int sd, int how);
Значение аргумента how показывает, что именно завершается: SHUT_RD
прекращает SHUT_WR - SHUT_RDWR - и то, и другое.
Приводимые далее примеры строятся вокруг задачи копирования данных в распределенной программной
конфигурации со стандартного ввода одного процесса на стандартный вывод другого, удаленного.
Однако начнем мы с локального случая, обслуживаемого AF_UNIX
(см. листинг 11.30).
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа копирует строки со стандартного ввода на стандартный вывод, */
/* "прокачивая" их через пару сокетов. */
/* Используются функции ввода/вывода нижнего уровня */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/wait.h>
#define MY_PROMPT "Вводите строки\n"
#define MY_MSG "Вы ввели: "
int main (void) {
int sds [2];
char buf [1];
int new_line = 1; /* Признак того, что надо выдать сообщение MY_MSG */
/* перед отображением очередной строки */
/* Создадим пару соединенных безымянных сокетов */
if (socketpair (AF_UNIX, SOCK_STREAM, 0, sds) < 0) {
perror ("SOCKETPAIR");
exit (1);
}
switch (fork ()) {
case -1:
perror ("FORK");
exit (2);
case 0:
/* Чтение из сокета sds [0] и выдачу на стандартный вывод */
/* реализуем в порожденном процессе */
while (read (sds [0], buf, 1) == 1) {
if (write (STDOUT_FILENO, buf, 1) != 1) {
perror ("WRITE TO STDOUT");
break;
}
}
exit (0);
}
/* Чтение со стандартного ввода и запись в сокет sds [1] */
/* возложим на родительский процесс */
if (write (sds [1], MY_PROMPT, sizeof (MY_PROMPT) - 1) !=
sizeof (MY_PROMPT) - 1) {
perror ("WRITE TO SOCKET-1");
}
while (read (STDIN_FILENO, buf, 1) == 1) {
/* Перед отображением очередной строки */
/* нужно выдать сообщение MY_MSG */
if (new_line) {
if (write (sds [1], MY_MSG, sizeof (MY_MSG) - 1) != sizeof (MY_MSG) - 1) {
perror ("WRITE TO SOCKET-2");
break;
}
}
if (write (sds [1], buf, 1) != 1) {
perror ("WRITE TO SOCKET-3");
break;
}
new_line = (buf [0] == '\n');
}
shutdown (sds [1], SHUT_WR);
(void) wait (NULL);
return (0);
}
Обратим внимание на употребление в процессе, осуществляющем запись в shutdown(),
без чего читающий процесс не определит конец файла и не выйдет из цикла.
Решим теперь ту же задачу для действительно распределенной конфигурации. Наше приложение будет состоять
из двух процессов, выполняющихся на разных хостах. Первый процесс (см. листинг 11.31) читает строки со
стандартного ввода и отправляет их в виде
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа процесса, читающего строки со стандартного ввода */
/* и посылающего их в виде датаграмм другому процессу */
/* (будем называть его сервером), */
/* который должно выдать их на свой стандартный вывод. */
/* Имя серверного хоста - аргумент командной строки */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h>
#include <netdb.h>
#include <sys/socket.h>
#include <string.h>
#define MY_PROMPT "Вводите строки\n"
int main (int argc, char *argv []) {
int sd; /* Дескриптор передающего сокета */
char line [LINE_MAX]; /* Буфер для копируемых строк */
/* Структура - аргумент sendmsg */
struct msghdr msg = {NULL, 0, NULL, 0, NULL, 0, 0};
struct iovec iovbuf; /* Структура для сборки отправляемых данных */
/* Структура - входной аргумент getaddrinfo */
struct addrinfo hints = {0, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
0, NULL, NULL, NULL};
/* Указатель - выходной аргумент getaddrinfo */
struct addrinfo *addr_res;
int res; /* Результат getaddrinfo */
int msg_len; /* Длина очередной введенной строки, */
/* включая завершающий нулевой байт */
if (argc != 2) {
fprintf (stderr, "Использование: %s имя_серверного_хоста\n", argv [0]);
return (1);
}
/* Создадим сокет, через который будем отправлять */
/* прочитанные строки */
if ((sd = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
perror ("SOCKET");
return (2);
}
/* Выясним целевой адрес для датаграмм */
/* Воспользуемся портом для сервиса spooler */
if ((res = getaddrinfo (argv [1], "spooler", hints, addr_res)) != 0) {
fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res));
return (3);
}
/* Заполним структуру msghdr */
msg.msg_name = addr_res->ai_addr;
msg.msg_namelen = addr_res->ai_addrlen;
msg.msg_iov = iovbuf;
msg.msg_iovlen = 1;
iovbuf.iov_base = line;
/* Цикл чтения строк со стандартного ввода */
/* и отправки их через сокет в виде датаграмм */
fputs (MY_PROMPT, stdout);
while (fgets (line, sizeof (line), stdin) != NULL) {
msg_len = strlen (line) + 1;
iovbuf.iov_len = msg_len;
if (sendmsg (sd, msg, 0) != msg_len) {
perror ("SENDMSG");
break;
}
}
return (0);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа процесса (будем называть его сервером), */
/* читающего сообщения (строки) из датаграммного сокета */
/* и выдающего их на стандартный вывод */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h>
#include <netdb.h>
#include <sys/socket.h>
#define MY_MSG "Вы ввели: "
int main (void) {
int sd; /* Дескриптор приемного сокета */
char line [LINE_MAX]; /* Буфер для копируемых строк */
/* Структура - аргумент recvmsg */
struct msghdr msg = {NULL, 0, NULL, 0, NULL, 0, 0};
struct iovec iovbuf; /* Структура для разнесения принимаемых данных */
/* Структура - входной аргумент getaddrinfo */
struct addrinfo hints = {AI_PASSIVE, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
0, NULL, NULL, NULL};
/* Указатель - выходной аргумент getaddrinfo */
struct addrinfo *addr_res;
int res; /* Результат getaddrinfo */
/* Создадим сокет, через который будем принимать */
/* прочитанные строки */
if ((sd = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
perror ("SOCKET");
return (1);
}
/* Привяжем этот сокет к адресу сервиса spooler на локальном хосте */
if ((res = getaddrinfo (NULL, "spooler", hints, addr_res)) != 0) {
fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res));
return (2);
}
if (bind (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) {
perror ("BIND");
return (3);
}
/* Можно освободить память, которую запрашивала функция getaddrinfo() */
freeaddrinfo (addr_res);
/* Заполним структуру msghdr */
msg.msg_iov = iovbuf;
msg.msg_iovlen = 1;
iovbuf.iov_base = line;
iovbuf.iov_len = sizeof (line);
/* Цикл приема и выдачи строк */
while (1) {
if (recvmsg (sd, msg, 0) < 0) {
perror ("RECVMSG");
break;
}
fputs (MY_MSG, stdout);
fputs (line, stdout);
}
return (0);
}
Обратим внимание на несколько моментов. getaddrinfo() с " в качестве аргумента. (Если на
хосте этот
Для успешного вызова функции bind() процесс должен обладать соответствующими привилегиями.
Это может оказаться существенным для запуска программы, показанной в листинге 11.32.
Чтобы получить от getaddrinfo() адрес, пригодный для использования в качестве аргумента
ориентированных на прием функций ( listen(), bind() для приемного AI_PASSIVE во входной для getaddrinfo() структуре addrinfo (аргумент hints в описании функции getaddrinfo() ).
В таком случае для IP-адреса будет выдано значение INADDR_ANY, которое трактуется в AF_INET как адрес 127.0.0.1 ), так и с реальным
Цикл
Для передающего
В программе, посылающей connect() и, для оправдания затраченных усилий,
изменим способ отправки: вместо универсальной sendmsg() задействуем даже не более
простую функцию send(), а ее вполне привычный аналог write(), скрытый под
личиной fputs().
Какой режим буферизации выбрать для потока, сформированного по открытому файловому дескриптору MSG_TRUNC,
чтобы убедиться, что при пересылке в виде
Модифицированные варианты программ представлены в листингах 11.33 и 11.34.
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа процесса, читающего строки со стандартного ввода */
/* и посылающего их в виде датаграмм другому процессу */
/* (будем называть его сервером), */
/* который должно выдать их на свой стандартный вывод. */
/* Имя серверного хоста - аргумент командной строки */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h>
#include <netdb.h>
#include <sys/socket.h>
#define MY_PROMPT "Вводите строки\n"
int main (int argc, char *argv []) {
int sd; /* Дескриптор передающего сокета */
FILE *ss; /* Поток, соответствующий передающему сокету */
char line [LINE_MAX]; /* Буфер для копируемых строк */
/* Структура - входной аргумент getaddrinfo */
struct addrinfo hints = {0, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
0, NULL, NULL, NULL};
/* Указатель - выходной аргумент getaddrinfo */
struct addrinfo *addr_res;
int res; /* Результат getaddrinfo */
if (argc != 2) {
fprintf (stderr, "Использование: %s имя_серверного_хоста\n", argv [0]);
return (1);
}
/* Создадим сокет, через который будем отправлять */
/* прочитанные строки */
if ((sd = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
perror ("SOCKET");
return (2);
}
/* Выясним целевой адрес для датаграмм */
/* Воспользуемся портом для сервиса spooler */
if ((res = getaddrinfo (argv [1], "spooler", hints, addr_res)) != 0) {
fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res));
return (3);
}
/* Воспользуемся функцией connect() для достижения двух целей: */
/* фиксации целевого адреса и привязки к некоему локальному */
if (connect (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) {
perror ("CONNECT");
return (4);
}
/* Сформирует поток по дескриптору сокета */
if ((ss = fdopen (sd, "w")) == NULL) {
perror ("FDOPEN");
return (5);
}
/* Отменим буферизацию для этого потока */
setbuf (ss, NULL);
/* Цикл чтения строк со стандартного ввода */
/* и отправки их через сокет в виде датаграмм */
fputs (MY_PROMPT, stdout);
while (fgets (line, sizeof (line), stdin) != NULL) {
fputs (line, ss);
}
return (0);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа процесса (будем называть его сервером), */
/* читающего сообщения из датаграммного сокета */
/* и копирующего их на стандартный вывод */
/* с указанием адреса, откуда они поступили */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h>
#include <netdb.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main (void) {
int sd; /* Дескриптор приемного сокета */
/* Буфер для принимаемых сообщений */
/* Оставлено место для вставки нулевого байта */
char lbuf [BUFSIZ + 1];
/* Структура - аргумент recvmsg */
struct msghdr msg = {NULL, 0, NULL, 0, NULL, 0, 0};
struct iovec iovbuf; /* Структура для разнесения принимаемых данных */
/* Структура - входной аргумент getaddrinfo */
struct addrinfo hints = {AI_PASSIVE, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
0, NULL, NULL, NULL};
/* Указатель - выходной аргумент getaddrinfo */
struct addrinfo *addr_res;
int res; /* Результат getaddrinfo */
/* Структура для исходного адреса датаграмм */
struct sockaddr_in sai;
ssize_t lmsg; /* Длина принятой датаграммы */
/* Создадим сокет, через который будем принимать */
/* прочитанные строки */
if ((sd = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
perror ("SOCKET");
return (1);
}
/* Привяжем этот сокет к адресу сервиса spooler на локальном хосте */
if ((res = getaddrinfo (NULL, "spooler", hints, addr_res)) != 0) {
fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res));
return (1);
}
if (bind (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) {
perror ("BIND");
return (2);
}
/* Можно освободить память, которую запрашивала функция getaddrinfo() */
freeaddrinfo (addr_res);
/* Заполним структуру msghdr */
msg.msg_name = sai;
msg.msg_namelen = sizeof (struct sockaddr_in);
msg.msg_iov = iovbuf;
msg.msg_iovlen = 1;
iovbuf.iov_base = lbuf;
/* Оставим место для вставки нулевого байта */
iovbuf.iov_len = sizeof (lbuf) - 1;
/* Цикл приема и выдачи строк */
while (1) {
if ((lmsg = recvmsg (sd, msg, 0)) < 0) {
perror ("RECVMSG");
break;
}
printf ("Вы ввели и отправили с адреса %s, порт %d :",
inet_ntoa (((struct sockaddr_in *) msg.msg_name)->sin_addr),
ntohs (((struct sockaddr_in *) msg.msg_name)->sin_port));
if (msg.msg_flags MSG_TRUNC) {
printf ("Датаграмма была урезана\n");
}
lbuf [lmsg] = 0;
fputs (lbuf, stdout);
}
return (0);
}
Читателю предлагается оценить степень надежности (точнее, ненадежности) доставки
Сделаем теперь более существенные изменения, прежде всего на серверной стороне. Перейдем на использование
надежного SOCK_STREAM ).
Сервер будет получать запросы на " и
порождать для каждого принятого запроса свой обслуживающий процесс.
Новый вариант программы процесса, читающего строки со стандартного ввода и отправляющего их через
потоковый gsce.
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа процесса, читающего строки со стандартного ввода */
/* и посылающего их в виде потока другому процессу */
/* (будем называть его сервером), */
/* который должно выдать строки на свой стандартный вывод. */
/* Имя серверного хоста - аргумент командной строки */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h>
#include <netdb.h>
#include <sys/socket.h>
#define MY_PROMPT "Вводите строки\n"
int main (int argc, char *argv []) {
int sd; /* Дескриптор передающего сокета */
FILE *ss; /* Поток, соответствующий передающему сокету */
char line [LINE_MAX]; /* Буфер для копируемых строк */
/* Структура - входной аргумент getaddrinfo */
struct addrinfo hints = {0, AF_INET, SOCK_STREAM, IPPROTO_TCP,
0, NULL, NULL, NULL};
/* Указатель - выходной аргумент getaddrinfo */
struct addrinfo *addr_res;
int res; /* Результат getaddrinfo */
if (argc != 2) {
fprintf (stderr, "Использование: %s имя_серверного_хоста\n", argv [0]);
return (1);
}
/* Создадим сокет, через который будем отправлять */
/* прочитанные строки */
if ((sd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
perror ("SOCKET");
return (2);
}
/* Выясним целевой адрес для соединения */
/* Воспользуемся портом для сервиса spooler */
if ((res = getaddrinfo (argv [1], "spooler", hints, addr_res)) != 0) {
fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res));
return (3);
}
/* Воспользуемся функцией connect() для достижения двух целей: */
/* установления соединения и привязки к некоему локальному адресу */
if (connect (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) {
perror ("CONNECT");
return (4);
}
/* Сформируем поток по дескриптору сокета */
if ((ss = fdopen (sd, "w")) == NULL) {
perror ("FDOPEN");
return (5);
}
/* Отменим буферизацию для этого потока */
setbuf (ss, NULL);
/* Цикл чтения строк со стандартного ввода */
/* и отправки их через сокет в виде потока */
fputs (MY_PROMPT, stdout);
while (fgets (line, sizeof (line), stdin) != NULL) {
fputs (line, ss);
}
shutdown (sd, SHUT_WR);
return (0);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа процесса (будем называть его демоном), */
/* принимающего запросы на установления соединения */
/* и запускающего процессы для их обслуживания */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/wait.h>
int main (void) {
int sd; /* Дескриптор слушающего сокета */
int ad; /* Дескриптор приемного сокета */
/* Буфер для принимаемых строк */
struct addrinfo hints = {AI_PASSIVE, AF_INET, SOCK_STREAM, IPPROTO_TCP,
0, NULL, NULL, NULL};
/* Указатель - выходной аргумент getaddrinfo */
struct addrinfo *addr_res;
int res; /* Результат getaddrinfo */
/* Создадим слушающий сокет */
if ((sd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
perror ("SOCKET");
return (1);
}
/* Привяжем этот сокет к адресу сервиса spooler на локальном хосте */
if ((res = getaddrinfo (NULL, "spooler", hints, addr_res)) != 0) {
fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res));
return (1);
}
if (bind (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) {
perror ("BIND");
return (2);
}
/* Можно освободить память, которую запрашивала функция getaddrinfo() */
freeaddrinfo (addr_res);
/* Пометим сокет как слушающий */
if (listen (sd, SOMAXCONN) < 0) {
perror ("LISTEN");
return (3);
}
/* Цикл приема соединений и запуска обслуживающих процессов */
while (1) {
/* Примем соединение. */
/* Адрес партнера по общению нас в данном случае не интересует */
if ((ad = accept (sd, NULL, NULL)) < 0) {
perror ("ACCEPT");
return (4);
}
/* Запустим обслуживающий процесс */
switch (fork ()) {
case -1:
perror ("FORK");
return (5);
case 0:
/* Сделаем сокет ad стандартным вводом */
(void) close (STDIN_FILENO);
(void) fcntl (ad, F_DUPFD, STDIN_FILENO);
(void) close (ad);
/* Сменим программу процесса на обслуживающую */
if (execl ("./gsce", "gsce", (char *) NULL) < 0) {
perror ("EXECL");
return (6);
}
}
/* В родительском процессе дескриптор принятого соединения нужно закрыть */
(void) close (ad);
/* Обслужим завершившиеся порожденные процессы */
while (waitpid ((pid_t) (-1), NULL, WNOHANG) > 0) ;
}
return (0);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа обслуживающего процесса, */
/* выдающего принимаемые строки на стандартный вывод. */
/* Дескриптор приемного сокета передан */
/* в качестве стандартного ввода */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h>
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main (void) {
char line [LINE_MAX]; /* Буфер для принимаемых строк */
/* Структура для записи адреса */
struct sockaddr_in sai;
/* Длина адреса */
socklen_t sai_len = sizeof (struct sockaddr_in);
/* Опросим адрес партнера по общению (передающего сокета) */
if (getpeername (STDIN_FILENO, (struct sockaddr *) sai, sai_len) < 0) {
perror ("GETPEERNAME");
return (1);
}
/* Цикл чтения строк из сокета */
/* и выдачи их на стандартный вывод */
while (fgets (line, sizeof (line), stdin) != NULL) {
printf ("Вы ввели и отправили с адреса %s, порт %d :",
inet_ntoa (sai.sin_addr), ntohs (sai.sin_port));
fputs (line, stdout);
}
/* Закрытие соединения */
shutdown (STDIN_FILENO, SHUT_RD);
return (0);
}
Обратим внимание на два технических момента. Во-первых, дескриптор приемного waitpid() со значением аргумента pid, равным -1
(выражающим готовность обслужить любой завершившийся процесс-потомок), и флагом WNOHANG,
означающим отсутствие ожидания. В данном случае подобный прием не гарантирует отсутствия
Чтобы проиллюстрировать еще один типичный прием, связанный с обработкой запросов на SIGCHLD и поместим в нее вызов waitpid().
Модифицированный вариант программы процесса-демона, аккуратно ликвидирующий все
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа процесса (будем называть его демоном), */
/* принимающего запросы на установления соединения */
/* и запускающего процессы для их обслуживания */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>
/* Функция обработки сигнала SIGCHLD */
static void chldied (int dummy) {
/* Вдруг число завершившихся потомков отлично от единицы? */
while (waitpid ((pid_t) (-1), NULL, WNOHANG) > 0) ;
}
int main (void) {
int sd; /* Дескриптор слушающего сокета */
int ad; /* Дескриптор приемного сокета */
/* Структура - входной аргумент getaddrinfo */
struct addrinfo hints = {AI_PASSIVE, AF_INET, SOCK_STREAM, IPPROTO_TCP,
0, NULL, NULL, NULL};
/* Указатель - выходной аргумент getaddrinfo */
struct addrinfo *addr_res;
int res; /* Результат getaddrinfo */
/* Структура для задания реакции на сигнал SIGCHLD */
struct sigaction sact;
/* Создадим слушающий сокет */
if ((sd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
perror ("SOCKET");
return (1);
}
/* Привяжем этот сокет к адресу сервиса spooler на локальном хосте */
if ((res = getaddrinfo (NULL, "spooler", hints, addr_res)) != 0) {
fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res));
return (1);
}
if (bind (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) {
perror ("BIND");
return (2);
}
/* Можно освободить память, которую запрашивала функция getaddrinfo() */
freeaddrinfo (addr_res);
/* Пометим сокет как слушающий */
if (listen (sd, SOMAXCONN) < 0) {
perror ("LISTEN");
return (3);
}
/* Установим обработку сигнала о завершении потомка */
sact.sa_handler = chldied;
(void) sigemptyset (sact.sa_mask);
sact.sa_flags = 0;
(void) sigaction (SIGCHLD, sact, (struct sigaction *) NULL);
/* Цикл приема соединений и запуска обслуживающих процессов */
while (1) {
/* Примем соединение с учетом того, что ожидание может быть прервано */
/* доставкой обрабатываемого сигнала. */
/* Адрес партнера по общению в данном случае нас не интересует */
while ((ad = accept (sd, NULL, NULL)) < 0) {
if (errno != EINTR) {
perror ("ACCEPT");
return (4);
}
}
/* Запустим обслуживающий процесс */
switch (fork ()) {
case -1:
perror ("FORK");
return (5);
case 0:
/* Сделаем сокет ad стандартным вводом */
(void) close (STDIN_FILENO);
(void) fcntl (ad, F_DUPFD, STDIN_FILENO);
(void) close (ad);
/* Сменим программу процесса на обслуживающую */
if (execl ("./gsce", "gsce", (char *) NULL) < 0) {
perror ("EXECL");
return (6);
}
}
/* В родительском процессе дескриптор принятого соединения нужно закрыть */
(void) close (ad);
}
return (0);
}
Задание accept(): ее вызов может быть прерван доставкой сигнала SIGCHLD.
В такой ситуации прежний вариант процесса-демона завершился бы, выдав в стандартный "ACCEPT: Interrupted . Чтобы этого не случилось,
вызов accept() пришлось заключить в цикл с проверкой (в случае неудачного завершения)
значения переменной errno на совпадение с EINTR. Вообще говоря, по подобной
схеме рекомендуется действовать для всех функций, выполнение которых может быть прервано доставкой
сигнала, но допускающих и повторный вызов. Отметим в этой связи, что, например, функция connect() очень похожа, но все-таки отличается своими особенностями. После доставки
сигнала ее вызов завершается неудачей, а errno, равным EALREADY.
Рассмотрим теперь еще один вариант сервера, имеющего однопроцессную организацию, но select() (см. листинг 11.39).
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа процесса (будем называть его сервером), */
/* принимающего запросы на установления соединения */
/* и обслуживающего их с использованием select() */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h>
#include <stdio.h>
#include <netdb.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/select.h>
/* Структура для хранения дескрипторов приемных сокетов */
/* и ассоциированной информации */
#define MY_MSG_LN 128
struct sads {
int ad;
FILE *sd;
char my_msg [MY_MSG_LN];
};
/* Максимальное число параллельно обслуживаемых запросов */
#define MY_MAX_QS FD_SETSIZE
/* Функция для освобождения элемента массива */
/* дескрипторов приемных сокетов */
static void free_elem (struct sads *sadsp) {
shutdown (sadsp->ad, SHUT_RD);
close (sadsp->ad);
sadsp->ad = -1;
}
int main (void) {
int sd; /* Дескриптор слушающего сокета */
/* Структура - входной аргумент getaddrinfo */
struct addrinfo hints = {AI_PASSIVE, AF_INET, SOCK_STREAM, IPPROTO_TCP,
0, NULL, NULL, NULL};
/* Указатель - выходной аргумент getaddrinfo */
struct addrinfo *addr_res;
int res; /* Результат getaddrinfo */
fd_set rdset; /* Набор дескрипторов для чтения */
/* Структура для записи адреса */
struct sockaddr_in sai;
socklen_t sai_len; /* Длина адреса */
char line [LINE_MAX]; /* Буфер для принимаемых строк */
/* Массов для хранения дескрипторов приемных сокетов */
/* и ассоциированной информации. */
/* Последний элемент - фиктивный, */
/* нужен для упрощения поиска свободного */
struct sads sadsarr [MY_MAX_QS + 1];
int sads_max; /* Верхняя граница дескрипторов, проверяемых select() */
int i;
/* Создадим слушающий сокет */
if ((sd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
perror ("SOCKET");
return (1);
}
/* Привяжем этот сокет к адресу сервиса spooler на локальном хосте */
if ((res = getaddrinfo (NULL, "spooler", hints, addr_res)) != 0) {
fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res));
return (1);
}
if (bind (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) {
perror ("BIND");
return (2);
}
/* Можно освободить память, которую запрашивала функция getaddrinfo() */
freeaddrinfo (addr_res);
/* Пометим сокет как слушающий */
if (listen (sd, SOMAXCONN) < 0) {
perror ("LISTEN");
return (3);
}
/* Инициализируем массив sadsarr. */
/* -1 в поле ad означает, что элемент свободен */
for (i = 0; i < (MY_MAX_QS + 1); i++) {
sadsarr [i].ad = -1;
}
/* Цикл приема соединений и обслуживания запросов */
while (1) {
/* Подготовим наборы дескрипторов для select() */
FD_ZERO (rdset);
FD_SET (sd, rdset);
sads_max = sd + 1;
for (i = 0; i < MY_MAX_QS; i++) {
if (sadsarr [i].ad >= 0) {
FD_SET (sadsarr [i].ad, rdset);
if ((sadsarr [i].ad + 1) > sads_max) {
sads_max = sadsarr [i].ad + 1;
}
}
}
/* Подождем запроса на установление соединения */
/* или готовности данных для чтения. */
/* Время ожидания зададим как бесконечное */
if (select (sads_max, rdset, NULL, NULL, NULL) == -1) {
perror ("PSELECT");
return (4);
}
/* Посмотрим, есть ли запросы, ждущие установления соединения */
if (FD_ISSET (sd, rdset)) {
/* Примем запрос на установление соединения, */
/* если есть свободный элемент массива дескрипторов. */
/* Последний элемент считаем фиктивным; он всегда свободен */
i = -1;
while (sadsarr [++i].ad >= 0) ;
if (i < MY_MAX_QS) {
/* Свободный элемент нашелся */
sai_len = sizeof (struct sockaddr_in);
if ((sadsarr [i].ad = accept (sd, (struct sockaddr *) sai,
sai_len)) == -1) {
perror ("ACCEPT");
continue;
}
/* Сформируем сообщение, выдаваемое перед принятой строкой */
(void) sprintf (sadsarr [i].my_msg,
"Вы ввели и отправили с адреса %s, порт %d :",
inet_ntoa (sai.sin_addr), ntohs (sai.sin_port));
/* Сформируем поток по дескриптору сокета */
/* и отменим буферизацию для этого потока */
if ((sadsarr [i].sd = fdopen (sadsarr [i].ad, "r")) == NULL) {
perror ("FDOPEN");
free_elem (sadsarr [i]);
continue;
}
setbuf (sadsarr [i].sd, NULL);
}
}
/* Посмотрим, есть ли данные, готовые для чтения */
for (i = 0; i < MY_MAX_QS; i++) {
if ((sadsarr [i].ad >= 0) FD_ISSET (sadsarr [i].ad, rdset)) {
/* Есть данные, готовые для чтения, */
/* или установлен признак конца файла */
if (fgets (line, sizeof (line), sadsarr [i].sd) != NULL) {
/* Выведем полученную строку */
fputs (sadsarr [i].my_msg, stdout);
fputs (line, stdout);
} else {
/* Отправитель закрыл соединение. */
/* Закроем его и мы */
fclose (sadsarr [i].sd);
free_elem (sadsarr [i]);
}
}
}
}
return (0);
}
Обратим внимание на то, что функция select() позволяет select()
сообщает о наличии данных для чтения, а сама операция чтения возвращает
В качестве следующего примера рассмотрим передачу целочисленных данных. Главная проблема здесь -
аккуратное выполнение преобразований между
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа вычисляет несколько первых строк треугольника Паскаля */
/* и отправляет их через сокет сервису spooler. */
/* Имя целевого хоста - аргумент командной строки. */
/* Передаваемые данные снабжаются однобайтными маркерами типа */
/* и кратности и преобразуются к сетевому порядку байт */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h>
#include <stdio.h>
#include <netdb.h>
#include <sys/socket.h>
/* Количество вычисляемых строк треугольника Паскаля */
#define T_SIZE 16
/* Маркеры типов передаваемых данных */
#define T_UCHAR 1
#define T_UINT16 2
#define T_UINT32 4
#define T_HDR "\nТреугольник Паскаля:"
int main (int argc, char *argv []) {
uint32_t tp [T_SIZE]; /* Массив для хранения текущей строки треугольника */
unsigned char mtl; /* Переменная для хранения маркеров типа и кратности */
uint32_t ntelem; /* Текущий элемент строки в сетевом порядке байт */
int sd; /* Дескриптор передающего сокета */
/* Структура - входной аргумент getaddrinfo */
struct addrinfo hints = {0, AF_INET, SOCK_STREAM, IPPROTO_TCP,
0, NULL, NULL, NULL};
/* Указатель - выходной аргумент getaddrinfo */
struct addrinfo *addr_res;
int res; /* Результат getaddrinfo */
int i, j;
if (argc != 2) {
fprintf (stderr, "Использование: %s имя_серверного_хоста\n", argv [0]);
return (1);
}
/* Создадим передающий сокет и установим соединение */
if ((sd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
perror ("SOCKET");
return (2);
}
if ((res = getaddrinfo (argv [1], "spooler", hints, addr_res)) != 0) {
fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res));
return (3);
}
if (connect (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) {
perror ("CONNECT");
return (4);
}
/* Инициализируем массив для хранения текущей строки треугольника Паскаля, */
/* чтобы далее все элементы можно было считать и передавать единообразно */
tp [0] = 1;
for (i = 1; i < T_SIZE; i++) {
tp [i] = 0;
}
/* Передадим заголовок */
mtl = T_UCHAR;
if (write (sd, mtl, 1) != 1) {
perror ("WRITE-1");
return (5);
}
mtl = sizeof (T_HDR) - 1;
if (write (sd, mtl, 1) != 1) {
perror ("WRITE-2");
return (6);
}
if (write (sd, T_HDR, mtl) != mtl) {
perror ("WRITE-3");
return (7);
}
/* Вычислим и передадим строки треугольника Паскаля */
for (i = 0; i < T_SIZE; i++) {
/* Элементы очередной строки нужно считать от конца к началу */
/* Элемент tp [0] пересчитывать не нужно */
for (j = i; j > 0; j--) {
tp [j] += tp [j - 1];
}
/* Вывод строки треугольника в сокет */
mtl = T_UINT32;
if (write (sd, mtl, 1) != 1) {
perror ("WRITE-4");
return (8);
}
mtl = i + 1;
if (write (sd, mtl, 1) != 1) {
perror ("WRITE-5");
return (9);
}
/* Преобразование элементов строки к сетевому порядку байт и вывод */
for (j = 0; j <= i; j++) {
ntelem = htonl (tp [j]);
if (write (sd, ntelem, sizeof (ntelem)) != sizeof (ntelem)) {
perror ("WRITE-6");
return (10);
}
}
}
shutdown (sd, SHUT_WR);
return (close (sd));
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа процесса (будем называть его наивным сервером), */
/* принимающего запросы на установления соединения */
/* и осуществляющего вывод поступающих числовых данных */
/* без порождения процессов и мультиплексирования */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h>
#include <stdio.h>
#include <netdb.h>
#include <sys/socket.h>
#include <arpa/inet.h>
/* Маркеры типов передаваемых данных */
#define T_UCHAR 1
#define T_UINT16 2
#define T_UINT32 4
int main (void) {
int sd; /* Дескриптор слушающего сокета */
int ad; /* Дескриптор приемного сокета */
/* Структура - входной аргумент getaddrinfo */
struct addrinfo hints = {AI_PASSIVE, AF_INET, SOCK_STREAM, IPPROTO_TCP,
0, NULL, NULL, NULL};
/* Указатель - выходной аргумент getaddrinfo */
struct addrinfo *addr_res;
int res; /* Результат getaddrinfo */
/* Структура для записи адреса */
struct sockaddr_in sai;
socklen_t sai_len; /* Длина адреса */
char mt; /* Маркер типа поступающих данных */
char ml; /* Маркер кратности поступающих данных */
char bufc; /* Буфер для приема символьных данных */
uint16_t buf16; /* Буфер для приема 16-разрядных целых */
uint32_t buf32; /* Буфер для приема 16-разрядных целых */
/* Создадим слушающий сокет */
if ((sd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
perror ("SOCKET");
return (1);
}
/* Привяжем этот сокет к адресу сервиса spooler на локальном хосте */
if ((res = getaddrinfo (NULL, "spooler", hints, addr_res)) != 0) {
fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res));
return (1);
}
if (bind (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) {
perror ("BIND");
return (2);
}
/* Можно освободить память, которую запрашивала функция getaddrinfo() */
freeaddrinfo (addr_res);
/* Пометим сокет как слушающий */
if (listen (sd, SOMAXCONN) < 0) {
perror ("LISTEN");
return (3);
}
/* Цикл приема соединений и обслуживания запросов на вывод */
while (1) {
/* Примем соединение */
sai_len = sizeof (struct sockaddr_in);
if ((ad = accept (sd, (struct sockaddr *) sai, sai_len)) < 0) {
perror ("ACCEPT");
continue;
}
/* Цикл приема поступающих данных и их вывода */
printf ("Получено с адреса %s, порт %d :",
inet_ntoa (sai.sin_addr), ntohs (sai.sin_port));
while (read (ad, mt, 1) == 1) {
/* Есть очередная порция данных, начинающаяся с типа. */
/* Прочитаем кратность, потом сами данные */
if (read (ad, ml, 1) != 1) {
perror ("READ-1");
return (4);
}
while (ml-- > 0) {
switch (mt) {
case T_UCHAR:
if (read (ad, bufc, sizeof (bufc)) != sizeof (bufc)) {
perror ("READ-2");
return (5);
}
printf ("%c", bufc);
continue;
case T_UINT16:
if (read (ad, buf16, sizeof (buf16)) != sizeof (buf16)) {
perror ("READ-3");
return (6);
}
printf (" %d", ntohs (buf16));
continue;
case T_UINT32:
if (read (ad, buf32, sizeof (buf32)) != sizeof (buf32)) {
perror ("READ-4");
return (7);
}
printf (" %d", ntohl (buf32));
continue;
}
}
/* Вывод порции завершим переводом строки */
printf ("\n");
}
/* Конец обслуживания соединения */
(void) close (ad);
}
return (0);
}
Стандарт POSIX-2001 определяет
Данные о хостах как узлах
Обычно
Поддерживается
Данные передаются по
Для преобразования значений типов uint16_t и uint32_t из htons() и htonl() ; функции ntohs() и ntohl() осуществляют обратную операцию.
При взаимодействии процессов
Основные описания, относящиеся к <sys/socket.h>. Фигурирует в нем и упомянутая выше структура sockaddr для
sa_family_t sa_family; /* Адресное семейство */ char sa_data []; /* Адрес сокета (данные переменной длины) */
AF_UNIX
<sys/un.h>.
AF_INET
IPv4. Специфичные
для него описания располагаются в заголовочном файле <netinet/in.h>.
AF_INET6
Взаимодействие по IPv6 (необязательная возможность). За счет поддержки
адресов IPv6, отображенных на IPv4, обеспечивается совместимость с
приложениями, использующими IPv4. Применяемые эти <netinet/in.h>, < и <netdb.h>.
В пределах каждого
SOCK_STREAM
SOCK_SEQPACKET
Аналог SOCK_STREAM с дополнительным сохранением границ между записями.
SOCK_DGRAM
Передача данных в виде
SOCK_RAW
(Необязательная возможность). Аналог SOCK_DGRAM с дополнительной возможностью
доступа к протокольным заголовкам и другой информации нижнего уровня. Создавать
Для каждого AF_INET для SOCK_STREAM
подразумеваемым является IPPROTO_TCP, а для SOCK_DGRAM - IPPROTO_UDP ; посредством "прозрачных" SOCK_RAW ) можно
воспользоваться ICMP, задав имя IPPROTO_ICMP, и т.д.
Общая логика работы с socket(), которой в качестве аргументов передают bind() listen(), его следует пометить как готового принимать
соединения. Реальный accept(), создающая для
каждого из них новый connect(). (В connect() позволяет
специфицировать адрес отправляемых через
Для read() или специализированным семейством функций recv*(), а для передачи - функцией write() или семейством send*().
Кроме того, функции select() и/или poll() помогут проверить наличие
данных для
Обращение к функции shutdown() завершает взаимодействие между партнерами.
Данные о хостах как узлах sethostent(), gethostent() и endhostent() (см. листинг 11.1).
#include <netdb.h> void sethostent (int stayopen); struct hostent *gethostent (void); void endhostent (void);
Функция sethostent() устанавливает gethostent(), если значение аргумента stayopen отлично от нуля.
Функция gethostent() последовательно читает элементы базы, возвращая результат
в структуре типа hostent, содержащей по крайней мере следующие поля.
char *h_name; /* Официальное имя хоста */ char **h_aliases; /* Массив указателей на альтернативные */ /* имена хоста, завершаемый пустым */ /* указателем */ int h_addrtype; /* Тип адреса хоста */ int h_length; /* Длина в байтах адреса данного типа */ char **h_addr_list; /* Массив указателей на сетевые адреса */ /* хоста, завершаемый пустым указателем */
Функция endhostent() закрывает
В листинге 11.2 показана программа, осуществляющая последовательный просмотр сетевой
#include <stdio.h>
#include <netdb.h>
int main (void) {
struct hostent *pht;
char *pct;
int i, j;
sethostent (1);
while ((pht = gethostent ()) != NULL) {
printf ("Официальное имя хоста: %s\n", pht->h_name);
printf ("Альтернативные имена:\n");
for (i = 0; (pct = pht->h_aliases [i]) != NULL; i++) {
printf (" %s\n", pct);
}
printf ("Тип адреса хоста: %d\n", pht->h_addrtype);
printf ("Длина адреса хоста: %d\n", pht->h_length);
printf ("Сетевые адреса хоста:\n");
for (i = 0; (pct = pht->h_addr_list [i]) != NULL; i++) {
for (j = 0; j < pht->h_length; j++) {
printf (" %d", (unsigned char) pct [j]);
}
printf ("\n");
}
}
endhostent ();
return 0;
}
Официальное имя хоста: localhost Альтернативные имена: Тип адреса хоста: 2 Длина адреса хоста: 4 Сетевые адреса хоста: 127 0 0 1 . . . Официальное имя хоста: t01 Альтернативные имена: niisi.msk.ru t01.niisi.msk.ru mail mailhost loghost server server3 server3.systud.msk.su www www.systud.msk.su t01.systud.msk.su Тип адреса хоста: 2 Длина адреса хоста: 4 Сетевые адреса хоста: 193 232 173 1 . . . Официальное имя хоста: t17 Альтернативные имена: galatenko Тип адреса хоста: 2 Длина адреса хоста: 4 Сетевые адреса хоста: 193 232 173 17 . . .
К рассматриваемой базе возможен и случайный доступ по ключам - именам и адресам
хостов с помощью функций gethostbyname() и gethostbyaddr(), однако они считаются
устаревшими и из новой версии стандарта POSIX могут быть исключены. Вместо них
предлагается использовать функции getnameinfo() и getaddrinfo() (см. листинг 11.4).
#include <sys/socket.h>
#include <netdb.h>
void freeaddrinfo (struct addrinfo *ai);
int getaddrinfo
(const char *restrict nodename,
const char *restrict servname,
const struct addrinfo *restrict hints,
struct addrinfo **restrict res);
int getnameinfo
(const struct sockaddr *restrict sa,
socklen_t salen, char *restrict node,
socklen_t nodelen, char *restrict service,
socklen_t servicelen, int flags);
Функция getaddrinfo() позволяет по имени nodename ) и/или имени сетевого servname )
получить набор
Если аргумент nodename отличен от пустого указателя, он способен задавать описательное
имя или адресную цепочку. Для AF_INET и AF_UNSPEC
(см. ниже описание аргумента hints ) именем может служить имя хоста,
а адресной цепочкой - стандартные для Internet адреса в точечных
обозначениях (например, 193.232.173.17 ).
При пустом значении nodename подразумевается хост, локальный для вызывающего процесса.
Аргумент servname может задавать имя AF_INET и AF_UNSPEC ) десятичный servname
означает запрос
Аргумент hints позволяет передать дополнительную информацию об опрашиваемом addrinfo,
описанная в заголовочном файле <netdb.h>, должна содержать по крайней мере следующие поля.
int ai_flags; /* Входные флаги */ int ai_family; /* Адресное семейство сокета */ int ai_socktype; /* Тип сокета */ int ai_protocol; /* Протокол сокета */ socklen_t ai_addrlen; /* Длина адреса сокета */ struct sockaddr *ai_addr; /* Адрес сокета */ char *ai_canonname; /* Официальное имя узла сети */ struct addrinfo *ai_next; /* Указатель на следующий элемент списка */
При обращении к функции getaddrinfo() все поля структуры addrinfo,
на которую указывает аргумент hints, кроме первых четырех
( ai_flags, ai_family, ai_socktype, ai_protocol ),
должны быть нулевыми или равными NULL. Значение AF_UNSPEC
в поле ai_family подразумевает, что вызывающего устроит любое ai_socktype и ai_protocol. При hints, равном NULL,
подразумевается AF_UNSPEC для ai_family и нулевые значения для других полей.
Из флагов, которые могут быть установлены в поле ai_flags, упомянем следующие.
AI_PASSIVE
Если значение аргумента nodename равно NULL,
этот флаг игнорируется. В противном случае, если он указан, будет возвращен
AI_CANONNAME
Данный флаг предписывает выяснить официальное имя
AI_NUMERICHOST
Флаг означает, что хост задан адресной цепочкой, и не допускает использования
какого-либо
AI_NUMERICSERV
Флаг помечает, что servname )
задан
Признаком успешного завершения функции getaddrinfo() является нулевой результат.
В таком случае res будет ссылаться на указатель на список
структур типа addrinfo (связанных полем ai_next ) - принадлежащие им значения
полей ai_family, ai_socktype, ai_protocol пригодны для socket(), а значения ai_addr и ai_addrlen, в зависимости
от флага AI_PASSIVE, могут служить аргументами функций connect() или bind(),
применяемых к созданному
Функция freeaddrinfo() позволяет освободить память, занятую списком структур типа addrinfo и ассоциированными данными.
Функцию getnameinfo() можно считать обратной по отношению к getaddrinfo().
Аргумент sa - входной, он задает транслируемый salen - размер структуры sockaddr ). Аргументы node и service - выходные, задающие
адреса областей памяти, куда помещаются, соответственно, имя узла и nodelen и servicelen.
По умолчанию предполагается, что SOCK_STREAM, а кроме того, возвращается
полное доменное имя хоста. Аргумент flags позволяет изменить подразумеваемое поведение.
Если задан флаг NI_DGRAM, NI_NOFQDN возвращается короткое имя узла. Флаги NI_NUMERICHOST и NI_NUMERICSERV предписывают возвращать числовые цепочки для адресов хоста и
Обратим внимание на несколько технических деталей. При работе с sockaddr обычно используются более специализированные (описанные в заголовочном
файле <netinet/in.h> ) - sockaddr_in для AF_INET
( IPv4 ) и sockaddr_in6 для AF_INET6 ( IPv6 ).
Первая из них, согласно стандарту POSIX-2001, должна содержать по крайней мере следующие поля
(все с
sa_family_t sin_family; /* AF_INET */ in_port_t sin_port; /* Номер порта */ struct in_addr sin_addr; /* IP-адрес */
Структура типа sockaddr_in6 устроена несколько сложнее; мы не будем ее рассматривать.
Структуры типов sockaddr и sockaddr_in (или sockaddr_in6 )
мысленно накладываются друг на друга, а преобразование типов между ними выполняется по мере
необходимости. Отметим также, что тип in_port_t эквивалентен uint16_t,
структура типа in_addr содержит по крайней мере одно поле:
in_addr_t s_addr; тип in_addr_t эквивалентен uint32_t.
Техническую роль играют и функции преобразования IP-адресов из текстового представления в числовое и наоборот (см. листинг 11.5).
#include <arpa/inet.h>
in_addr_t inet_addr (const char *cp);
char *inet_ntoa (struct in_addr in);
int inet_pton (int af, const char
*restrict src, void *restrict dst);
const char *inet_ntop (int af, const void
*restrict src, char *restrict dst,
socklen_t size);
Первые две функции манипулируют только адресами IPv4: inet_addr() преобразует
текстовую цепочку cp (адрес в стандартных точечных обозначениях) в пригодное
для использования в качестве IP-адреса целочисленное значение, inet_ntoa()
выполняет обратное преобразование.
Вторая IPv6. Первый аргумент этих
функций, af, задает AF_INET для IPv4
и AF_INET6 для IPv6. Буфер, на который указывает аргумент
функции inet_pton() (в него помещается результат преобразования - IP-адрес в числовой
двоичной форме с IPv4
и 128 бит для IPv6.
Аргумент src функции inet_ntop(), возвращающей текстовое представление,
указывает на буфер с IP-адресом в числовой форме с size
задает длину выходного буфера, на него указывает аргумент . Подходящими значениями,
в зависимости от INET_ADDRSTRLEN или INET6_ADDRSTRLEN.
Выше было отмечено, что преобразование значений типов uint16_t и uint32_t
из htons() и htonl() ; функции ntohs() и ntohl() осуществляют обратную
операцию (см. листинг 11.6).
#include <arpa/inet.h> uint32_t htonl (uint32_t hostlong); uint16_t htons (uint16_t hostshort); uint32_t ntohl (uint32_t netlong); uint16_t ntohs (uint16_t netshort);
Использование функции getaddrinfo() вместе с сопутствующими техническими
деталями проиллюстрируем программой, показанной в листинге 11.7. Возможные результаты ее
выполнения приведены в листинге 11.8.
#include <stdio.h>
#include <netdb.h>
#include <arpa/inet.h>
int main (void) {
struct addrinfo hints = {AI_CANONNAME, AF_INET, SOCK_STREAM, IPPROTO_TCP,
0, NULL, NULL, NULL};
struct addrinfo *addr_res;
if (getaddrinfo ("www", "http", hints, addr_res) != 0) {
perror ("GETADDRINFO");
} else {
printf ("Результаты для сервиса http\n");
/* Пройдем по списку возвращенных структур */
do {
printf ("Адрес сокета: Порт: %d IP-адрес: %s\n",
ntohs (((struct sockaddr_in *) addr_res->ai_addr)->sin_port),
inet_ntoa (((struct sockaddr_in *) addr_res->ai_addr)->sin_addr));
printf ("Официальное имя хоста: %s\n", addr_res->ai_canonname);
} while ((addr_res = addr_res->ai_next) != NULL);
}
return 0;
}
Результаты для сервиса http Адрес сокета: Порт: 80 IP-адрес: 193.232.173.1 Официальное имя хоста: t01
Завершая изложение серии технических моментов, укажем, что полезным дополнением к функциям getaddrinfo() и getnameinfo() является функция gai_strerror()
(см. листинг 11.9). Она возвращает текстовую цепочку, расшифровывающую коды ошибок, перечисленные
в заголовочном файле <netdb.h>. Стандарт POSIX-2001 специфицирует следующие коды
ошибок, имена которых говорят сами за себя: EAI_AGAIN, EAI_BADFLAGS, EAI_FAIL, EAI_FAMILY, EAI_MEMORY, EAI_NONAME, EAI_OVERFLOW, EAI_SERVICE, EAI_SOCKTYPE, EAI_SYSTEM.
#include <netdb.h> const char *gai_strerror (int ecode);
Если немного модифицировать приведенную выше программу (в листинге 11.10 показан измененный
фрагмент, где имя "HTTP" - задано большими буквами), то в стандартный gai_strerror() будет выдано содержательное диагностическое perror() в данном случае
невразумительна.
. . .
int res;
if ((res = getaddrinfo ("www", "HTTP", hints, addr_res)) != 0) {
perror ("GETADDRINFO");
fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res));
} else {
printf ("Результаты для сервиса HTTP\n");
. . .
GETADDRINFO: No such file or directory GETADDRINFO: Servname not supported for ai_socktype
Наряду с базой данных хостов (
#include <netdb.h>
void setnetent (int stayopen);
struct netent *getnetent (void);
struct netent *getnetbyaddr (uint32_t net,
int type);
struct netent *getnetbyname (const char *name);
void endnetent (void);
Функция getnetent() обслуживает последовательный доступ к базе, getnetbyaddr()
осуществляет поиск по type ) и номеру net getnetbyname() выбирает netent,
указатель на которую возвращается в качестве результата этих функций, согласно стандарту POSIX-2001, должна
содержать по крайней мере следующие поля.
char *n_name; /* Официальное имя сети */ char **n_aliases; /* Массив указателей на альтернативные */ /* имена сети, завершаемый пустым указателем */ int n_addrtype; /* Адресное семейство (тип адресов) сети */ uint32_t n_net; /* Номер сети (в хостовом порядке байт) */
Точно такой же программный интерфейс предоставляет
#include <netdb.h>
void setprotoent (int stayopen);
struct protoent *getprotoent (void);
struct protoent *getprotobyname
(const char *name);
struct protoent *getprotobynumber (int proto);
void endprotoent (void);
Структура типа protoent содержит по крайней мере следующие поля.
char *p_name; /* Официальное имя протокола */ char **p_aliases; /* Массив указателей на альтернативные */ /* имена протокола, завершаемый пустым */ /* указателем */ int p_proto; /* Номер протокола */
В листинге 11.14 показан пример программы, осуществляющей последовательный и случайный доступ
к
#include <stdio.h>
#include <netdb.h>
int main (void) {
struct protoent *pht;
char *pct;
int i;
setprotoent (1);
while ((pht = getprotoent ()) != NULL) {
printf ("Официальное имя протокола: %s\n", pht->p_name);
printf ("Альтернативные имена:\n");
for (i = 0; (pct = pht->p_aliases [i]) != NULL; i++) {
printf (" %s\n", pct);
}
printf ("Номер протокола: %d\n\n", pht->p_proto);
}
if ((pht = getprotobyname ("ipv6")) != NULL) {
printf ("Номер протокола ipv6: %d\n\n", pht->p_proto);
} else {
fprintf (stderr, "Протокол ip в базе не найден\n");
}
if ((pht = getprotobyname ("IPV6")) != NULL) {
printf ("Номер протокола IPV6: %d\n\n", pht->p_proto);
} else {
fprintf (stderr, "Протокол IPV6 в базе не найден\n");
}
endprotoent ();
return 0;
}
Официальное имя протокола: ip Альтернативные имена: IP Номер протокола: 0 Официальное имя протокола: icmp Альтернативные имена: ICMP Номер протокола: 1 . . . Официальное имя протокола: tcp Альтернативные имена: TCP Номер протокола: 6 . . . Официальное имя протокола: udp Альтернативные имена: UDP Номер протокола: 17 . . . Официальное имя протокола: ipv6 Альтернативные имена: IPv6 Номер протокола: 41 . . . Официальное имя протокола: ipv6-crypt Альтернативные имена: IPv6-Crypt Номер протокола: 50 . . . Официальное имя протокола: visa Альтернативные имена: VISA Номер протокола: 70 . . . Официальное имя протокола: iso-ip Альтернативные имена: ISO-IP Номер протокола: 80 . . . Официальное имя протокола: sprite-rpc Альтернативные имена: Sprite-RPC Номер протокола: 90 . . . Официальное имя протокола: ipx-in-ip Альтернативные имена: IPX-in-IP Номер протокола: 111 . . . Официальное имя протокола: fc Альтернативные имена: FC Номер протокола: 133 Номер протокола ipv6: 41 Протокол IPV6 в базе не найден
Еще одно проявление той же логики работы -
#include <netdb.h>
void setservent (int stayopen);
struct servent *getservent (void);
struct servent *getservbyname
(const char *name, const char *proto);
struct servent *getservbyport
(int port, const char *proto);
void endservent (void);
Обратим внимание на то, что в данном случае можно указывать второй аргумент поиска - имя proto может быть пустым указателем, и тогда поиск производится
только по имени getservbyname() ) или getservbyport() ),
который должен быть задан с
Структура типа servent содержит по крайней мере следующие поля.
char *s_name; /* Официальное имя сервиса */ char **s_aliases; /* Массив указателей на альтернативные */ /* имена сервиса, завершаемый пустым */ /* указателем */ int s_port; /* Номер порта, соответствующий сервису */ /* (в сетевом порядке байт) */ char *s_proto; /* Имя протокола для взаимодействия с */ /* сервисом */
В листинге 11.17 приведен пример программы, использующей функции доступа к
#include <stdio.h>
#include <netdb.h>
int main (void) {
struct servent *pht;
char *pct;
int i;
setservent (1);
while ((pht = getservent ()) != NULL) {
printf ("Официальное имя сервиса: %s\n", pht->s_name);
printf ("Альтернативные имена:\n");
for (i = 0; (pct = pht->s_aliases [i]) != NULL; i++) {
printf (" %s\n", pct);
}
printf ("Номер порта: %d\n", ntohs ((in_port_t) pht->s_port));
printf ("Имя протокола: %s\n\n", pht->s_proto);
}
if ((pht = getservbyport (htons ((in_port_t) 21), "udp")) != NULL) {
printf ("Официальное имя сервиса: %s\n", pht->s_name);
printf ("Альтернативные имена:\n");
for (i = 0; (pct = pht->s_aliases [i]) != NULL; i++) {
printf (" %s\n", pct);
}
printf ("Номер порта: %d\n", ntohs ((in_port_t) pht->s_port));
printf ("Имя протокола: %s\n\n", pht->s_proto);
} else {
perror ("GETSERVBYPORT");
}
if ((pht = getservbyport (htons ((in_port_t) 21), (char *) NULL)) != NULL) {
printf ("Официальное имя сервиса: %s\n", pht->s_name);
printf ("Альтернативные имена:\n");
for (i = 0; (pct = pht->s_aliases [i]) != NULL; i++) {
printf (" %s\n", pct);
}
printf ("Номер порта: %d\n", ntohs ((in_port_t) pht->s_port));
printf ("Имя протокола: %s\n\n", pht->s_proto);
} else {
perror ("GETSERVBYPORT");
}
endservent ();
return 0;
}
. . . Официальное имя сервиса: ftp-data Альтернативные имена: Номер порта: 20 Имя протокола: tcp Официальное имя сервиса: ftp-data Альтернативные имена: Номер порта: 20 Имя протокола: udp Официальное имя сервиса: ftp Альтернативные имена: Номер порта: 21 Имя протокола: tcp Официальное имя сервиса: ftp Альтернативные имена: fsp fspd Номер порта: 21 Имя протокола: udp . . . Официальное имя сервиса: kerberos Альтернативные имена: kerberos5 krb5 Номер порта: 88 Имя протокола: tcp Официальное имя сервиса: kerberos Альтернативные имена: kerberos5 krb5 Номер порта: 88 Имя протокола: udp . . . Официальное имя сервиса: auth Альтернативные имена: authentication tap ident Номер порта: 113 Имя протокола: tcp Официальное имя сервиса: auth Альтернативные имена: authentication tap ident Номер порта: 113 Имя протокола: udp . . . Официальное имя сервиса: printer Альтернативные имена: spooler Номер порта: 515 Имя протокола: tcp Официальное имя сервиса: printer Альтернативные имена: spooler Номер порта: 515 Имя протокола: udp . . . Официальное имя сервиса: fido Альтернативные имена: Номер порта: 60179 Имя протокола: tcp Официальное имя сервиса: fido Альтернативные имена: Номер порта: 60179 Имя протокола: udp Официальное имя сервиса: ftp Альтернативные имена: fsp fspd Номер порта: 21 Имя протокола: udp Официальное имя сервиса: ftp Альтернативные имена: Номер порта: 21 Имя протокола: tcp
Отметим, что при поиске по ключу возвращается первый подходящий элемент базы данных.
По этой причине, когда не был задан getservbyport() ), в качестве результата был возвращен элемент с tcp.
Работа с socket() (см. листинг 11.19). Она возвращает открытый
файловый дескриптор, который может быть использован в последующих вызовах функций, оперирующих с socketpair(), создающей пару
#include <sys/socket.h>
int socket (int af, int type, int protocol);
int socketpair (int af, int type,
int protocol, int sds [2]);
Аргумент af задает type - protocol - конкретный 0 обозначает
неспецифицированный подразумеваемый af, type и protocol можно
получить с помощью описанной ранее функции getaddrinfo().
Функция socketpair() по назначению аналогична pipe(), только организуется не
безымянный канал, а sds. Обычно она
используется для AF_UNIX ; поддержка для других семейств не гарантируется.
После того как посредством функции bind() (см. листинг 11.20) sd, ему присваивают address ( address_len - длина структуры sockaddr, на которую
указывает address ). Источником локальных адресов для getaddrinfo().
#include <sys/socket.h>
int bind (int sd, const struct sockaddr
*address, socklen_t address_len);
getsockname() (см. листинг 11.21): она помещает его в структуру sockaddr,
на которую указывает аргумент address, а длину адреса записывает по указателю address_len.
#include <sys/socket.h>
int getsockname
(int sd, struct sockaddr *restrict address,
socklen_t *restrict address_len);
Если SOCK_STREAM ), то,
воспользовавшись функцией listen() (см. листинг 11.22), его следует пометить как готового
принимать соединения ("
#include <sys/socket.h> int listen (int sd, int backlog);
Аргумент backlog сообщает операционной системе рекомендуемую длину backlog вплоть до SOMAXCONN, определенной в
заголовочном файле <sys/socket.h>. ОС имеет право установить длину backlog
accept() (см. листинг 11.23). Она выбирает первое
соединение из sd
#include <sys/socket.h>
int accept
(int sd, struct sockaddr *restrict address,
socklen_t *restrict address_len);
Если значение аргумента address отлично от пустого указателя, то в структуру sockaddr, на которую указывает address, помещается address_len при обращении к функции accept() должна быть задана длина переданной структуры sockaddr, а на выходе
туда записывается длина адреса партнера по взаимодействию.
Если sd
не установлен флаг O_NONBLOCK, то вызвавший функцию accept() процесс
(поток управления) блокируется до появления подобного соединения. При непустой очереди функция select() сообщит о готовности дескриптора sd к чтению.
Другая сторона, т. е. потенциальный партнер по взаимодействию, инициирует соединение с помощью
функции connect() (см. листинг 11.24). Аргументы address и address_len стандартным образом задают
#include <sys/socket.h>
int connect (int sd, const struct sockaddr
*address, socklen_t address_len);
Если для sd (запрашивающего connect() сама осуществит AF_UNIX ).
Функция connect() ограничится фиксацией sd, если SOCK_DGRAM таким способом можно специфицировать адреса отправляемых
(с помощью функции send() ) и принимаемых (посредством обращения к функции recv() )
Когда в качестве аргумента address передается пустой указатель, адрес взаимодействующего
Попытка O_NONBLOCK ). Если по истечении этого промежутка соединение connect(), равно как и попытка connect() завершается
неудачей (переменной errno присваивается значение EINTR ), но
Если для дескриптора sd задан флаг O_NONBLOCK, а соединение не может быть connect() завершается неудачей со значением errno,
равным EINPROGRESS, но connect() с тем же EALREADY ).
connect() завершается
неудачей по другим причинам. Приложения, соответствующие стандарту POSIX-2001, должны закрыть файловый
дескриптор sd и создать новый
После select() и poll() сообщат,
что файловый дескриптор sd готов к записи.
Функция poll(), позволяющая select() и ее недавно введенный аналог pselect() представлены в листинге 11.25.
#include <sys/select.h>
int pselect (int nfds, fd_set *restrict readfds, fd_set *restrict writefds,
fd_set *restrict errorfds, const struct timespec *restrict timeout,
const sigset_t *restrict sigmask);
int select (int nfds, fd_set *restrict readfds, fd_set *restrict writefds,
fd_set *restrict errorfds, struct timeval *restrict timeout);
void FD_CLR (int fd, fd_set *fdset);
int FD_ISSET (int fd, fd_set *fdset);
void FD_SET (int fd, fd_set *fdset);
void FD_ZERO (fd_set *fdset);
Если значение аргумента readfds ( writefds, errorfds )
функции pselect() отлично от NULL, оно ссылается на объект типа fd_set, который на входе специфицирует набор файловых дескрипторов, проверяемых
на готовность к чтению (или к записи, или к обработке исключительных ситуаций), а на выходе
указывает, какие из них успешно прошли проверку. Аргумент nfds задает границу
проверяемых дескрипторов (они являются небольшими целыми числами): дескриптор подлежит проверке,
если его значение находится в пределах от 0 до (nfds - 1) включительно.
Стандарт POSIX-2001 определяет тип fd_set как абстрактный. Для работы со значениями
этого типа служат макросы FD_ZERO() (сделать набор пустым), FD_SET()
(добавить дескриптор к набору), FD_CLR() (удалить дескриптор из набора), FD_ISSET() (проверить принадлежность дескриптора набору). Значение именованной
константы FD_SETSIZE равно максимально допустимому числу дескрипторов в наборе.
В качестве результата pselect() возвращается общее количество дескрипторов во всех
трех наборах, успешно прошедших проверку.
При отсутствии готовых дескрипторов вызывающий процесс блокируется, пока таковые не появятся,
или пока не истечет заданное аргументом timeout timeout означает бесконечное ожидание,
а нулевые значения полей структуры timespec - отсутствие блокировки. Реализация может накладывать
ограничение на максимальное
Если значение аргумента sigmask отлично от пустого указателя, функция pselect()
на входе заменяет маску сигналов процесса на заданную, а на выходе восстанавливает прежнюю маску.
Функция pselect() поддерживает обычные файлы, терминалы и псевдотерминалы, каналы и
Функция select() эквивалентна pselect() со следующими оговорками.
select() timeval, а для pselect() - в секундах и наносекундах как аргумент типа struct timespec.select() нет аргумента - sigmask функции pselect().select() может модифицировать структуру, на которую
указывает аргумент timeout.С getsockopt() и setsockopt()
(см. листинг 11.26).
#include <sys/socket.h>
int getsockopt (int sd, int level,
int option_name,
void *restrict option_value,
socklen_t *restrict option_len);
int setsockopt (int sd, int level,
int option_name,
const void *option_value,
socklen_t option_len);
option_name ), которые определены в
заголовочном файле <sys/socket.h>. Выделим среди них наиболее употребительные и
разделим на несколько групп. К первой отнесем
SO_ERROR
SO_TYPE
Ко второй группе отнесем булевы
SO_DEBUG
Сообщает, записывается ли отладочная информация о работе
SO_ACCEPTCONN
Указывает, является ли
В третью группу включены
SO_SNDBUF
Размер буфера для передаваемых данных (выходного буфера).
SO_RCVBUF
Размер входного буфера.
SO_RCVLOWATM
Минимальное число байт, обрабатываемых при вводе. Подразумеваемое значение равно единице.
Большие значения могут вызвать блокировку принимающего процесса до поступления в
SO_SNDLOWAT
Минимальное число байт, обрабатываемых при выводе.
В четвертую группу входят
SO_LINGER
Определяет, блокировать ли процесс при закрытии дескриптора sd до передачи буферизованных
данных, и если блокировать, то на какой срок. linger,
определенная в заголовочном файле <sys/socket.h> и содержащая, согласно стандарту
POSIX-2001, по крайней мере следующие поля.
int l_onoff; /* Признак, включена ли опция блокирования */ /* при закрытии */ int l_linger; /* Длительность блокирования в секундах */
SO_RCVTIMEO
timeval. Подразумевая длительность равна нулю. Если в течение специфицированного
промежутка времени новых данных не поступило, операция ввода вернет число байт, меньше запрошенного,
или завершится ошибкой EAGAIN.
SO_SNDTIMEO
Не все из перечисленных setsockopt().
Исключение по понятным причинам составляют SO_ERROR, SO_TYPE, SO_ACCEPTCONN.
Аргумент level задает SOL_SOCKET, уровню TCP - IPPROTO_TCP.
Функция getpeername() (см. листинг 11.27), во многом аналогичная рассмотренной выше
функции getsockname(), позволяет опросить еще одну характеристику -
#include <sys/socket.h>
int getpeername (int sd,
struct sockaddr *restrict address,
socklen_t *restrict address_len);
После привязки
#include <sys/socket.h>
ssize_t recvfrom
(int sd, void *restrict buffer,
size_t length, int flags,
struct sockaddr *restrict address,
socklen_t *restrict address_len);
ssize_t recv (int sd, void *buffer,
size_t length, int flags);
ssize_t recvmsg (int sd, struct msghdr
*message, int flags);
ssize_t sendto
(int sd, const void *message,
size_t length, int flags,
const struct sockaddr *dest_addr,
socklen_t dest_len);
ssize_t send (int sd, const void *buffer,
size_t length, int flags);
ssize_t sendmsg (int sd, const struct msghdr
*message, int flags);
Функция recvfrom() позволяет прочитать данные (в рассматриваемом контексте называемые
также sd и поместить их в буфер buffer
длины length. Обычно функцию recvfrom() применяют к sockaddr.
В число возможных составляющих значения аргумента flags входят следующие флаги.
MSG_PEEK
Не удалять прочитанные данные. Следующий вызов recvfrom() или другой функции ввода снова вернет их.
MSG_WAITALL
Для SOCK_STREAM флаг означает, что вызывающий процесс блокируется до получения
всего запрошенного объема данных (а не до прихода первого
MSG_OOB
Запрашиваются
Как и положено функции ввода, в результате recvfrom() возвращает количество прочитанных
и помещенных в буфер данных. Для SOCK_RAW, SOCK_DGRAM, SOCK_SEQPACKET ), за одно обращение читается все MSG_PEEK ) отбрасываются.
Формально можно считать, что функция recv() эквивалентна recvfrom() с
нулевым значением аргумента address_len. Поскольку она не позволяет узнать recv() эквивалентна read().)
По сравнению с recv(), более содержательным аналогом функции recvfrom()
является recvmsg(). Отличия в данном случае носят скорее синтаксический характер и по
сути сводятся к способу передачи входных значений и возврата результатов: для минимизации числа
аргументов функция recvmsg() использует структуру типа msghdr, которая,
согласно стандарту POSIX-2001, должна содержать по крайней мере следующие поля.
void *msg_name; /* Указатель на буфер для адреса */ /* (исходного или целевого) */ socklen_t msg_namelen; /* Размер буфера для адреса */ struct iovec *msg_iov; /* Массив для разнесения/сборки сообщений */ int msg_iovlen; /* Число элементов в массиве */ /* для разнесения/сборки сообщений */ void *msg_control; /* Указатель на буфер */ /* для вспомогательных данных */ socklen_t msg_controllen; /* Размер буфера для вспомогательных данных */ int msg_flags; /* Флаги принятого сообщения */
Если для sd, соединение не msg_name буфер длины msg_namelen помещается msg_name можно сделать пустым.
При
Массив msg_iov совместно с полем msg_iovlen задает набор буферов для
размещения принимаемых данных. Структура типа iovec определяется в заголовочном файле <sys/uio.h> и содержит по крайней мере два поля.
void *iov_base; /* Адрес области памяти (буфера) */ size_t iov_len; /* Размер области памяти (в байтах) */
Заданные полем msg_iov области памяти по очереди заполняются поступающими данными,
пока не будут размещены все принятые данные или не заполнятся все буфера.
Трактовка msg_control и msg_controllen
структуры типа msghdr ) в стандарте POSIX-2001 весьма туманна. Описаны лишь средства
доступа к ним. Мы, однако, не будем на этом останавливаться.
В случае успешного завершения функции recvmsg() в поле msg_flags могут быть
установлены следующие флаги.
MSG_EOR
Достигнута
MSG_OOB
Получены
MSG_TRUNC
Пришлось урезать обычные данные.
MSG_CTRUNC
Пришлось урезать управляющие данные.
Подобно тому, как write() составляет пару read(), функцию sendto()
можно считать парной по отношению к recvfrom(). Она предназначена для отправки через sd message и length, по адресу dest_addr длины dest_len. Если для dest_addr игнорируется.
В число возможных составляющих значения аргумента flags входят следующие флаги.
MSG_EOR
Обозначает
MSG_OOB
Разумеется, успешное завершение функции sendto() не гарантирует доставку -1, указывает только на локально выявляемые ошибки.
Функция send() - пара для recv() со всеми следующими из этого обстоятельства
эквивалентностями и аналогиями.
Для функции sendmsg() структура типа msghdr, на которую указывает аргумент message, является входной (что показывает спецификатор const ). В поле msg_name задается целевой адрес. Поле msg_flags игнорируется.
Для shutdown()
(см. листинг 11.29).
#include <sys/socket.h> int shutdown (int sd, int how);
Значение аргумента how показывает, что именно завершается: SHUT_RD
прекращает SHUT_WR - SHUT_RDWR - и то, и другое.
Приводимые далее примеры строятся вокруг задачи копирования данных в распределенной программной
конфигурации со стандартного ввода одного процесса на стандартный вывод другого, удаленного.
Однако начнем мы с локального случая, обслуживаемого AF_UNIX
(см. листинг 11.30).
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа копирует строки со стандартного ввода на стандартный вывод, */
/* "прокачивая" их через пару сокетов. */
/* Используются функции ввода/вывода нижнего уровня */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/wait.h>
#define MY_PROMPT "Вводите строки\n"
#define MY_MSG "Вы ввели: "
int main (void) {
int sds [2];
char buf [1];
int new_line = 1; /* Признак того, что надо выдать сообщение MY_MSG */
/* перед отображением очередной строки */
/* Создадим пару соединенных безымянных сокетов */
if (socketpair (AF_UNIX, SOCK_STREAM, 0, sds) < 0) {
perror ("SOCKETPAIR");
exit (1);
}
switch (fork ()) {
case -1:
perror ("FORK");
exit (2);
case 0:
/* Чтение из сокета sds [0] и выдачу на стандартный вывод */
/* реализуем в порожденном процессе */
while (read (sds [0], buf, 1) == 1) {
if (write (STDOUT_FILENO, buf, 1) != 1) {
perror ("WRITE TO STDOUT");
break;
}
}
exit (0);
}
/* Чтение со стандартного ввода и запись в сокет sds [1] */
/* возложим на родительский процесс */
if (write (sds [1], MY_PROMPT, sizeof (MY_PROMPT) - 1) !=
sizeof (MY_PROMPT) - 1) {
perror ("WRITE TO SOCKET-1");
}
while (read (STDIN_FILENO, buf, 1) == 1) {
/* Перед отображением очередной строки */
/* нужно выдать сообщение MY_MSG */
if (new_line) {
if (write (sds [1], MY_MSG, sizeof (MY_MSG) - 1) != sizeof (MY_MSG) - 1) {
perror ("WRITE TO SOCKET-2");
break;
}
}
if (write (sds [1], buf, 1) != 1) {
perror ("WRITE TO SOCKET-3");
break;
}
new_line = (buf [0] == '\n');
}
shutdown (sds [1], SHUT_WR);
(void) wait (NULL);
return (0);
}
Обратим внимание на употребление в процессе, осуществляющем запись в shutdown(),
без чего читающий процесс не определит конец файла и не выйдет из цикла.
Решим теперь ту же задачу для действительно распределенной конфигурации. Наше приложение будет состоять
из двух процессов, выполняющихся на разных хостах. Первый процесс (см. листинг 11.31) читает строки со
стандартного ввода и отправляет их в виде
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа процесса, читающего строки со стандартного ввода */
/* и посылающего их в виде датаграмм другому процессу */
/* (будем называть его сервером), */
/* который должно выдать их на свой стандартный вывод. */
/* Имя серверного хоста - аргумент командной строки */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h>
#include <netdb.h>
#include <sys/socket.h>
#include <string.h>
#define MY_PROMPT "Вводите строки\n"
int main (int argc, char *argv []) {
int sd; /* Дескриптор передающего сокета */
char line [LINE_MAX]; /* Буфер для копируемых строк */
/* Структура - аргумент sendmsg */
struct msghdr msg = {NULL, 0, NULL, 0, NULL, 0, 0};
struct iovec iovbuf; /* Структура для сборки отправляемых данных */
/* Структура - входной аргумент getaddrinfo */
struct addrinfo hints = {0, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
0, NULL, NULL, NULL};
/* Указатель - выходной аргумент getaddrinfo */
struct addrinfo *addr_res;
int res; /* Результат getaddrinfo */
int msg_len; /* Длина очередной введенной строки, */
/* включая завершающий нулевой байт */
if (argc != 2) {
fprintf (stderr, "Использование: %s имя_серверного_хоста\n", argv [0]);
return (1);
}
/* Создадим сокет, через который будем отправлять */
/* прочитанные строки */
if ((sd = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
perror ("SOCKET");
return (2);
}
/* Выясним целевой адрес для датаграмм */
/* Воспользуемся портом для сервиса spooler */
if ((res = getaddrinfo (argv [1], "spooler", hints, addr_res)) != 0) {
fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res));
return (3);
}
/* Заполним структуру msghdr */
msg.msg_name = addr_res->ai_addr;
msg.msg_namelen = addr_res->ai_addrlen;
msg.msg_iov = iovbuf;
msg.msg_iovlen = 1;
iovbuf.iov_base = line;
/* Цикл чтения строк со стандартного ввода */
/* и отправки их через сокет в виде датаграмм */
fputs (MY_PROMPT, stdout);
while (fgets (line, sizeof (line), stdin) != NULL) {
msg_len = strlen (line) + 1;
iovbuf.iov_len = msg_len;
if (sendmsg (sd, msg, 0) != msg_len) {
perror ("SENDMSG");
break;
}
}
return (0);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа процесса (будем называть его сервером), */
/* читающего сообщения (строки) из датаграммного сокета */
/* и выдающего их на стандартный вывод */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h>
#include <netdb.h>
#include <sys/socket.h>
#define MY_MSG "Вы ввели: "
int main (void) {
int sd; /* Дескриптор приемного сокета */
char line [LINE_MAX]; /* Буфер для копируемых строк */
/* Структура - аргумент recvmsg */
struct msghdr msg = {NULL, 0, NULL, 0, NULL, 0, 0};
struct iovec iovbuf; /* Структура для разнесения принимаемых данных */
/* Структура - входной аргумент getaddrinfo */
struct addrinfo hints = {AI_PASSIVE, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
0, NULL, NULL, NULL};
/* Указатель - выходной аргумент getaddrinfo */
struct addrinfo *addr_res;
int res; /* Результат getaddrinfo */
/* Создадим сокет, через который будем принимать */
/* прочитанные строки */
if ((sd = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
perror ("SOCKET");
return (1);
}
/* Привяжем этот сокет к адресу сервиса spooler на локальном хосте */
if ((res = getaddrinfo (NULL, "spooler", hints, addr_res)) != 0) {
fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res));
return (2);
}
if (bind (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) {
perror ("BIND");
return (3);
}
/* Можно освободить память, которую запрашивала функция getaddrinfo() */
freeaddrinfo (addr_res);
/* Заполним структуру msghdr */
msg.msg_iov = iovbuf;
msg.msg_iovlen = 1;
iovbuf.iov_base = line;
iovbuf.iov_len = sizeof (line);
/* Цикл приема и выдачи строк */
while (1) {
if (recvmsg (sd, msg, 0) < 0) {
perror ("RECVMSG");
break;
}
fputs (MY_MSG, stdout);
fputs (line, stdout);
}
return (0);
}
Обратим внимание на несколько моментов. getaddrinfo() с " в качестве аргумента. (Если на
хосте этот
Для успешного вызова функции bind() процесс должен обладать соответствующими привилегиями.
Это может оказаться существенным для запуска программы, показанной в листинге 11.32.
Чтобы получить от getaddrinfo() адрес, пригодный для использования в качестве аргумента
ориентированных на прием функций ( listen(), bind() для приемного AI_PASSIVE во входной для getaddrinfo() структуре addrinfo (аргумент hints в описании функции getaddrinfo() ).
В таком случае для IP-адреса будет выдано значение INADDR_ANY, которое трактуется в AF_INET как адрес 127.0.0.1 ), так и с реальным
Цикл
Для передающего
В программе, посылающей connect() и, для оправдания затраченных усилий,
изменим способ отправки: вместо универсальной sendmsg() задействуем даже не более
простую функцию send(), а ее вполне привычный аналог write(), скрытый под
личиной fputs().
Какой режим буферизации выбрать для потока, сформированного по открытому файловому дескриптору MSG_TRUNC,
чтобы убедиться, что при пересылке в виде
Модифицированные варианты программ представлены в листингах 11.33 и 11.34.
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа процесса, читающего строки со стандартного ввода */
/* и посылающего их в виде датаграмм другому процессу */
/* (будем называть его сервером), */
/* который должно выдать их на свой стандартный вывод. */
/* Имя серверного хоста - аргумент командной строки */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h>
#include <netdb.h>
#include <sys/socket.h>
#define MY_PROMPT "Вводите строки\n"
int main (int argc, char *argv []) {
int sd; /* Дескриптор передающего сокета */
FILE *ss; /* Поток, соответствующий передающему сокету */
char line [LINE_MAX]; /* Буфер для копируемых строк */
/* Структура - входной аргумент getaddrinfo */
struct addrinfo hints = {0, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
0, NULL, NULL, NULL};
/* Указатель - выходной аргумент getaddrinfo */
struct addrinfo *addr_res;
int res; /* Результат getaddrinfo */
if (argc != 2) {
fprintf (stderr, "Использование: %s имя_серверного_хоста\n", argv [0]);
return (1);
}
/* Создадим сокет, через который будем отправлять */
/* прочитанные строки */
if ((sd = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
perror ("SOCKET");
return (2);
}
/* Выясним целевой адрес для датаграмм */
/* Воспользуемся портом для сервиса spooler */
if ((res = getaddrinfo (argv [1], "spooler", hints, addr_res)) != 0) {
fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res));
return (3);
}
/* Воспользуемся функцией connect() для достижения двух целей: */
/* фиксации целевого адреса и привязки к некоему локальному */
if (connect (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) {
perror ("CONNECT");
return (4);
}
/* Сформирует поток по дескриптору сокета */
if ((ss = fdopen (sd, "w")) == NULL) {
perror ("FDOPEN");
return (5);
}
/* Отменим буферизацию для этого потока */
setbuf (ss, NULL);
/* Цикл чтения строк со стандартного ввода */
/* и отправки их через сокет в виде датаграмм */
fputs (MY_PROMPT, stdout);
while (fgets (line, sizeof (line), stdin) != NULL) {
fputs (line, ss);
}
return (0);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа процесса (будем называть его сервером), */
/* читающего сообщения из датаграммного сокета */
/* и копирующего их на стандартный вывод */
/* с указанием адреса, откуда они поступили */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h>
#include <netdb.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main (void) {
int sd; /* Дескриптор приемного сокета */
/* Буфер для принимаемых сообщений */
/* Оставлено место для вставки нулевого байта */
char lbuf [BUFSIZ + 1];
/* Структура - аргумент recvmsg */
struct msghdr msg = {NULL, 0, NULL, 0, NULL, 0, 0};
struct iovec iovbuf; /* Структура для разнесения принимаемых данных */
/* Структура - входной аргумент getaddrinfo */
struct addrinfo hints = {AI_PASSIVE, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
0, NULL, NULL, NULL};
/* Указатель - выходной аргумент getaddrinfo */
struct addrinfo *addr_res;
int res; /* Результат getaddrinfo */
/* Структура для исходного адреса датаграмм */
struct sockaddr_in sai;
ssize_t lmsg; /* Длина принятой датаграммы */
/* Создадим сокет, через который будем принимать */
/* прочитанные строки */
if ((sd = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
perror ("SOCKET");
return (1);
}
/* Привяжем этот сокет к адресу сервиса spooler на локальном хосте */
if ((res = getaddrinfo (NULL, "spooler", hints, addr_res)) != 0) {
fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res));
return (1);
}
if (bind (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) {
perror ("BIND");
return (2);
}
/* Можно освободить память, которую запрашивала функция getaddrinfo() */
freeaddrinfo (addr_res);
/* Заполним структуру msghdr */
msg.msg_name = sai;
msg.msg_namelen = sizeof (struct sockaddr_in);
msg.msg_iov = iovbuf;
msg.msg_iovlen = 1;
iovbuf.iov_base = lbuf;
/* Оставим место для вставки нулевого байта */
iovbuf.iov_len = sizeof (lbuf) - 1;
/* Цикл приема и выдачи строк */
while (1) {
if ((lmsg = recvmsg (sd, msg, 0)) < 0) {
perror ("RECVMSG");
break;
}
printf ("Вы ввели и отправили с адреса %s, порт %d :",
inet_ntoa (((struct sockaddr_in *) msg.msg_name)->sin_addr),
ntohs (((struct sockaddr_in *) msg.msg_name)->sin_port));
if (msg.msg_flags MSG_TRUNC) {
printf ("Датаграмма была урезана\n");
}
lbuf [lmsg] = 0;
fputs (lbuf, stdout);
}
return (0);
}
Читателю предлагается оценить степень надежности (точнее, ненадежности) доставки
Сделаем теперь более существенные изменения, прежде всего на серверной стороне. Перейдем на использование
надежного SOCK_STREAM ).
Сервер будет получать запросы на " и
порождать для каждого принятого запроса свой обслуживающий процесс.
Новый вариант программы процесса, читающего строки со стандартного ввода и отправляющего их через
потоковый gsce.
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа процесса, читающего строки со стандартного ввода */
/* и посылающего их в виде потока другому процессу */
/* (будем называть его сервером), */
/* который должно выдать строки на свой стандартный вывод. */
/* Имя серверного хоста - аргумент командной строки */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h>
#include <netdb.h>
#include <sys/socket.h>
#define MY_PROMPT "Вводите строки\n"
int main (int argc, char *argv []) {
int sd; /* Дескриптор передающего сокета */
FILE *ss; /* Поток, соответствующий передающему сокету */
char line [LINE_MAX]; /* Буфер для копируемых строк */
/* Структура - входной аргумент getaddrinfo */
struct addrinfo hints = {0, AF_INET, SOCK_STREAM, IPPROTO_TCP,
0, NULL, NULL, NULL};
/* Указатель - выходной аргумент getaddrinfo */
struct addrinfo *addr_res;
int res; /* Результат getaddrinfo */
if (argc != 2) {
fprintf (stderr, "Использование: %s имя_серверного_хоста\n", argv [0]);
return (1);
}
/* Создадим сокет, через который будем отправлять */
/* прочитанные строки */
if ((sd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
perror ("SOCKET");
return (2);
}
/* Выясним целевой адрес для соединения */
/* Воспользуемся портом для сервиса spooler */
if ((res = getaddrinfo (argv [1], "spooler", hints, addr_res)) != 0) {
fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res));
return (3);
}
/* Воспользуемся функцией connect() для достижения двух целей: */
/* установления соединения и привязки к некоему локальному адресу */
if (connect (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) {
perror ("CONNECT");
return (4);
}
/* Сформируем поток по дескриптору сокета */
if ((ss = fdopen (sd, "w")) == NULL) {
perror ("FDOPEN");
return (5);
}
/* Отменим буферизацию для этого потока */
setbuf (ss, NULL);
/* Цикл чтения строк со стандартного ввода */
/* и отправки их через сокет в виде потока */
fputs (MY_PROMPT, stdout);
while (fgets (line, sizeof (line), stdin) != NULL) {
fputs (line, ss);
}
shutdown (sd, SHUT_WR);
return (0);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа процесса (будем называть его демоном), */
/* принимающего запросы на установления соединения */
/* и запускающего процессы для их обслуживания */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/wait.h>
int main (void) {
int sd; /* Дескриптор слушающего сокета */
int ad; /* Дескриптор приемного сокета */
/* Буфер для принимаемых строк */
struct addrinfo hints = {AI_PASSIVE, AF_INET, SOCK_STREAM, IPPROTO_TCP,
0, NULL, NULL, NULL};
/* Указатель - выходной аргумент getaddrinfo */
struct addrinfo *addr_res;
int res; /* Результат getaddrinfo */
/* Создадим слушающий сокет */
if ((sd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
perror ("SOCKET");
return (1);
}
/* Привяжем этот сокет к адресу сервиса spooler на локальном хосте */
if ((res = getaddrinfo (NULL, "spooler", hints, addr_res)) != 0) {
fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res));
return (1);
}
if (bind (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) {
perror ("BIND");
return (2);
}
/* Можно освободить память, которую запрашивала функция getaddrinfo() */
freeaddrinfo (addr_res);
/* Пометим сокет как слушающий */
if (listen (sd, SOMAXCONN) < 0) {
perror ("LISTEN");
return (3);
}
/* Цикл приема соединений и запуска обслуживающих процессов */
while (1) {
/* Примем соединение. */
/* Адрес партнера по общению нас в данном случае не интересует */
if ((ad = accept (sd, NULL, NULL)) < 0) {
perror ("ACCEPT");
return (4);
}
/* Запустим обслуживающий процесс */
switch (fork ()) {
case -1:
perror ("FORK");
return (5);
case 0:
/* Сделаем сокет ad стандартным вводом */
(void) close (STDIN_FILENO);
(void) fcntl (ad, F_DUPFD, STDIN_FILENO);
(void) close (ad);
/* Сменим программу процесса на обслуживающую */
if (execl ("./gsce", "gsce", (char *) NULL) < 0) {
perror ("EXECL");
return (6);
}
}
/* В родительском процессе дескриптор принятого соединения нужно закрыть */
(void) close (ad);
/* Обслужим завершившиеся порожденные процессы */
while (waitpid ((pid_t) (-1), NULL, WNOHANG) > 0) ;
}
return (0);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа обслуживающего процесса, */
/* выдающего принимаемые строки на стандартный вывод. */
/* Дескриптор приемного сокета передан */
/* в качестве стандартного ввода */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h>
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main (void) {
char line [LINE_MAX]; /* Буфер для принимаемых строк */
/* Структура для записи адреса */
struct sockaddr_in sai;
/* Длина адреса */
socklen_t sai_len = sizeof (struct sockaddr_in);
/* Опросим адрес партнера по общению (передающего сокета) */
if (getpeername (STDIN_FILENO, (struct sockaddr *) sai, sai_len) < 0) {
perror ("GETPEERNAME");
return (1);
}
/* Цикл чтения строк из сокета */
/* и выдачи их на стандартный вывод */
while (fgets (line, sizeof (line), stdin) != NULL) {
printf ("Вы ввели и отправили с адреса %s, порт %d :",
inet_ntoa (sai.sin_addr), ntohs (sai.sin_port));
fputs (line, stdout);
}
/* Закрытие соединения */
shutdown (STDIN_FILENO, SHUT_RD);
return (0);
}
Обратим внимание на два технических момента. Во-первых, дескриптор приемного waitpid() со значением аргумента pid, равным -1
(выражающим готовность обслужить любой завершившийся процесс-потомок), и флагом WNOHANG,
означающим отсутствие ожидания. В данном случае подобный прием не гарантирует отсутствия
Чтобы проиллюстрировать еще один типичный прием, связанный с обработкой запросов на SIGCHLD и поместим в нее вызов waitpid().
Модифицированный вариант программы процесса-демона, аккуратно ликвидирующий все
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа процесса (будем называть его демоном), */
/* принимающего запросы на установления соединения */
/* и запускающего процессы для их обслуживания */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>
/* Функция обработки сигнала SIGCHLD */
static void chldied (int dummy) {
/* Вдруг число завершившихся потомков отлично от единицы? */
while (waitpid ((pid_t) (-1), NULL, WNOHANG) > 0) ;
}
int main (void) {
int sd; /* Дескриптор слушающего сокета */
int ad; /* Дескриптор приемного сокета */
/* Структура - входной аргумент getaddrinfo */
struct addrinfo hints = {AI_PASSIVE, AF_INET, SOCK_STREAM, IPPROTO_TCP,
0, NULL, NULL, NULL};
/* Указатель - выходной аргумент getaddrinfo */
struct addrinfo *addr_res;
int res; /* Результат getaddrinfo */
/* Структура для задания реакции на сигнал SIGCHLD */
struct sigaction sact;
/* Создадим слушающий сокет */
if ((sd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
perror ("SOCKET");
return (1);
}
/* Привяжем этот сокет к адресу сервиса spooler на локальном хосте */
if ((res = getaddrinfo (NULL, "spooler", hints, addr_res)) != 0) {
fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res));
return (1);
}
if (bind (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) {
perror ("BIND");
return (2);
}
/* Можно освободить память, которую запрашивала функция getaddrinfo() */
freeaddrinfo (addr_res);
/* Пометим сокет как слушающий */
if (listen (sd, SOMAXCONN) < 0) {
perror ("LISTEN");
return (3);
}
/* Установим обработку сигнала о завершении потомка */
sact.sa_handler = chldied;
(void) sigemptyset (sact.sa_mask);
sact.sa_flags = 0;
(void) sigaction (SIGCHLD, sact, (struct sigaction *) NULL);
/* Цикл приема соединений и запуска обслуживающих процессов */
while (1) {
/* Примем соединение с учетом того, что ожидание может быть прервано */
/* доставкой обрабатываемого сигнала. */
/* Адрес партнера по общению в данном случае нас не интересует */
while ((ad = accept (sd, NULL, NULL)) < 0) {
if (errno != EINTR) {
perror ("ACCEPT");
return (4);
}
}
/* Запустим обслуживающий процесс */
switch (fork ()) {
case -1:
perror ("FORK");
return (5);
case 0:
/* Сделаем сокет ad стандартным вводом */
(void) close (STDIN_FILENO);
(void) fcntl (ad, F_DUPFD, STDIN_FILENO);
(void) close (ad);
/* Сменим программу процесса на обслуживающую */
if (execl ("./gsce", "gsce", (char *) NULL) < 0) {
perror ("EXECL");
return (6);
}
}
/* В родительском процессе дескриптор принятого соединения нужно закрыть */
(void) close (ad);
}
return (0);
}
Задание accept(): ее вызов может быть прерван доставкой сигнала SIGCHLD.
В такой ситуации прежний вариант процесса-демона завершился бы, выдав в стандартный "ACCEPT: Interrupted . Чтобы этого не случилось,
вызов accept() пришлось заключить в цикл с проверкой (в случае неудачного завершения)
значения переменной errno на совпадение с EINTR. Вообще говоря, по подобной
схеме рекомендуется действовать для всех функций, выполнение которых может быть прервано доставкой
сигнала, но допускающих и повторный вызов. Отметим в этой связи, что, например, функция connect() очень похожа, но все-таки отличается своими особенностями. После доставки
сигнала ее вызов завершается неудачей, а errno, равным EALREADY.
Рассмотрим теперь еще один вариант сервера, имеющего однопроцессную организацию, но select() (см. листинг 11.39).
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа процесса (будем называть его сервером), */
/* принимающего запросы на установления соединения */
/* и обслуживающего их с использованием select() */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h>
#include <stdio.h>
#include <netdb.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/select.h>
/* Структура для хранения дескрипторов приемных сокетов */
/* и ассоциированной информации */
#define MY_MSG_LN 128
struct sads {
int ad;
FILE *sd;
char my_msg [MY_MSG_LN];
};
/* Максимальное число параллельно обслуживаемых запросов */
#define MY_MAX_QS FD_SETSIZE
/* Функция для освобождения элемента массива */
/* дескрипторов приемных сокетов */
static void free_elem (struct sads *sadsp) {
shutdown (sadsp->ad, SHUT_RD);
close (sadsp->ad);
sadsp->ad = -1;
}
int main (void) {
int sd; /* Дескриптор слушающего сокета */
/* Структура - входной аргумент getaddrinfo */
struct addrinfo hints = {AI_PASSIVE, AF_INET, SOCK_STREAM, IPPROTO_TCP,
0, NULL, NULL, NULL};
/* Указатель - выходной аргумент getaddrinfo */
struct addrinfo *addr_res;
int res; /* Результат getaddrinfo */
fd_set rdset; /* Набор дескрипторов для чтения */
/* Структура для записи адреса */
struct sockaddr_in sai;
socklen_t sai_len; /* Длина адреса */
char line [LINE_MAX]; /* Буфер для принимаемых строк */
/* Массов для хранения дескрипторов приемных сокетов */
/* и ассоциированной информации. */
/* Последний элемент - фиктивный, */
/* нужен для упрощения поиска свободного */
struct sads sadsarr [MY_MAX_QS + 1];
int sads_max; /* Верхняя граница дескрипторов, проверяемых select() */
int i;
/* Создадим слушающий сокет */
if ((sd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
perror ("SOCKET");
return (1);
}
/* Привяжем этот сокет к адресу сервиса spooler на локальном хосте */
if ((res = getaddrinfo (NULL, "spooler", hints, addr_res)) != 0) {
fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res));
return (1);
}
if (bind (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) {
perror ("BIND");
return (2);
}
/* Можно освободить память, которую запрашивала функция getaddrinfo() */
freeaddrinfo (addr_res);
/* Пометим сокет как слушающий */
if (listen (sd, SOMAXCONN) < 0) {
perror ("LISTEN");
return (3);
}
/* Инициализируем массив sadsarr. */
/* -1 в поле ad означает, что элемент свободен */
for (i = 0; i < (MY_MAX_QS + 1); i++) {
sadsarr [i].ad = -1;
}
/* Цикл приема соединений и обслуживания запросов */
while (1) {
/* Подготовим наборы дескрипторов для select() */
FD_ZERO (rdset);
FD_SET (sd, rdset);
sads_max = sd + 1;
for (i = 0; i < MY_MAX_QS; i++) {
if (sadsarr [i].ad >= 0) {
FD_SET (sadsarr [i].ad, rdset);
if ((sadsarr [i].ad + 1) > sads_max) {
sads_max = sadsarr [i].ad + 1;
}
}
}
/* Подождем запроса на установление соединения */
/* или готовности данных для чтения. */
/* Время ожидания зададим как бесконечное */
if (select (sads_max, rdset, NULL, NULL, NULL) == -1) {
perror ("PSELECT");
return (4);
}
/* Посмотрим, есть ли запросы, ждущие установления соединения */
if (FD_ISSET (sd, rdset)) {
/* Примем запрос на установление соединения, */
/* если есть свободный элемент массива дескрипторов. */
/* Последний элемент считаем фиктивным; он всегда свободен */
i = -1;
while (sadsarr [++i].ad >= 0) ;
if (i < MY_MAX_QS) {
/* Свободный элемент нашелся */
sai_len = sizeof (struct sockaddr_in);
if ((sadsarr [i].ad = accept (sd, (struct sockaddr *) sai,
sai_len)) == -1) {
perror ("ACCEPT");
continue;
}
/* Сформируем сообщение, выдаваемое перед принятой строкой */
(void) sprintf (sadsarr [i].my_msg,
"Вы ввели и отправили с адреса %s, порт %d :",
inet_ntoa (sai.sin_addr), ntohs (sai.sin_port));
/* Сформируем поток по дескриптору сокета */
/* и отменим буферизацию для этого потока */
if ((sadsarr [i].sd = fdopen (sadsarr [i].ad, "r")) == NULL) {
perror ("FDOPEN");
free_elem (sadsarr [i]);
continue;
}
setbuf (sadsarr [i].sd, NULL);
}
}
/* Посмотрим, есть ли данные, готовые для чтения */
for (i = 0; i < MY_MAX_QS; i++) {
if ((sadsarr [i].ad >= 0) FD_ISSET (sadsarr [i].ad, rdset)) {
/* Есть данные, готовые для чтения, */
/* или установлен признак конца файла */
if (fgets (line, sizeof (line), sadsarr [i].sd) != NULL) {
/* Выведем полученную строку */
fputs (sadsarr [i].my_msg, stdout);
fputs (line, stdout);
} else {
/* Отправитель закрыл соединение. */
/* Закроем его и мы */
fclose (sadsarr [i].sd);
free_elem (sadsarr [i]);
}
}
}
}
return (0);
}
Обратим внимание на то, что функция select() позволяет select()
сообщает о наличии данных для чтения, а сама операция чтения возвращает
В качестве следующего примера рассмотрим передачу целочисленных данных. Главная проблема здесь -
аккуратное выполнение преобразований между
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа вычисляет несколько первых строк треугольника Паскаля */
/* и отправляет их через сокет сервису spooler. */
/* Имя целевого хоста - аргумент командной строки. */
/* Передаваемые данные снабжаются однобайтными маркерами типа */
/* и кратности и преобразуются к сетевому порядку байт */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h>
#include <stdio.h>
#include <netdb.h>
#include <sys/socket.h>
/* Количество вычисляемых строк треугольника Паскаля */
#define T_SIZE 16
/* Маркеры типов передаваемых данных */
#define T_UCHAR 1
#define T_UINT16 2
#define T_UINT32 4
#define T_HDR "\nТреугольник Паскаля:"
int main (int argc, char *argv []) {
uint32_t tp [T_SIZE]; /* Массив для хранения текущей строки треугольника */
unsigned char mtl; /* Переменная для хранения маркеров типа и кратности */
uint32_t ntelem; /* Текущий элемент строки в сетевом порядке байт */
int sd; /* Дескриптор передающего сокета */
/* Структура - входной аргумент getaddrinfo */
struct addrinfo hints = {0, AF_INET, SOCK_STREAM, IPPROTO_TCP,
0, NULL, NULL, NULL};
/* Указатель - выходной аргумент getaddrinfo */
struct addrinfo *addr_res;
int res; /* Результат getaddrinfo */
int i, j;
if (argc != 2) {
fprintf (stderr, "Использование: %s имя_серверного_хоста\n", argv [0]);
return (1);
}
/* Создадим передающий сокет и установим соединение */
if ((sd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
perror ("SOCKET");
return (2);
}
if ((res = getaddrinfo (argv [1], "spooler", hints, addr_res)) != 0) {
fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res));
return (3);
}
if (connect (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) {
perror ("CONNECT");
return (4);
}
/* Инициализируем массив для хранения текущей строки треугольника Паскаля, */
/* чтобы далее все элементы можно было считать и передавать единообразно */
tp [0] = 1;
for (i = 1; i < T_SIZE; i++) {
tp [i] = 0;
}
/* Передадим заголовок */
mtl = T_UCHAR;
if (write (sd, mtl, 1) != 1) {
perror ("WRITE-1");
return (5);
}
mtl = sizeof (T_HDR) - 1;
if (write (sd, mtl, 1) != 1) {
perror ("WRITE-2");
return (6);
}
if (write (sd, T_HDR, mtl) != mtl) {
perror ("WRITE-3");
return (7);
}
/* Вычислим и передадим строки треугольника Паскаля */
for (i = 0; i < T_SIZE; i++) {
/* Элементы очередной строки нужно считать от конца к началу */
/* Элемент tp [0] пересчитывать не нужно */
for (j = i; j > 0; j--) {
tp [j] += tp [j - 1];
}
/* Вывод строки треугольника в сокет */
mtl = T_UINT32;
if (write (sd, mtl, 1) != 1) {
perror ("WRITE-4");
return (8);
}
mtl = i + 1;
if (write (sd, mtl, 1) != 1) {
perror ("WRITE-5");
return (9);
}
/* Преобразование элементов строки к сетевому порядку байт и вывод */
for (j = 0; j <= i; j++) {
ntelem = htonl (tp [j]);
if (write (sd, ntelem, sizeof (ntelem)) != sizeof (ntelem)) {
perror ("WRITE-6");
return (10);
}
}
}
shutdown (sd, SHUT_WR);
return (close (sd));
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа процесса (будем называть его наивным сервером), */
/* принимающего запросы на установления соединения */
/* и осуществляющего вывод поступающих числовых данных */
/* без порождения процессов и мультиплексирования */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h>
#include <stdio.h>
#include <netdb.h>
#include <sys/socket.h>
#include <arpa/inet.h>
/* Маркеры типов передаваемых данных */
#define T_UCHAR 1
#define T_UINT16 2
#define T_UINT32 4
int main (void) {
int sd; /* Дескриптор слушающего сокета */
int ad; /* Дескриптор приемного сокета */
/* Структура - входной аргумент getaddrinfo */
struct addrinfo hints = {AI_PASSIVE, AF_INET, SOCK_STREAM, IPPROTO_TCP,
0, NULL, NULL, NULL};
/* Указатель - выходной аргумент getaddrinfo */
struct addrinfo *addr_res;
int res; /* Результат getaddrinfo */
/* Структура для записи адреса */
struct sockaddr_in sai;
socklen_t sai_len; /* Длина адреса */
char mt; /* Маркер типа поступающих данных */
char ml; /* Маркер кратности поступающих данных */
char bufc; /* Буфер для приема символьных данных */
uint16_t buf16; /* Буфер для приема 16-разрядных целых */
uint32_t buf32; /* Буфер для приема 16-разрядных целых */
/* Создадим слушающий сокет */
if ((sd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
perror ("SOCKET");
return (1);
}
/* Привяжем этот сокет к адресу сервиса spooler на локальном хосте */
if ((res = getaddrinfo (NULL, "spooler", hints, addr_res)) != 0) {
fprintf (stderr, "GETADDRINFO: %s\n", gai_strerror (res));
return (1);
}
if (bind (sd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) {
perror ("BIND");
return (2);
}
/* Можно освободить память, которую запрашивала функция getaddrinfo() */
freeaddrinfo (addr_res);
/* Пометим сокет как слушающий */
if (listen (sd, SOMAXCONN) < 0) {
perror ("LISTEN");
return (3);
}
/* Цикл приема соединений и обслуживания запросов на вывод */
while (1) {
/* Примем соединение */
sai_len = sizeof (struct sockaddr_in);
if ((ad = accept (sd, (struct sockaddr *) sai, sai_len)) < 0) {
perror ("ACCEPT");
continue;
}
/* Цикл приема поступающих данных и их вывода */
printf ("Получено с адреса %s, порт %d :",
inet_ntoa (sai.sin_addr), ntohs (sai.sin_port));
while (read (ad, mt, 1) == 1) {
/* Есть очередная порция данных, начинающаяся с типа. */
/* Прочитаем кратность, потом сами данные */
if (read (ad, ml, 1) != 1) {
perror ("READ-1");
return (4);
}
while (ml-- > 0) {
switch (mt) {
case T_UCHAR:
if (read (ad, bufc, sizeof (bufc)) != sizeof (bufc)) {
perror ("READ-2");
return (5);
}
printf ("%c", bufc);
continue;
case T_UINT16:
if (read (ad, buf16, sizeof (buf16)) != sizeof (buf16)) {
perror ("READ-3");
return (6);
}
printf (" %d", ntohs (buf16));
continue;
case T_UINT32:
if (read (ad, buf32, sizeof (buf32)) != sizeof (buf32)) {
perror ("READ-4");
return (7);
}
printf (" %d", ntohl (buf32));
continue;
}
}
/* Вывод порции завершим переводом строки */
printf ("\n");
}
/* Конец обслуживания соединения */
(void) close (ad);
}
return (0);
}
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.