Программирование PC Linux: пример клиента и сервера UDP Sat, October 12 2024  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

Linux: пример клиента и сервера UDP Печать
Добавил(а) microsin   

В этой статье приведен перевод документации с описанием API-функций getaddrinfo, freeaddrinfo, gai_strerror [1], с примером использования в реализации клиента и сервера UDP.

Функции getaddrinfo, freeaddrinfo, gai_strerror применяются организации сетевых соединений на основе сокетов [14, 15].

#include < sys/types.h>
#include < sys/socket.h>
#include < netdb.h>

int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);
void freeaddrinfo(struct addrinfo *res);
const char *gai_strerror(int errcode);

getaddrinfo(). По заданному имени хоста (node) и номеру порта службы (service) функция getaddrinfo() возвратит одну или большее количество структур addrinfo, каждая из которых содержит Internet-адрес, который может быть указан в вызовах функций bind [2] или connect [3]. Функция getaddrinfo() комбинирует в себе функциональность, которую предоставляют функции gethostbyname [4] и getservbyname [5] в один программный интерфейс, однако в отличие от этих функций, getaddrinfo() реэнтрантна и позволяет программам устранить зависимости от типа адреса IPv4 и IPv6.

Структура addrinfo, используемая getaddrinfo(), содержит следующие поля:

struct addrinfo {
    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;
};

Аргумент hints указывает на структуру addrinfo, которая задает критерий выбора структур адреса сокета, возвращаемых в списке, на которую указывает res. Если hints не NULL, то он указывает на структуру addrinfo, поля которой ai_family, ai_socktype и ai_protocol задают критерий, который ограничивает набор адресов сокета, возвращенных getaddrinfo(), следующим образом:

ai_family. Это поле задает желаемое семейство возвращаемых адресов. Для этого поля допускаются значения, включающие AF_INET и AF_INET6. Значение AF_UNSPEC показывает, что getaddrinfo() должна возвратить адреса сокета для любого семейства адресов (например, IPv4 или IPv6), которые могут использоваться сетевым узлом (node) и сетевой службой (service).

ai_socktype. Это поле задает предпочтительный тип сокета, например SOCK_STREAM или SOCK_DGRAM. Указание 0 в этом поле показывает, что getaddrinfo() может возвратить адрес сокета любого типа.

ai_protocol. Это поле задает протокол для возвращаемых адресов сокетов. Указание 0 в этом поле показывает, что getaddrinfo() может возвратить адрес сокета любого протокола.

ai_flags. Это поле задает дополнительные опции, описанные ниже. Несколько флагов могут быть указаны одновременно с помощью операции побитного ИЛИ.

Все другие поля в структуре, на которую указывает hints, должны содержать либо 0, либо null-указатель, как это необходимо.

Если указать hints как NULL, то это эквивалентно установке ai_socktype и ai_protocol в 0, а также ai_family в AF_UNSPEC, и ai_flags в (AI_V4MAPPED | AI_ADDRCONFIG). POSIX задает другие умолчания для ai_flags, см. далее Примечания. Параметр node указывает либо числовой сетевой адрес (для IPv4 это четыре числа, указанные через точку, как это поддерживается функцией inet_aton [6]; для IPv6 возможен шестнадцатеричный строковый формат, как это поддерживается функцией inet_pton [7]), либо сетевое имя хоста, который преобразуется в IP соответствующими сервисными функциями (DNS, /etc/hosts). Если hints.ai_flags содержит флаг AI_NUMERICHOST, то node должен быть задан числовым сетевым адресом. Флаг AI_NUMERICHOST подавляет потенциально продолжительное по времени преобразование имени хоста в адрес хоста (network host address lookup).

Если в hints.ai_flags указан флаг AI_PASSIVE, и node NULL, то возвращаемый адрес будет подходящим для вызова bind [2], чтобы привязать сокет, который будет принимать соединения вызовом accept [16]. Возвращенный адрес сокета будет содержать "подстановочный" (wildcard, т. е. INADDR_ANY для адресов IPv4, IN6ADDR_ANY_INIT для адресов IPv6). Wildcard-адрес используется приложениями applications (обычно серверами), которые рассчитаны на прием соединений на любом из сетевых адресов хоста. Если node не NULL, то флаг AI_PASSIVE игнорируется.

