русс | укр

Языки программирования

ПаскальСиАссемблерJavaMatlabPhpHtmlJavaScriptCSSC#DelphiТурбо Пролог

Компьютерные сетиСистемное программное обеспечениеИнформационные технологииПрограммирование

Все о программировании


Linux Unix Алгоритмические языки Аналоговые и гибридные вычислительные устройства Архитектура микроконтроллеров Введение в разработку распределенных информационных систем Введение в численные методы Дискретная математика Информационное обслуживание пользователей Информация и моделирование в управлении производством Компьютерная графика Математическое и компьютерное моделирование Моделирование Нейрокомпьютеры Проектирование программ диагностики компьютерных систем и сетей Проектирование системных программ Системы счисления Теория статистики Теория оптимизации Уроки AutoCAD 3D Уроки базы данных Access Уроки Orcad Цифровые автоматы Шпаргалки по компьютеру Шпаргалки по программированию Экспертные системы Элементы теории информации

Опрос данных о сети


Дата добавления: 2014-11-28; просмотров: 710; Нарушение авторских прав


Данные о хостах как узлах сети хранятся в сетевой базе, последовательный доступ к которой обслуживается функциями sethostent(), gethostent() и endhostent() (см. пример 11.1).

#include <netdb.h>void sethostent (int stayopen);struct hostent *gethostent (void);void endhostent (void);

Листинг 11.1. Описание функций последовательного доступа к сетевой базе данных о хостах - узлах сети.

Функция sethostent() устанавливает соединение с базой, остающееся открытым после вызова gethostent(), если значение аргумента stayopen отлично от нуля. Функция gethostent() последовательно читает элементы базы, возвращая результат в структуре типа hostent, содержащей по крайней мере следующие поля.

char *h_name; /* Официальное имя хоста */char **h_aliases; /* Массив указателей на альтернативные *//* имена хоста, завершаемый пустым *//* указателем */int h_addrtype; /* Тип адреса хоста */int h_length; /* Длина в байтах адреса данного типа */char **h_addr_list; /* Массив указателей на сетевые адреса *//* хоста, завершаемый пустым указателем */

Функция endhostent() закрывает соединение с базой.

В пример 11.2 показана программа, осуществляющая последовательный просмотр сетевой базы данных о хостах - узлах сети, а в пример 11.3 приведен фрагмент ее возможной выдачи.

#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;}

Листинг 11.2. Пример программы, осуществляющей последовательный доступ к сетевой базе данных о хостах - узлах сети.



Официальное имя хоста: 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 . . .

Листинг 11.3. Фрагмент возможных результатов работы программы, осуществляющей последовательный доступ к сетевой базе данных о хостах - узлах сети.

К рассматриваемой базе возможен и случайный доступ по ключам - именам и адресам хостов с помощью функций 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);

Листинг 11.4. Описание функций freeaddrinfo(), getaddrinfo(), getnameinfo().

Функция 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);

Листинг 11.5. Описание функций преобразования IP-адресов из текстового представления в числовое и наоборот.

Первые две функции манипулируют только адресами IPv4: inet_addr() преобразует текстовую цепочку cp (адрес в стандартных точечных обозначениях) в пригодное для использования в качестве IP-адреса целочисленное значение, inet_ntoa() выполняет обратное преобразование.

Вторая пара функций по сути аналогична первой, но имеет чуть более общий характер, так как способна преобразовывать адреса в формате IPv6. Первый аргумент этих функций, af, задает адресное семейство: AF_INET для IPv4 и AF_INET6 для IPv6. Буфер, на который указывает аргумент dst функции inet_pton() (в него помещается результат преобразования - IP-адрес в числовой двоичной форме с сетевым порядком байт), должен иметь длину не менее 32 бит для адресов IPv4 и 128 бит для IPv6.

Аргумент src функции inet_ntop(), возвращающей текстовое представление, указывает на буфер с IP-адресом в числовой форме с сетевым порядком байт. Аргумент size задает длину выходного буфера, на него указывает аргумент dst. Подходящими значениями, в зависимости от адресного семейства, могут служить 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);

Листинг 11.6. Описание функций преобразования целочисленных значений из хостового порядка байт в сетевой и наоборот.

Использование функции 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;}

Листинг 11.7. Пример программы, использующей функцию getaddrinfo().

Результаты для сервиса httpАдрес сокета: Порт: 80 IP-адрес: 193.232.173.1Официальное имя хоста: t01

Листинг 11.8. Возможный результат работы программы, использующей функцию getaddrinfo().

Завершая изложение серии технических моментов, укажем, что полезным дополнением к функциям 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.9. Описание функции gai_strerror().

Если немного модифицировать приведенную выше программу (в пример 11.10 показан измененный фрагмент, где имя сервиса - "HTTP" - задано большими буквами), то в стандартный протокол с помощью функции gai_strerror() будет выдано содержательное диагностическое сообщение (см. пример 11.11). Отметим, что выдача функции 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"); . . .

