38: ptr = argv + 1;
39: while (*ptr && *ptr[0] == '-') {
40: if (!strcmp(*ptr, "—numerichost")) {
41: flags |= NI_NUMERICHOST;
42: } else if (!strcmp (*ptr, "--numericserv")) {
43: flags |= NI_NUMERICSERV;
44: } else if (!strcmp (*ptr, "--namereqd")) {
45: flags |= NI_NAMEREQD;
46: } else if (!strcmp(*ptr, "--nofqdn")) {
47: flags |= NI_NOFQDN;
48: } else if (!strcmp (*ptr, "--udp")) {
49: flags |= NI_DGRAM;
50: } else if (!strcmp(*ptr, "--host")) {
51: ptr++;
52: if (!*ptr) usage();
53: hostAddress = *ptr;
54: } else if (!strcmp(*ptr, "--service")) {
55: ptr++;
56: if (!*ptr) usage();
57: serviceAddress = *ptr;
58: } else
59: usage();
60:
61: ptr++;
62: }
63:
64: /* необходимы адреса hostAddress, serviceAddress или оба */
65: if (!hostAddress && !serviceAddress)
66: usage();
67:
68: if (serviceAddress) {
69: char * end;
70:
71: portNum = htons(strtol(serviceAddress, &end, 0));
72: if (*end) {
73: fprintf(stderr, "сбой при преобразовании %s в числоn",
74: serviceAddress);
75: return 1;
76: }
77: }
78:
79: if (!hostAddress) {
80: addr4.sin_family = AF_INET;
81: addr4.sin_port = portNum;
82: } else if (!strchr(hostAddress, ':')) {
83: /* Если hostAddress содержит двоеточие, то предполагаем версию IPv6.
84: В противном случае это IPv4 */
85:
86: if (inet_pton(AF_INET, hostAddress,
87: &addr4.sin_addr) <= 0) {
88: fprintf(stderr, "ошибка преобразования IPv4-адреса %sn",
89: hostAddress);
90: return 1;
91: }
92:
93: addr4.sin_family = AF_INET;
94: addr4.sin_port = portNum;
95: } else {
96:
97: memset(&addr6, 0, sizeof(addr6));
98:
99: if (inet_pton(AF_INET6, hostAddress,
100: &addr6.sin6_addr) <= 0) {
101: fprintf(stderr, "ошибка преобразования IPv6-адреса %sn",
102: hostAddress);
103: return 1;
104: }
105:
106: addr6.sin6_family = AF_INET6;
107: addr6.sin6_port = portNum;
108: addr = (struct sockaddr *) &addr6;
109: addrLen = sizeof(addr6);
110: }
111:
112: if (!serviceAddress) {
113: rc = getnameinfo(addr, addrLen, hostName, sizeof(hostName),
114: NULL, 0, flags);
115: } else if (!hostAddress) {
116: rc = getnameinfo(addr, addrLen, NULL, 0,
117: serviceName, sizeof(serviceName), flags);
118: } else {
119: rc = getnameinfo(addr, addrLen, hostName, sizeof(hostName),
120: serviceName, sizeof(serviceName), flags);
121: }
122:
123: if (rc) {
124: fprintf(stderr, "сбой обратного поиска: %sn",
125: gai_strerror(rc));
126: return 1;
127: }
128:
129: if (hostAddress)
130: printf("имя хоста: %sn", hostName);
131: if (serviceAddress)
132: printf("имя службы: %sn", serviceName);
133:
134: return 0;
135: }
17.5.7. Ожидание TCP-соединений
Ожидание соединений TCP происходит почти идентично ожиданию соединений домена Unix. Единственные различия заключаются в семействах протоколов и адресов. Ниже показан вариант примера сервера домена Unix, который работает через сокеты TCP.
1: /* tserver.с */
2:
3: /* Ожидает соединение на порте 4321. Как только соединение установлено,
4: из сокета в stdout копируются данные до тех пор, пока вторая
5: сторона не закроет соединение. Затем ожидает следующее соединение
6: с сокетом. */
7:
8: #include <arpa/inet.h>
9: #include <netdb.h>
10: #include <netinet/in.h>
11: #include <stdio.h>
12: #include <string.h>
13: #include <sys/socket.h>
14: #include <unistd.h>
15:
16: #include "sockutil.h" /* некоторые служебные функции */
17:
18: int main(void) {
19: int sock, conn, i, rc;
20: struct sockaddr address;
21: size_t addrLength = sizeof(address);
22: struct addrinfo hints, * addr;
23:
24: memset(&hints, 0, sizeof(hints));
25:
26: hints.ai_socktype = SOCK_STREAM;
27: hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
28: if ((rc = getaddrinfo(NULL, "4321", &hints, &addr))) {
29: fprintf(stderr, "сбой поиска имени хоста: %sn",
30: gai_strerror(rc));
31: return 1;
32: }
33:
34: if ((sock = socket(addr->ai_family, addr->ai_socktype,
35: addr->ai_protocol)) < 0)
36: die("socket");
37:
38: /* Позволяет ядру повторно использовать адрес сокета. Это разрешает
39: нам запускать программу два раза подряд, не ожидая пока истечет
40: время для кортежа (ip-адрес, порт). */
41: i = 1;
42: setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
43:
44: if (bind(sock, addr->ai_addr, addr->ai_addrlen))
45: die("bind");
46:
47: freeaddrinfo(addr);
48:
49: if (listen(sock, 5))
50: die("listen");
51:
52: while ((conn = accept(sock, (struct sockaddr *) &address,
53: &addrLength)) >=0) {
54: printf("----получение данныхn");
55: copyData(conn, 1);
56: printf("----готовоn");
57: close(conn);
58: }
59:
60: if (conn < 0)
61: die("accept");
62:
63: close(sock);
64: return 0;
65: }
Обратите внимание на то, что IP-адрес, привязанный к сокету, указывает номер порта 4321, но не IP-адрес. Это предоставляет ядру возможность при необходимости воспользоваться локальным IP-адресом.
Код в строках 41–42 требует дополнительного объяснения.
41: i = 1;
42: setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
Linux-реализация TCP, как и в остальных системах Unix, вводит ограничения на то, насколько скоро можно повторно использовать кортеж (локальный хост, локальный порт)[136]. Этот код устанавливает опцию на сокет, которая обходит это ограничение и позволяет дважды запускать сервер за короткий период времени. По сходной причине сервер-пример сокета домена Unix удаляет любой существующий файл сокета, прежде чем вызывать bind().
Функция setsockopt() позволяет устанавливать множество специальных опций для сокета и протокола:
#include <sys/socket.h>
int setsockopt(int sock, int level, int option,
const void * valptr, int vallength);
Первый аргумент — это сокет, для которого определяется опция. Второй аргумент, level, указывает тип устанавливаемой опции. В нашем сервере используется SOL_SOCKET, что указывает на установку опции обобщенного сокета. Параметр option определяет опцию, которая подлежит изменению. Указатель на новое значение опции передается через valptr, а размер значения, на которое указывает valptr, передается как vallength. Для нашего сервера применяется указатель на ненулевое целое число, которое вводит в действие опцию SO_REUSEADDR.
17.5.8. Клиентские приложения TCP
Клиенты TCP подобны клиентам домена Unix. Как правило, сразу же после создания сокета, клиент подключается к серверу с помощью функции connect(). Единственное различие состоит в способе передачи адреса в connect(). Вместо того, чтобы использовать имя файла, большинство клиентов TCP отыскивают имя хоста через функцию getaddrinfo(), которая предоставляет информацию для connect().
Ниже приводится несложный TCP-клиент, который взаимодействует с сервером, представленным в предыдущем разделе. Он принимает один аргумент: имя хоста, на котором работает сервер, или его IP-номер (в десятичном представлении с разделительными точками). Во всем остальном программа ведет себя также как клиент сокета домена Unix, показанный ранее в этой главе.
1: /* tclient.с */
2:
3: /* Подключиться к серверу, чье имя хоста или IP-адрес переданы в качестве
4: аргумента, на порте 4321. После соединения скопировать все содержимое
5: stdin в сокет, затем завершить работу. */
6:
7: #include <arpa/inet.h>
8: #include <netdb.h>
9: #include <netinet/in.h>
10: #include <stdio.h>
11: #include <stdlib.h>
12: #include <string.h>
13: #include <sys/socket.h>
14: #include <unistd.h>
15:
16: #include "sockutil.h" /* некоторые служебные функции */