Если в hints.ai_flags не установлен флаг AI_PASSIVE, то возвращенные адреса сокета подойдут для вызовом connect [3], sendto [17] или sendmsg [18]. Если node NULL, то сетевой адрес будет установлен в адрес интерфейса loopback (INADDR_LOOPBACK для адресов IPv4, IN6ADDR_LOOPBACK_INIT для адресов IPv6); это используется приложениями, которые предназначены для коммуникации с программами, работающими на том же самом хосте.

Параметр service установит порт для каждой возвращенной структуры адреса. Если в этом аргументе вместо номера порта указано имя службы (см. services [19]), то оно транслируется в соответствующий номер порта. Этот аргумент может также быть указан как десятичное число, которое преобразуется в двоичное значение. Если service NULL, то номер порта возвращенного адреса сокета остается не инициализированным. Если в hints.ai_flags указан флаг AI_NUMERICSERV, и service не NULL, то service должен указывать на строку, содержащую цифровой номер порта. Этот флаг используется для запрета вызова службы разрешения имен в случаях, когда известно, что это не требуется.

Может быть NULL либо параметр node, либо параметр service, но не оба одновременно.

Функция getaddrinfo() выделяет и инициализирует связанный список структур addrinfo, по одной для каждого сетевого адреса, соответствующего node и service, с учетом любых ограничений, налагаемых hints, и возвращает указатель на начало этого списка в res. Элементы в связанном списке пристыковываются через поле ai_next.

Есть несколько причин, по которым связанный список может содержать больше одной структуры addrinfo, включая: сетевой хост имеет несколько сетей (multihomed) и доступен по нескольким протоколам (например как через AF_INET, так и AF_INET6); или одна и та же служба (service) доступна на нескольких типах сокета (например одна на адресе SOCK_STREAM, другая на адресе SOCK_DGRAM). Обычно приложение должно использовать адреса в том порядке, в каком они возвращены. Функция сортировки, используемая внутри getaddrinfo(), определена стандартом RFC 3484; порядок сортировки можно настроить для конкретной системы путем редактирования /etc/gai.conf [11] (доступно начиная с glibc 2.5).

Если hints.ai_flags включает флаг AI_CANONNAME, то поле ai_canonname первой из структур addrinfo в возвращенном списке установлено в официальное имя хоста.

Остальные поля каждой возвращенной структуры addrinfo инициализируются следующим образом:

* Поля ai_family, ai_socktype и ai_protocol возвращают параметры создания сокетаfields return the socket creation parameters (например, эти поля имеют то же значение, что и соответствующие аргументы socket [15]). Например, ai_family может быть возвращено AF_INET или AF_INET6; ai_socktype может возвратить SOCK_DGRAM или SOCK_STREAM; и ai_protocol возвращает протокол для сокета.
* Указатель на адрес сокета помещается в поле ai_addr, и длина адреса сокета в байтах помещается в поле ai_addrlen.

Если hints.ai_flags включают флаг AI_ADDRCONFIG, то адреса IPv4, возвращенные в списке, на который указывает res, будут только если сконфигурирован хотя бы один адрес IPv4, и адреса IPv6 возвращаются только если в системе настроен хотя бы один адрес IPv6. Адрес loopback в этом случае не считается действительным как сконфигурированный адрес. Этот флаг полезен, например, в системах с поддержкой только IPv4, чтобы гарантировать, что getaddrinfo() никогда не возвратит адреса сокета IPv6, которые всегда приведут к неудаче вызовов connect [3] или bind [2].

Если hints.ai_flags указывают флаг AI_V4MAPPED, и hints.ai_family указано как AF_INET6, и не найдено соответствующих адресов IPv6, то возвращаются адреса IPv6, отображенные на IPv4, в списке, на который указывает res. Если в hints.ai_flags указаны оба флага AI_V4MAPPED и AI_ALL, то в том же списке возвращаются и адреса IPv6, и отображенные на IPv4 адреса IPv6. AI_ALL игнорируется, если также не указан AI_V4MAPPED.