Листинг 11.10. Модифицированный фрагмент программы, использующей функции getaddrinfo() и gai_strerror().

GETADDRINFO: No such file or directoryGETADDRINFO: Servname not supported for ai_socktype

Листинг 11.11. Диагностические сообщения от функций perror() и gai_strerror(), выданные по результатам работы функции getaddrinfo().

Наряду с базой данных хостов (узлов сети) поддерживается база данных сетей с аналогичной логикой работы и набором функций (см. пример 11.12).

#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);

Листинг 11.12. Описание функций доступа к базе данных сетей.

Функция getnetent() обслуживает последовательный доступ к базе, getnetbyaddr() осуществляет поиск по адресному семейству (аргумент type) и номеру net сети, а getnetbyname() выбирает сеть с заданным (официальным) именем. Структура типа netent, указатель на которую возвращается в качестве результата этих функций, согласно стандарту POSIX-2001, должна содержать по крайней мере следующие поля.

char *n_name; /* Официальное имя сети */ char **n_aliases; /* Массив указателей на альтернативные *//* имена сети, завершаемый пустым указателем */ int n_addrtype; /* Адресное семейство (тип адресов) сети */ uint32_t n_net; /* Номер сети (в хостовом порядке байт) */

Точно такой же программный интерфейс предоставляет база данных сетевых протоколов (см. пример 11.13).

#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);

Листинг 11.13. Описание функций доступа к базе данных сетевых протоколов.

Структура типа protoent содержит по крайней мере следующие поля.

char *p_name; /* Официальное имя протокола */ char **p_aliases; /* Массив указателей на альтернативные *//* имена протокола, завершаемый пустым *//* указателем */ int p_proto; /* Номер протокола */

В пример 11.14 показан пример программы, осуществляющей последовательный и случайный доступ к базе данных сетевых протоколов. пример 11.15 содержит фрагмент возможных результатов работы этой программы.

#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;}

Листинг 11.14. Пример программы, осуществляющей последовательный и случайный доступ к базе данных сетевых протоколов.

Официальное имя протокола: 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 в базе не найден

Листинг 11.15. Фрагмент возможных результатов работы программы, осуществляющей последовательный и случайный доступ к базе данных сетевых протоколов.

Еще одно проявление той же логики работы - база данных сетевых сервисов (см. пример 11.16).

#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);

Листинг 11.16. Описание функций доступа к базе данных сетевых сервисов.

Обратим внимание на то, что в данном случае можно указывать второй аргумент поиска - имя протокола. Впрочем, значение аргумента proto может быть пустым указателем, и тогда поиск производится только по имени сервиса (функция getservbyname()) или номеру порта ( getservbyport()), который должен быть задан с сетевым порядком байт.

Структура типа servent содержит по крайней мере следующие поля.

char *s_name; /* Официальное имя сервиса */ char **s_aliases; /* Массив указателей на альтернативные *//* имена сервиса, завершаемый пустым *//* указателем */ int s_port; /* Номер порта, соответствующий сервису *//* (в сетевом порядке байт) */ char *s_proto; /* Имя протокола для взаимодействия с *//* сервисом */

В пример 11.17 приведен пример программы, использующей функции доступа к базе данных сервисов, а также функции преобразования между хостовым и сетевым порядками байт. В пример 11.18 показан фрагмент возможных результатов работы этой программы.

#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;}

Листинг 11.17. Пример программы, использующей функции доступа к базе данных сервисов, а также функции преобразования между хостовым и сетевым порядками байт.

. . . Официальное имя сервиса: 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

Листинг 11.18. Фрагмент возможных результатов работы программы, использующей функции доступа к базе данных сервисов, а также функции преобразования между хостовым и сетевым порядками байт.

Отметим, что при поиске по ключу возвращается первый подходящий элемент базы данных. По этой причине, когда не был задан протокол (второе обращение к функции getservbyport()), в качестве результата был возвращен элемент с протоколом tcp.



<== предыдущая лекция | следующая лекция ==>
 | Функции для работы с сокетами


Карта сайта Карта сайта укр


Уроки php mysql Программирование

Онлайн система счисления Калькулятор онлайн обычный Инженерный калькулятор онлайн Замена русских букв на английские для вебмастеров Замена русских букв на английские

Аппаратное и программное обеспечение Графика и компьютерная сфера Интегрированная геоинформационная система Интернет Компьютер Комплектующие компьютера Лекции Методы и средства измерений неэлектрических величин Обслуживание компьютерных и периферийных устройств Операционные системы Параллельное программирование Проектирование электронных средств Периферийные устройства Полезные ресурсы для программистов Программы для программистов Статьи для программистов Cтруктура и организация данных


 


Не нашли то, что искали? Google вам в помощь!

 
 

© life-prog.ru При использовании материалов прямая ссылка на сайт обязательна.

Генерация страницы за: 0.17 сек.