Листинг 55.2. Заголовочный файл, применяемый программами i6d_ucase_sv.c и i6d_ucase_cl.c
sockets/i6d_ucase.h
#include
#include
#include
#include
#include "tlpi_hdr.h"
#define BUF_SIZE 10 /* Максимальный размер сообщений,
которыми обмениваются клиент и сервер */
#define PORT_NUM 50002 /* Номер серверного порта */
sockets/i6d_ucase.h
В листинге 55.3 показана серверная программа. Сервер использует функцию inet_ntop() для приведения адреса клиента (полученного с помощью вызова recvfrom()) в презентационный вид.
Клиентская программа, представленная в листинге 55.4, содержит два заметных нововведения по сравнению со старой версией, рассчитанной на домен UNIX (см. листинг 53.7). Во-первых, клиент интерпретирует аргумент командной строки как адрес сервера в формате IPv6 (остальные аргументы воспринимаются как отдельные датаграммы, которые нужно послать серверу). С помощью функции inet_pton() данный адрес приводится в двоичный вид. Во-вторых, клиент не привязывает свой сокет ни к какому адресу. Как отмечалось в подразделе 54.6.1, если у сокета в интернет-домене нет адреса, то ядро автоматически привязывает его к динамическому порту в текущей системе. Это можно наблюдать в следующей сессии командной строки, где сервер и клиент запускаются на одном и том же компьютере:
$ ./i6d_ucase_sv &
[1] 31047
$ ./i6d_ucase_cl::1 ciao
Server received 4 bytes from (::1, 32770)
Response 1: CIAO
Этот вывод говорит о том, что вызов recvfrom() сумел получить адрес клиентского сокета, в том числе номер его динамического порта, хотя клиент и не выполнял операцию bind().
Листинг 55.3. Сервер для изменения регистра на основе датаграммных сокетов и IPv6
sockets/i6d_ucase_sv.c
#include "i6d_ucase.h"
int
main(int argc, char *argv[])
{
struct sockaddr_in6 svaddr, claddr;
int sfd, j;
ssize_t numBytes;
socklen_t len;
char buf[BUF_SIZE];
char claddrStr[INET6_ADDRSTRLEN];
sfd = socket(AF_INET6, SOCK_DGRAM, 0);
if (sfd == -1)
errExit("socket");
memset(&svaddr, 0, sizeof(struct sockaddr_in6));
svaddr.sin6_family = AF_INET6;
svaddr.sin6_addr = in6addr_any; /* Универсальный адрес */
svaddr.sin6_port = htons(PORT_NUM);
if (bind(sfd, (struct sockaddr *) &svaddr,
sizeof(struct sockaddr_in6)) == -1)
errExit("bind");
/* Получаем сообщения, переводим их в верхний регистр и возвращаем клиенту */
for (;;) {
len = sizeof(struct sockaddr_in6);
numBytes = recvfrom(sfd, buf, BUF_SIZE, 0,
(struct sockaddr *) &claddr, &len);
if (numBytes == -1)
errExit("recvfrom");
if (inet_ntop(AF_INET6, &claddr.sin6_addr, claddrStr,
INET6_ADDRSTRLEN) == NULL)
printf("Couldn't convert client address to string\n");
else
printf("Server received %ld bytes from (%s, %u)\n",
(long) numBytes, claddrStr, ntohs(claddr.sin6_port));
for (j = 0; j < numBytes; j++)
buf[j] = toupper((unsigned char) buf[j]);
if (sendto(sfd, buf, numBytes, 0, (struct sockaddr *)
&claddr, len)!=
numBytes)
fatal("sendto");
}
}
sockets/i6d_ucase_sv.c
Листинг 55.4. Клиент для изменения регистра на основе датаграммных сокетов и IPv6
sockets/i6d_ucase_cl.c
#include "i6d_ucase.h"
int
main(int argc, char *argv[])
{
struct sockaddr_in6 svaddr;
int sfd, j;
size_t msgLen;
ssize_t numBytes;
char resp[BUF_SIZE];
if (argc < 3 || strcmp(argv[1], "-help") == 0)
usageErr("%s host-address msg…\n", argv[0]);
sfd = socket(AF_INET6, SOCK_DGRAM, 0); /* Создаем клиентский сокет */
if (sfd == -1)
errExit("socket");
memset(&svaddr, 0, sizeof(struct sockaddr_in6));
svaddr.sin6_family = AF_INET6;
svaddr.sin6_port = htons(PORT_NUM);
if (inet_pton(AF_INET6, argv[1], &svaddr.sin6_addr) <= 0)
fatal("inet_pton failed for address '%s'", argv[1]);
/* Отправляем сообщения серверу; направляем ответы в стандартный вывод */
for (j = 2; j < argc; j++) {
msgLen = strlen(argv[j]);
if (sendto(sfd, argv[j], msgLen, 0, (struct sockaddr *) &svaddr,
sizeof(struct sockaddr_in6))!= msgLen)
fatal("sendto");
numBytes = recvfrom(sfd, resp, BUF_SIZE, 0, NULL, NULL);
if (numBytes == -1)
errExit("recvfrom");
printf("Response %d: %.*s\n", j — 1, (int) numBytes, resp);
}
exit(EXIT_SUCCESS);
}
sockets/i6d_ucase_cl.c
В разделе 55.10 мы подробно рассмотрим функции getaddrinfo() и getnameinfo(), которые позволяют получать IP-адрес из имени сетевого узла и наоборот. Но перед этим нужно объяснить, как служба DNS хранит связи между именами узлов и IP-адресами.
До появления DNS имена сетевых узлов и IP-адреса связывались с помощью локального файла /etc/hosts, содержащего записи следующего вида:
# IP-address canonical hostname [aliases]
127.0.0.1 localhost