freeaddrinfo(). Функция freeaddrinfo() освобождает память, динамически выделенную для связанного списка, на который указывает res.

Расширения getaddrinfo() для интернациональных доменных имен. Начиная с glibc 2.3.4, getaddrinfo() была расширена, чтобы позволить входящие и исходящие имена хостов прозрачно конвертировать из формата Internationalized Domain Name (IDN), см. RFC 3490, Internationalizing Domain Names in Applications (IDNA). Определены четыре новых флага:

AI_IDN. Если указан этот флаг, то имя узла, указанное в параметре node, при необходимости преобразуется в формат IDN. Кодирование source такое, как в текущих локальных настройках языка системы (current locale). Если входное имя содержит не-ASCII символы, то используется кодировка IDN. Эти части имени узла (разделенные точками), которые содержат не-ASCII символами, кодируются с использованием ASCII Compatible Encoding (ACE) перед передаче в функции преобразования имени в IP-адрес (name resolution functions).

AI_CANONIDN. После успешного преобразования имени (name lookup), и если указан флаг AI_CANONNAME, функция getaddrinfo() вернет каноническое имя узла, соответствующее переданному обратно значению структуры addrinfo. Возвращаемое значение является точной копией значения, возвращаемое функцией разрешения имен. Если имя закодировано с использованием ACE, то оно будет содержать префикс xn-- для одного или большего количества компонентов имени. Для конвертации этих компонентов в читаемую форму может быть передан флаг AI_CANONIDN в дополнение к флагу AI_CANONNAME. Результирующая строка кодируется с использованием кодировки текущей языковой локали.

AI_IDN_ALLOW_UNASSIGNED, AI_IDN_USE_STD3_ASCII_RULES. Установка этих флагов разрешит соответственно флаги IDNA_ALLOW_UNASSIGNED (разрешение не назначенных точек Юникода, unassigned Unicode code points) и IDNA_USE_STD3_ASCII_RULES (проверка вывода, что это имя хоста, соответствующее STD3) для использования в обработке IDNA.

[Возвращаемое значение]

getaddrinfo() возвратит 0 в случае успеха, либо один из следующих ненулевых кодов ошибки:

EAI_ADDRFAMILY. Указанный сетевой узел не имеет сетевых адресов в запрошенном семействе адресов.

EAI_AGAIN. Сервером имен возвращена индикация временной неудачи разрешения имени (temporary failure indication). Сделайте позже еще попытку.

EAI_BADFLAGS. Поле hints.ai_flags содержит недопустимые флаги; или hints.ai_flags включает флаг AI_CANONNAME и name было NULL.

EAI_FAIL. Сервер имен возвратил индикацию перманентного отказа (permanent failure indication).

EAI_FAMILY. Запрошенное семейство адресов не поддерживается.

EAI_MEMORY. Недостаточно памяти.

EAI_NODATA. Указанный сетевой хост существует, однако для него не определено никакие сетевые адреса.

EAI_NONAME. Параметры node или service неизвестны; или оба параметра node и service указаны как NULL; или был указан AI_NUMERICSERV в поле hints.ai_flags, и параметр service был указан не как строка с цифровым номером порта.

EAI_SERVICE. Запрошенная служба service недоступна для запрошенного типа сокета. Она может быть доступна через другой тип сокета. Например, эта ошибка могла возникнуть, если служба была "оболочкой" (shell, т. е. служба, доступная только в потоковых сокетах, stream sockets), и либо hints.ai_protocol был IPPROTO_UDP, либо hints.ai_socktype был SOCK_DGRAM; или ошибка могла возникнуть, если service был не NULL, и hints.ai_socktype был SOCK_RAW (тип сокета, который не поддерживает концепцию сервисов).

EAI_SOCKTYPE. Запрошенный тип сокета не поддерживается. Это могло произойти, например, если не совместимы друг с другом hints.ai_socktype и hints.ai_protocol (например указаны соответственно как SOCK_DGRAM и IPPROTO_TCP).

EAI_SYSTEM. Другая системная ошибка, проверьте код errno для получения подробностей.

gai_strerror(). Функция gai_strerror() транслирует эти коды ошибки в удобочитаемый текст, подходящий для сообщений об ошибках.

[Примечания]

getaddrinfo() поддерживает нотацию the address%scope-id для указания IPv6 scope-ID.

AI_ADDRCONFIG, AI_ALL и AI_V4MAPPED доступны начиная с glibc 2.3.3. AI_NUMERICSERV доступен начиная с glibc 2.3.4.

В соответствии с POSIX.1 указание hints как NULL должно привести к тому, что ai_flags будет считаться равным 0. Библиотека GNU C вместо этого подразумевает для этого случая значение (AI_V4MAPPED | AI_ADDRCONFIG), поскольку это считается улучшение спецификации.

[Пример использования]

Следующие программы демонстрируют использование функций getaddrinfo(), gai_strerror(), freeaddrinfo() и getnameinfo(3). Реализован эхо-сервер UDP и клиент для него.

// Программа демонстрирует использование getaddrinfo(), gai_strerror(),
// freeaddrinfo(), и getnameinfo(3).
// В этом файле показана реализация echo-сервера для датаграмм UDP.
#include < sys/types.h>
#include < stdio.h>
#include < stdlib.h>
#include < unistd.h>
#include < string.h>
#include < sys/socket.h>
#include < netdb.h>

#define BUF_SIZE 500

int main(int argc, char *argv[]) { struct addrinfo hints; struct addrinfo *result, *rp; int sfd, s; struct sockaddr_storage peer_addr; socklen_t peer_addr_len; ssize_t nread; char buf[BUF_SIZE];
if (argc != 2) { fprintf(stderr, "Usage: %s port\n", argv[0]); exit(EXIT_FAILURE); }
memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; /* Разрешение IPv4 или IPv6 */ hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */ hints.ai_flags = AI_PASSIVE; /* Для wildcard IP-адреса */ hints.ai_protocol = 0; /* Любой протокол */ hints.ai_canonname = NULL; hints.ai_addr = NULL; hints.ai_next = NULL;
s = getaddrinfo(NULL, argv[1], &hints, &result); if (s != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s)); exit(EXIT_FAILURE); }
/* getaddrinfo() возвратит список структур адреса. Проба каждого адреса, пока не будет успешный bind(2). Если окажется неудачным вызов socket(2) (или bind(2)), то мы закроем сокет и сделаем попытку на следующем адресе. */ for (rp = result; rp != NULL; rp = rp->ai_next) { sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sfd == -1) continue; if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0) break; /* Успех */ close(sfd); }
if (rp == NULL) { /* Нет нормального адреса */ fprintf(stderr, "Could not bind\n"); exit(EXIT_FAILURE); }
freeaddrinfo(result); /* Больше не требуется */
/* Чтение датаграмм и отправка их обратно отправителю */ for (;;) { peer_addr_len = sizeof(struct sockaddr_storage); nread = recvfrom(sfd, buf, BUF_SIZE, 0, (struct sockaddr *) &peer_addr, &peer_addr_len); if (nread == -1) continue; /* Игнорировать неудачный запрос */
char host[NI_MAXHOST], service[NI_MAXSERV];
s = getnameinfo((struct sockaddr *) &peer_addr, peer_addr_len, host, NI_MAXHOST, service, NI_MAXSERV, NI_NUMERICSERV); if (s == 0) printf("Received %zd bytes from %s:%s\n", nread, host, service); else fprintf(stderr, "getnameinfo: %s\n", gai_strerror(s));
if (sendto(sfd, buf, nread, 0, (struct sockaddr *) &peer_addr, peer_addr_len) != nread) fprintf(stderr, "Error sending response\n"); } }

// Программа демонстрирует использование getaddrinfo(), gai_strerror(),
// freeaddrinfo(), и getnameinfo(3).
// В этом файле показана реализация клиента для датаграмм UDP.
#include < sys/types.h>
#include < sys/socket.h>
#include < netdb.h>
#include < stdio.h>
#include < stdlib.h>
#include < unistd.h>
#include < string.h>

#define BUF_SIZE 500

int main(int argc, char *argv[]) { struct addrinfo hints; struct addrinfo *result, *rp; int sfd, s, j; size_t len; ssize_t nread; char buf[BUF_SIZE];
if (argc < 3) { fprintf(stderr, "Usage: %s host port msg...\n", argv[0]); exit(EXIT_FAILURE); }
/* Получение адреса (адресов), соответствующих host/port */ memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; /* Разрешение IPv4 или IPv6 */ hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */ hints.ai_flags = 0; hints.ai_protocol = 0; /* Любой протокол */
s = getaddrinfo(argv[1], argv[2], &hints, &result); if (s != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s)); exit(EXIT_FAILURE); }
/* getaddrinfo() возвратит список структур адреса. Проба каждого адреса, пока не произойдет успешный connect(2). Если socket(2) (или connect(2)) завершится неудачей, то мы закроем socket и попытаемся перейти к следующему адресу. */ for (rp = result; rp != NULL; rp = rp->ai_next) { sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sfd == -1) continue;
if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) break; /* Успех */
close(sfd); } if (rp == NULL) { /* Нет нормального адреса */ fprintf(stderr, "Could not connect\n"); exit(EXIT_FAILURE); }
freeaddrinfo(result); /* Больше не требуется */
/* Отправка остальных аргументов командной строки как отдельные датаграммы, и чтение ответов от сервера */ for (j = 3; j < argc; j++) { len = strlen(argv[j]) + 1; /* +1 для завершающего байта null */
if (len + 1 > BUF_SIZE) { fprintf(stderr, "Ignoring long message in argument %d\n", j); continue; }
if (write(sfd, argv[j], len) != len) { fprintf(stderr, "partial/failed write\n"); exit(EXIT_FAILURE); }
nread = read(sfd, buf, BUF_SIZE); if (nread == -1) { perror("read"); exit(EXIT_FAILURE); }
printf("Received %zd bytes: %s\n", nread, buf); }
exit(EXIT_SUCCESS); }

См. также gethostbyname [4], getaddrinfo_a [8], getnameinfo [9], inet [10],  gai.conf [11], hostname [12], ip [13].

[Ссылки]

1. GETADDRINFO https://www.linuxhowtos.org/manpages/3/getaddrinfo.htm.
2. BIND https://www.linuxhowtos.org/manpages/2/bind.htm.
3. CONNECT https://www.linuxhowtos.org/manpages/2/connect.htm.
4. GETHOSTBYNAME https://www.linuxhowtos.org/manpages/3/gethostbyname.htm.
5. getservbyname https://www.linuxhowtos.org/manpages/3/getservbyname.htm.
6. INET_ATON https://www.linuxhowtos.org/manpages/3/inet_aton.htm.
7. INET_PTON https://www.linuxhowtos.org/manpages/3/inet_pton.htm.
8. GETADDRINFO_A https://www.linuxhowtos.org/manpages/3/getaddrinfo_a.htm.
9. GETNAMEINFO https://www.linuxhowtos.org/manpages/3/getnameinfo.htm.
10. INET https://www.linuxhowtos.org/manpages/3/inet.htm.
11. GAI.CONF https://www.linuxhowtos.org/manpages/5/gai.conf.htm.
12. HOSTNAME https://www.linuxhowtos.org/manpages/7/hostname.htm.
13. IP https://www.linuxhowtos.org/manpages/7/ip.htm.
14. Sockets Tutorial https://www.linuxhowtos.org/C_C++/socket.htm.
15. SOCKET https://www.linuxhowtos.org/manpages/2/socket.htm.
16. ACCEPT https://www.linuxhowtos.org/manpages/2/accept.htm.
17. sendto https://www.linuxhowtos.org/manpages/2/sendto.htm.
18. sendmsg https://www.linuxhowtos.org/manpages/2/sendmsg.htm.
19. SERVICES https://www.linuxhowtos.org/manpages/5/services.htm.

 

Добавить комментарий


Защитный код
Обновить

Top of Page