Справочник от Автор24
Поделись лекцией за скидку на Автор24

Вычислительные системы, сети и телекоммуникации

  • ⌛ 2013 год
  • 👀 2212 просмотров
  • 📌 2170 загрузок
  • 🏢️ Южно-Уральский государственный университет
Выбери формат для чтения
Статья: Вычислительные системы, сети и телекоммуникации
Найди решение своей задачи среди 1 000 000 ответов
Загружаем конспект в формате pdf
Это займет всего пару минут! А пока ты можешь прочитать работу в формате Word 👇
Конспект лекции по дисциплине «Вычислительные системы, сети и телекоммуникации» pdf
Министерство образования и науки Российской Федерации Южно-Уральский государственный университет Кафедра информатики 004.7(07) C207 Е.М. Сартасов ВЫЧИСЛИТЕЛЬНЫЕ СИСТЕМЫ, СЕТИ И ТЕЛЕКОММУНИКАЦИИ Учебное пособие Челябинск Издательский центр ЮУрГУ 2013 УДК 004.71(075.8) + 004.77(075.8) + 004.78(075.8) C207 Одобрено учебно-методической комиссией факультета экономики и управления. Рецензенты: Завьялов О.Г., Овсяницкая Л.Ю. Сартасов Е.М. Вычислительные системы, сети и C207 телекоммуникации: Учебное пособие. – Челябинск: Издательский центр ЮУрГУ, 2013. – 86 с. В пособии рассмотрены вопросы разработки вычислительных систем, работающих на нескольких компьютеров. В качестве средств передачи информации между компьютерами используются компьютерные сети и телекоммуникации. Изложение сопровождается примерами разработки элементов систем. Пособие ориентировано на студентов направления 230700.62 «Прикладная информатика», изучающих предмет «Вычислительные системы, сети и телекоммуникации». . УДК 004.71(075.8) + 004.77(075.8) + 004.78(075.8) © Издательский центр ЮУрГУ, 2013. Введение По мере развития вычислительной техники стал актуальным переход от простых программ, работающих на одном компьютере, к вычислительным системам, функционирующим на множестве компьютеров и использующих в качестве средств передачи информации компьютерные сети и телекоммуникации. Целью данного учебного пособия является: научить студентов направления 230700.62 «Прикладная информатика» разрабатывать вычислительные системы, работающие на множестве компьютеров. Из поставленной цели вытекают следующие задачи: изучить аппаратные и программные средства функционирования компьютерных сетей; изучение различных способов приема и передачи информации по сети между программами, работающими на разных компьютерах; рассмотрение различий приема и передачи информации в локальных и глобальных компьютерных сетях; изучение конфигураций вычислительных систем; получения навыков разработки вычислительных систем. В первой главе пособия описаны аппаратные и программные средства, необходимые для построения, настройки и оптимизации сети. Во второй главе рассматриваются вопросы разработки вычислительных систем с использованием сетевых протоколов передачи информации UDP и TCP. Также рассматриваются способы передачи информации с помощью блоков данных и каналов с применением библиотеки функций Windows Socket. В третьей главе описаны способы передачи и приема информации с помощью каналов Mailslot и Pipe. В четвертой главе рассматриваются отличия передачи информации в локальных и глобальных компьютерных сетях, для работы с глобальными сетями используется библиотека функций WinInet. В пособии рассмотрены практические примеры разработки элементов вычислительных сетей, работающих на различных компьютерах, связанных друг с другом сетью. 3 1. Компьютерные сети 1.1. Основные определения Система – множество элементов, находящихся в отношениях и связях друг с другом, которое образует определённую целостность, единство. Более простыми словами: система – множество взаимосвязанных элементов. Вычислительная (информационная) система – множество взаимосвязанных элементов для решения вычислительных (информационных) задач. Элементы вычислительной системы – компьютеры (более одного), компьютерная сеть (одна, соединяющая все компьютеры), программы, люди. Компьютерная сеть (вычислительная сеть, сеть передачи данных) – система связи компьютеров и компьютерного оборудования (серверы, маршрутизаторы и другое оборудование). Для передачи информации могут быть использованы различные физические явления, как правило – различные виды электрических сигналов, световых сигналов или электромагнитного излучения. Телекоммуникации – комплекс технических средств, предназначенных для передачи информации на расстояние. По сравнению с компьютерами, не включенными в систему, вычислительная система имеет следующие преимущества: обеспечение распределенной обработки данных и параллельной работы многих компьютеров; возможность создания распределенной базы данных (РБД), размещаемой в памяти различных компьютеров; возможность обмена большими массивами информации памятью компьютеров, удаленными друг от друга на значительные расстояния; коллективное использование дорогостоящих ресурсов: прикладных программных продуктов (ППП), баз данных (БД), баз знаний (БЗ), запоминающих устройств (ЗУ), печатающих устройств; предоставление большего перечня услуг (электронная почта (ЭП), телеконференции, электронные доски объявлений (ЭДО), дистанционное обучение; повышение эффективности использования средств ВТ за счет более интенсивной и равномерной их загрузки, а также надежности обслуживания; возможность оперативного перераспределения вычислительных мощностей между пользователями сети в зависимости от изменения их потребностей, а также их резервирования; 4 облегчение работ по совершенствованию технических, программных и информационных средств. 1.2. Аппаратные средства компьютерных сетей Для создания компьютерной сети необходимо в каждом компьютере иметь сетевую плату и средства коммуникации между сетевыми платами. К средствам коммуникации относятся кабели и коммутаторы. Кабели можно подразделить на электрические и оптоволоконные. Электрические кабели делятся на коаксиальные и витые пары. Структура коаксиального кабеля приведена на рис. 1.1, витая пара показана на рис 1.2. Рис. 1.1. Коаксиальный кабель На рис. 1.1 цифрами обозначено: 1– внутренний проводник, 2 – изоляция, 3 – внешний проводник, 4 – оболочка. Рис. 1.2. Витая пара В настоящее время, витая пара является самым распространённым решением для построения проводных (кабельных) локальных сетей благодаря своей надежности дешевизне и лёгкости в монтаже. Кабель подключается к сетевым устройствам при помощи разъёма 8P8C (8 Position 8 Contact), показанного на рис. 1.3. 5 Рис. 1.3. Разъём 8P8C Присоединение кабеля к разъему называется обжимом, для этого используется специальный обжимной инструмент, показанный на рис. 1.4. Рис. 1.4. Обжимной инструмент Если в сети более двух компьютеров, то следует использовать концентратор или коммутатор. Сетевой концентратор или хаб (от англ.hub – центр) работает на физическом уровне, ретранслируя входящий сигнал с одного из портов в сигнал на все остальные порты, реализуя, таким образом топологию общая шина, c разделением пропускной способности сети между всеми. Сетевой концентратор также обеспечивает бесперебойную работу сети при отключении устройства от одного из портов или повреждении кабеля, в отличие, например, от сети на коаксиальном кабеле, которая в таком случае прекращает работу целиком. Внешний вид сетевого концентратора показан на рис. 1.5. В настоящее время сетевые концентраторы в основном вытеснены сетевыми коммутаторами. 6 Рис. 1.5. Сетевой концентратор Сетевой коммутатор или свич от англ. switch – переключатель – работает на канальном уровне. В отличие от концентратора, который распространяет трафик от одного подключенного устройства ко всем остальным, коммутатор передаёт данные только непосредственно получателю (исключение составляет широковещательный трафик всем узлам сети и трафик для устройств, для которых не известен исходящий порт коммутатора). Это повышает производительность и безопасность сети, избавляя остальные сегменты сети от необходимости (и возможности) обрабатывать данные, которые им не предназначались. Внешний вид сетевого коммутатора показан на рис. 1.6. Рис. 1.6. Сетевой коммутатор Если в сети небольшое кол-во компьютеров и расположены на небольшом расстоянии друг от друга (не более 100 метров), то указанных устройств достаточно для создания сети методом подключения всех компьютеров к коммутатору или концентратору. Такая сеть называется одноранговой, однако если компьютеров много (больше чем разъемов у коммутатора или концентратора) или расположены они на достаточно удаленном расстоянии, то требуется создание многоранговой сети. Структура многоранговой сети показана на рис. 1.7. Коммутиру ющее устройство Коммутиру ющее устройство Рабочая станция Рабочая станция Коммутиру ющее устройство Рабочая станция Рабочая станция Коммутиру ющее устройство Рабочая станция Рис. 1.7. Структурная схема многоранговой сети 7 Рабочая станция Предположим, что нам нужно передать данные с одной рабочей станции на другую, находящуюся в другом сегменте сети. В этом случае данные должны пройти через несколько коммутирующих устройств. Коммутатор, как правило, не может определить цепочку коммутирующих устройств, через которые нужно передавать данные. Вместо коммутатора следует использовать маршрутизатор. Мар рути атор или роутер от англ. router – специализированный сетевой компьютер, пересылающий пакеты данных между различными сегментами сети. Обычно маршрутизатор использует адрес получателя, указанный в пакетных данных, и определяет по таблице маршрутизации путь, по которому следует передать данные. Внешний вид сетевого маршрутизатора показаны на рис. 1.8 и 1.9. Рис. 1.8. Маршрутизатор для магистральных каналах Рис. 1.9. Маршрутизатор для дома и малого офиса с Wi-Fi интерфейсом Если компьютеры, которые нужно связать в сеть расположены на достаточно далеком расстоянии друг от друга, то следует либо использовать усилители сигнала (до нескольких километров) либо отказаться от использования проводного электрического сигнала в пользу оптоволокна или радиосигнала. Оптоволокно – вид кабеля, основанный на передаче информации с помощью световых лучей. Лучи, входящие под разными углами в оптоволокно называются модами, а волокно, поддерживающее несколько мод – многомодовым. На рис. 1.10 показаны одномодовое и многомодовое оптоволокна. 8 Рис. 1.10. Сверху одномодовое, снизу – многомодовое оптоволокна Оптоволокно обладает высокой скоростью передачи информации и пропускной способностью, а также позволяет передавать информацию на большие расстояния (сотни и тысячи километров). Однако имеет и недостаток – на больших расстояниях возможны обрывы оптоволокна. Несколько большей надежностью обладает радиоканал. Схема подключения с помощью радиоканала показана на рис. 1.11. Рис. 1.11. Схема подключения по радиоканалу Для подключения по радиоканалу не требуются кабели, в качестве носителя информации является радиоволна. Для передачи большого кол-ва информации требуется радиоволна высокой частоты (не менее 90 МГц). Волны этого диапазона не огибают землю, поэтому приемник и передатчик должны находиться в пределах прямой видимости. При дальности более 20 км следует ретранслировать сигнал с помощью радиорелейной линии или через спутник. 1.3. Программные средства компьютерных сетей Программные средства компьютерных сетей можно разделить на 3 категории: сетевые драйверы, системное программное обеспечение (ПО) и проблемное ПО. 9 1.3.1. Сетевые драйверы Сетевые драйверы можно подразделить на 4 вида: драйверы сетевых плат, протоколов, клиентов и служб (серверов). Для установки и настройки сетевых драйверов следует открыть окно свойств подключения по сети. В операционной системе Windows для этого следует войти в окно сетевого окружения, в контекстном меню выбрать пункт свойства, щелкнуть по пункту подключение по локальной сети и еще раз в контекстном меню выбрать пункт свойства. Откроется окно, показанное на рис. 1.12. В других операционных системах настройка проводится аналогичным образом. Рис. 1.12. Окно свойств подключения по сети Драйвер сетевой платы – программа позволяющая унифицировать работу с сетевой платой для других программ. Данный драйвер отображен в верхнем поле ввода окна. В большинстве случаев операционная система сама определяет необходимый драйвер и настраивает его. Однако если сетевая плата нового типа, то разработчики операционной системы могут не успеть разработать и включить драйвер, тогда драйвер нужно устанавливать вручную. Для этого следует воспользоваться кнопкой 10 <настроить>. После нажатия этой кнопки откроется окно свойств сетевой платы с несколькими закладками. Следует выбрать закладку <драйвер> и нажать кнопку <изменить>. Во вновь открывшемся окне будет запрос: искать драйвер автоматически или выбрать вручную на данном компьютере. Автоматический поиск вряд ли приведет к нужному результату, поэтому будем выбирать вручную, указав путь к этому драйверу. При покупке компьютера обычно имеется компакт диск с драйверами для всех устройств, с него и будем устанавливать драйвер. Однако если диска нет, или диск есть, а нужного драйвера нет, то нужно с другого компьютера выйти в интернет, зайти на сайт производителя сетевой платы и скачать оттуда нужный драйвер, затем на флеши (или другом устройстве) перенести на наш компьютер и указать нужный путь. Драйвер будет установлен. Драйвер протокола – программа, реализующая правила обмена информацией между двумя и более компьютерами. С помощью драйверов протоколов формируются заголовки пакетов передаваемых данных, создаются каналы связи и т.д. На рис. 1.12 драйверы протоколов находятся в списке, расположенном в средней части окна и обозначены значком . К основным протоколам можно отнести: IPX/SPX, разработанный фирмой Novell, NetBIOS – Microsoft, TCP/IP – Sun. Если есть выход в Интернет, то требуется протокол TCP/IP. Если выхода в Интернет нет или его хочется запретить, и есть сервер с операционной системой Novell, то наиболее целесообразным будет протокол IPX/SPX. В настоящее время при установке основных операционных систем, по умолчанию ставится протокол TCP/IP. Для добавления, удаления или настройки протоколов следует воспользоваться соответствующими кнопками, расположенными ниже списка. Серьезной настройки требует протокол TCP/IP, остальные протоколы настройки не требуют. При настройке протокола TCP/IP следует в окне, изображенном на рис. 1.12, выбрать протокол TCP/IP версии 4 и нажать кнопку «Свойства». Откроется окно, изображенное на рис. 1.13. В данном окне следует ввести IP-адрес, маску подсети, основной шлюз и предпочитаемый DNS. Если есть выход в интернет без проксисервера, то все вышеперечисленные параметры должен указать интернетпровайдер. В этом случае IP-адрес должен быть уникальным на всем земном шаре, такие IP-адреса будем называть истинными или внешними. 11 Рис 1.13. Окно свойств протокола TCP/IP Однако приобретение истинных адресов на все компьютеры сети дело дорогостоящее, для экономии можно приобрести один (или несколько) истинных адресов и установить на компьютер с истинным адресом программу прокси-сервер. На все компьютеры сети устанавливаются адреса, уникальные только в своей сети. Такие адреса называются внутренними. Каждый конкретный адрес назначает администратор сети из возможного диапазона: 10.0.0.0 – 10.255.255, 172.16.0.0 – 172.31.255.255, 192.168.0.0 – 192.168.255.255. Компьютер с внутренним адресом обращается не в интернет, а к прокси-серверу, который находится на компьютере с истинным адресом и он от своего имени переправляет запрос в интернет, полученный ответ переправляет исходному компьютеру. Драйвер клиента – программа, позволяющая другим компьютерам получать доступ к сетевым ресурсам нашего компьютера. Сетевыми ресурсами могут быть: диски, папки, принтеры и другие устройства, открытые для общего доступа (естественно с возможными ограничениями). 12 Драйвер службы (сервера) – программа, позволяющая программам с других компьютеров получать доступ к сетевым ресурсам данного компьютера. 1.3.2. Системное программное обеспечение. К сетевому системному программному обеспечению относятся контроллеры доменов и сети, а также различные виды серверов. Контроллер домена – программа, контролирующий область компьютерной сети (домен). На большинстве Unix-подобных систем в качестве контроллера домена выступает пакет прикладных программ Samba. Контроллеры домена, работающие под управлением Windows Server, создаются при использовании мастера установки Active Directory. Контроллеры домена хранят информацию о политике безопасности в домане, а также управляют взаимодействиями пользователя и домена, включая процессы входа пользователя в систему, проверку подлинности, доступа к ресурсам. Обычно сеть делится на несколько доменов, однако небольшие сети могут состоять из одного домена, который будет называться контроллером сети. На контроллере домена следует создать пользователей и задать им атрибуты безопасности. Данные атрибуты будут распространяться на все компьютеры в домене. Веб-сервер (интернет сервер) — это программа, принимающий запросы от клиентов, обычно веб-браузеров, и выдающий им ответы, как правило, в виде HTML-страниц, изображений, файлов, медиа-потоков или других данных. В операционной системе Windows имеется встроенный веб-сервер IIS (Internet Information Service), в UNIX системах наиболее часто встречается веб-сервер Apache. Веб-сервер не только выдает готовые страницы, но и формирует их в процессе выполнения программ, написанных на специальных языках программирования, основными из которых являются PHP, ASP и C#. Почтовый сервер (сервер электронной почты, mail-сервер) – программа, которая передаёт сообщения от одного компьютера к другому (англ. mail transfer agent, MTA). Обычно почтовый сервер работает «за кулисами», а пользователи имеют дело с другой программой – клиентом электронной почты (англ. mail user agent, MUA). К примеру, в распространённой конфигурации клиентом электронной почты является Outlook Express, (однако в последнее время часто используются полноценные версии почтового клиента от Microsoft– Outlook, а также клиента от Mozilla– Thunderbird). Когда пользователь набрал сообщение и посылает его получателю, почтовый клиент взаимодействует с почтовым сервером. Почтовый сервер отправителя взаимодействует с почтовым сервером получателя. На почтовом сервере получателя сообщение попадает в почтовый ящик, откуда при помощи агента доставки 13 сообщений (mail delivery agent, MDA) доставляется клиенту получателя. Часто последние два агента совмещены в одной программе (к примеру, sendmail), хотя есть специализированные MDA, которые в том числе занимаются фильтрацией спама. Прокси-сервер (от англ. proxy – «представитель, уполномоченный») – программа в компьютерных сетях, позволяющая клиентам выполнять косвенные запросы к другим сетевым серверам. Наиболее частое использование прокси-сервера осуществляется для выхода в интернет с компьютеров, имеющих внутренние IP-адреса. Для этих целей должен быть компьютер с двумя IP-адресами: внешним и внутренним. На этот компьютер устанавливается прокси-сервер, который получает запросы от клиентов по внутренним адресам, отправляет запрос в интернет от своего имени по внешнему адресу, полученный ответ пересылает на клиентский компьютер по внутреннему адресу. Сервер ба данных – программа, которая выполняет обслуживание и управление базой данных и отвечает за целостность и сохранность данных, а также обеспечивает операции ввода-вывода при доступе клиента к информации. Примеры серверов баз данных: SQL SERVER (Microsoft), Oracle SERVER (Oracle Corporation), IBM DB2, Informix, My SQL. Borland InterBASE. С серверами баз данных взаимодействуют клиентские программы. Взаимодействие осуществляется в виде запрос – ответ. Запрос обычно формируется на языке SQL (Structured Query Language – язык структурированных запросов). Ответ может быть в виде таблиц, скалярных единиц или кодов возвратов. 1.3.3. Прикладное программное обеспечение. К прикладному программному обеспечению, относятся программы, позволяющие решать поставленные задачи. Сетевые прикладные программы можно разделить на 2 группы: приобретенные программные продукты (1С, Галактика, Парус и другие) и самостоятельно разработанные. Для приобретенных программных продуктов требуется настройка и сопровождение. Для самостоятельно разработанных программ нужна группа разработчиков, которые будут сопровождать и совершенствовать программное обеспечение. В дальнейшем основная часть пособия будет посвящена разработке сетевых программ. 1.4. Семиуровневая модель сетевого в аимодействия. В настоящее время используется достаточно большое количество сетевых протоколов, причем в рамках одной и той же сети определяется сразу несколько из них. Стремление к максимальному упорядочению и упрощению процессов разработки, модернизации и расширения сетей определило необходимость введения стандартов, регламентирующих принципы и процедуры организации взаимодействия абонентов 14 компьютерных сетей. С этой целью была разработана так называемая Эталонная модель в аимодействия открытых систем, состоящая из семи уровней. (OSI, Open Systems Interconnection), разработанна международной организацией стандартизации (ISO, International Standards Organization). Модель OSI напоминает разные "уровни" обычного почтового адреса – от страны и региона до улицы, дома (места назначения) и фамилии получателя. Для доставки информации соответствующему получателю устройства на маршруте передачи используют разные уровни детализации. Каждый из уровней представляет определенную группу функций, необходимых для работы компьютерной сети. Уровни модели OSI приведены в таблице. Таблица. Уровни модели OSI Уровень 7. Прикладной 6. Представительский 5. Сеансовый 4. Транспортный 3. Сетевой 2. Канальный 1. Физический Средства, работающие на данном уровне Протоколы HTTP, FTP, электронная почта Mailslot, pipe UDP, TCP, NetBIOS, SPX IP, IPX Ithernet Фи ический уровень осуществляет передачу потока битов по соответствующей физической среде (электрический или оптический кабель, радиоканал) Канальный уровень формирует из байт кадры данных. На этом уровне задаются физические адреса устройства-отправителя и устройстваполучателя данных. На этом же уровне к передаваемым данным добавляется контрольная сумма, определяемая с помощью алгоритма циклического кода. На приемной стороне по контрольной сумме определяют и по возможности исправляют ошибки. Сетевой уровень адресует сообщение, задавая единице передаваемых данных (пакету) логические сетевые адреса узла назначения и узла источника (IP-адреса), определяет маршрут, по которому будет отправлен пакет данных, транслирует логические сетевые адреса в физические, а на приемной стороне – физические адреса в логические. Сетевые логические адреса принадлежат пользователям. Транспортный уровень делит большое сообщение узла источника информации на части, при этом добавляет заголовок и формирует сегменты определенного объема, а короткие сообщения может объединять в один сегмент. В узле назначения происходит обратный процесс. В заголовке сегмента задаются номера порта источника и назначения. 15 Сеансовый уровень устанавливает сеанс связи двух конечных узлов (компьютеров), определяет, какой компьютер является передатчиком, а какой приемником, задает для передающей стороны время передачи. Представительский уровень изменяет форму представления данных. Например, данные преобразуются в общепринятый формат ASCII. При приеме данных происходит обратный процесс. На уровне 6 также происходит шифрация и сжатие данных. Прикладной уровень оперирует наиболее общей единицей данных – сообщением. На этом уровне реализуется управление общим доступом к сети, потоком данных, сетевыми службами, такими, как FTP, TFTP, HTTP, SMTP, SNMP и др. 1.5. Контрольные вопросы Что такое вычислительная система. Что такое компьютерная сеть. Что такое телекоммуникация Как работает сетевой концентратор Как работает сетевой коммутатор. Что такое маршрутизатор. Как функционирует оптоволоконная связь. Как функционирует радиоканальная связь. Что такое драйвер сетевой платы. Что такое драйвер протокола обмена информацией. Как настроить драйвер протокола обмена информацией. Что такое драйвер службы доступа. Что такое файловый сервер. Что такое IP-адрес. Какова адресация в сети. Что такое web-сервер. Что такое сервер баз данных. Что такое почтовый сервер. Что такое доменное имя. Что такое контроллер домена. Как осуществляется передача информации на физическом уровне. Как осуществляется передача информации на канальном уровне. Как осуществляется передача информации на сетевом уровне. Как осуществляется передача информации на транспортном уровне. Как осуществляется передача информации на сеансовом уровне. Как осуществляется передача информации на представительском уровне. 27. Как осуществляется передача информации на прикладном уровне. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 16 2. Исполь ование протоколов обмена информацией для ра работки сетевых программ 2.1. Исполь ование протоков UDP и TCP Разработку сетевых программ начнем с использования наиболее часто используемых протоколов UDP (User Datagram Protocol — протокол пользовательских датаграмм) и TCP (Transmission Control Protocol протокол управления передачей) из серии TCP/IP. Протокол UDP имеет аналогию с почтой: к передаваемому сообщению добавляется служебный пакет, который содержит адреса получателя и отправителя, размер сообщения, контрольную сумму и некоторую другую служебную информацию, затем служебный пакет и данные отправляются получателю. Максимальный размер сообщения – 64К. Если сообщение больше, то его следует разбить на части и каждую часть отправить отдельно. Однако, если передавать в цикле, то не факт, что части придут в том же порядке, в котором отправлялись. Поэтому следует либо нумеровать части, либо после отправки части ждать подтверждения приема. Еще одним недостатком протокола UDP является негарантированная доставка пакета и отсутствие сообщения о недоставке. Недоставка может быть по одной из следующих причин: адрес получателя указан неверно, компьютер выключен, программа, с которой осуществляется обмен, не запущена. Протокол ТСР исправляет указанные недостатки – доставка гарантирована, максимальный размер сообщения 2Гб, однако пользоваться им сложнее – сначала нужно установить канал связи, а затем принимать и передавать данные. В ТСР отсутствует широковещательная передача данных – всем компьютерам сети или подсети. Адресация в протоколах UDP и TCP состоит из 2-х составляющих: IPадреса и порта. IP-адрес характеризует компьютер, порт – точку подключение программы к сети. Порт нужен т.к. на одном компьютере могут работать одновременно несколько сетевых программ, а также у сетевой программы может быть несколько точек подключения к сети. 2.2. Библиотека функций Windows Socket Для реализации протоколов обмена информацией существует библиотека функций Windows Socket (сокращенно winsock). В языках программирования С и С++ для использования библиотеки следует подключить в проект библиотечный файл WS2_32.Lib и использовать include оператор: #include "winsock.h" 17 Рассмотрим набор функций библиотеки winsock. Функция WSAStartup предназначена для инициализация библиотеки Windows Socket. Она имеет два параметра. Первый параметр – версия библиотеки, она может быть 0x0101 или 0x0200. Второй параметр – указатель на структуру WSADATA, в которую будут записаны различные параметры библиотеки. В случае успеха функция возвращает нулевое значение, иначе код ошибки. В качестве примера использования функции можно привести следующий текст: WSADATA WSAData; int rc = WSAStartup(MAKEWORD(2,0), &WSAData); if (rc != 0) { // Ошибка инициализации WSAStartup } // if Функция WSACleanup предназначена для освобождения библиотеки. Данная функция не имеет параметров. В случае успеха функция возвращает нулевое значение, иначе код ошибки. Функция WSAGetLastError возвращает код ошибки последней операции. Данная функция не имеет параметров. Функция socket предназначена для создание сокета – логической единицы, для приема и отправки сообщений. Она имеет три параметра. Первый параметр – тип адреса, для протоколов UDP и TCP он должен принимать значение AF_INET. Второй параметр – тип передачи данных, для протокола UDP он должен быть SOCK_DGRAM, а для протокола TCP – SOCK_STREAM. Третий параметр – протокол, обычно он определяется первыми двумя параметрами и может принимать нулевое значение. В случае успеха функция возвращает целое полжительное значение, имеющее тип SOCKET, это значение нужно использовать в последующих функциях. В случае ошибки функция возвращает значение INVALID_SOCKET. В качестве примера использования функции можно привести следующий текст: SOCKET UDPSocket = socket(AF_INET, SOCK_DGRAM, 0); if (UDPSocket == INVALID_SOCKET) { // Протокол UDP не установлен. } // if Функция closesocket предназначена для удаление сокета. Она имеем один параметр – идентификатор сокета, который получен от функции socket. Функция bind предназначена для привязки адреса к сокету. Она имеет 3 параметра. Первый параметр – идентификатор сокета. Второй параметр – привязываемый адрес. Для протоколов UDP и TCP он должен быть в виде структуры: 18 struct sockaddr_in { short sin_family; // Должно быть AF_INET u_short sin_port; // Задается программистом struct in_addr sin_addr; // IP адрес с которым будем работать char sin_zero[8]; // должно быть нулевым }; где struct in_addr sin структура следующего вида: struct in_addr { union { struct {u_char s_b1, s_b2, s_b3, s_b4; } s_un_b; struct {u_short s_w1, s_w2; } s_un_w; u_long s_addr; } S_un; }. Третий параметр – размер адресной структуры. Рекомендуется использовать функцию sizeof(struct sockaddr_in). В случае успешного выполнения, функция bind возвращает ноль, в противном случае – SOCKET_ERROR. В качестве примера использования функции можно привести следующий текст: sockaddr_in OurAddress; memset(&OurAddress, 0, sizeof(OurAddress)); OurAddress.sin_family = AF_INET; OurAddress.sin_port = 2001; rc =bind(UDPSocket, (LPSOCKADDR)&OurAddress, sizeof(sockaddr_in)); if (rc == SOCKET_ERROR) { // Ошибка в функции bind } // if Функция inet_addr преобразует IP-адреса из символьного вида в числовой, например так inet_addr("200.200.200.201"). Возвращаемое значение – целое беззнаковое число содержащее IP-адрес. Функция gethostbyname позволяет по имени компьютера или доменному имени определить IP-адрес. В качестве параметра используется имя компьютера или доменное имя. Функция возвращает указатель на структуру HOSTENT, поле h_addr_list которой содержит массив IPадресов, поле h_addr является нулевым элементом массива h_addr_list и содержит первый адрес. Если IP-адресов нет, то функция возвращает NULL. В качестве примера использования функции можно привести следующий текст: HOSTENT *ph; sockaddr_in dest_sin; ph = gethostbyname(имя_компьютера); 19 if (ph != NULL) { memcpy((char *)&(dest_sin.sin_addr), ph->h_addr, ph->h_length); } Функция gethostname применяется для определения имени своего компьютера. Она имеет 2 параметра. Первый параметр – указатель на символьный массив, в который будет занесено имя компьютера. Второй параметр – целое число, размер массива. В качестве примера использования функции можно привести следующий текст: char OurCompName[100]; int gethostname(OurCompName, sizeof(OurCompName)); Функция sendto применяется для передачи данных без канала, по протоколу UDP. Она имеет 6 параметров. Первый параметр – идентификатор сокета. Второй параметр – указатель на область передаваемых данных. Тип второго параметра – char *, однако функция может передавать не только символьные, но и любые данные, однако указатель должен быть преобразован к char *. Третий параметр – размер передаваемых данных, максимальный размер для этой функции и для протокола UDP в целом 64К. Четвертый параметр – флаг, он может быть нулевым. Пятый параметр – указатель на структуру sockaddr_in, содержащую адрес получателя. Шестой параметр – размер структуры. В случае успеха, функция возвращает количество отправленных байт, в случае ошибки SOCKET_ERROR. В качестве примера использования функции можно привести следующий текст: #define SERVER_PORT 2001 sockaddr_in CallAddress; memset(&CallAddress, 0, sizeof(CallAddress)); CallAddress.sin_family = AF_INET; CallAddress.sin_port = SERVER_PORT; CallAddress.sin_addr.s_addr = inet_addr(“192.168.0.112”); char *Buf = “Проба пера”; rc = sendto(UDPSocket, Buf, strlen(Buf)+1, 0, (LPSOCKADDR) &CallAddress, sizeof(CallAddress)); if (rc == SOCKET_ERROR) { rc = WSAGetLastError(); // Ошибка sendto с кодом rc } // if Для приема данных без канала по протоколу UDP используется функция recvfrom. Также как и функция sendto, она имеет 6 параметров. Первый параметр – идентификатор сокета. Второй параметр – указатель на область, в которую будут записаны полученные данные. Третий параметр – размер области для получения данных. Четвертый параметр – флаг, он 20 может быть нулевым. Пятый параметр – указатель на структуру sockaddr_in, содержащую будет записан адрес отправителя. Шестой параметр – указатель на целочисленную беззнаковую переменную, куда заносится размер структуры. В случае успеха, функция возвращает количество полученных байт, в случае ошибки SOCKET_ERROR. В качестве примера использования функции можно привести следующий текст: char Buf[64*1024+1]; sockaddr_in CallAddress; unsigned int Len = sizeof(CallAddress) int rc = recvfrom(UDPSocket, Buf, sizeof(Buf)-1, 0, , (LPSOCKADDR) &CallAddress, &n); if (rc == SOCKET_ERROR) { rc = WSAGetLastError(); // Ошибка recvfrom с кодом rc } // if Сложность использования функции recvfrom заключается в том, что необходимо знать момент вызова этой функции, т.к. ее нужно вызвать, тогда, когда пришли данные. Для определения этого момента нужно использовать функцию WSAAsyncSelect. Эта функция нужна для преобразования сетевых сообщений в Windows сообщения. Она имеет 4 параметра. Первый параметр – идентификатор сокета. Второй параметр – идентификатор окна, в приложениях Windows Form среды разработки Microsoft Visual Studio этот идентификатор можно получить из свойства Handle формы, преобразовав его к типу Int32, а затем к типу HWND. Третий параметр – идентификатор Windows сообщения, например WSA_NETEVENT, он должен быть равным числу, большему WM_USER, например WM_USER+2. Четвертые параметр – идентификатор сетевого сообщения, сообщение о получении данных равно FD_READ. В качестве примера использования функции можно привести следующий текст: #define WSA_NETEVENT (WM_USER+2) WSAAsyncSelect(UDPSocket, (HWND)(this->Handle.ToInt32()), WSA_NETEVENT, FD_READ); При использовании протокола UDP функцию WSAAsyncSelect нужно вызвать после вызова функции bind. После вызова этой функции в момент прихода сетевого сообщения будет генерироваться Windows сообщение WSA_NETEVENT с lParam равным FD_READ. Следует перехватить и обработать это сообщение, для этого нужно переопределить функцию обработки сообщений WndProc. Она может быть такой: protected: virtual void WndProc (Message% m) override { 21 int rc, l=sizeof(CallAddress); char Buf[64*1024+1]; if (m.Msg == WSA_NETEVENT) { if (m.LParam.ToInt32() == FD_READ) { rc = recvfrom((SOCKET)m.WParam.ToInt32(), Buf, sizeof(Buf)-1, 0, (PSOCKADDR)&CallAddress, &l); if (rc == SOCKET_ERROR) { rc = WSAGetLastError(); // Ошибка recvfrom с кодом rc return; } // if if (rc >= 1) { // Получены данные в кол-ве rc байт, они находятся // в массиве Buf } // if } // if } // if Form::WndProc( m ); } // WndProc Рассмотренные выше функции полностью позволяют работать с протоколом UDP. Для работы с протоколом TCP требуется создать канал связи и по нему передавать и принимать данные. Рассмотрим необходимые для этого функции. Функция listen устанавливает сокет в режим ожидания подключения. Она имеет 2 параметра. Первый параметр – идентификатор сокета. Второй параметр – максимальный размер очереди на подключение, рекомендуемое значение 5. В качестве примера использования функции можно привести следующий текст: listen(TCPSocket, 5); Функция connect инициирует создание канала связи. Она имеет 3 параметра. Первый параметр – идентификатор сокета. Второй параметр – указатель на структуру sockaddr_in, содержащую адрес точки подключения с которой устанавливаем канал. Третий параметр – размер структуры. В случае успеха, функция возвращает нулевое значение, в случае ошибки SOCKET_ERROR. В качестве примера использования функции можно привести следующий текст: rc=connect(TCPSocket,(PSOCKADDR)&CallAddress, sizeof(CallAddress)); if (rc == SOCKET_ERROR) { rc = WSAGetLastError(); // Ошибка connect с кодом rc } // if 22 Функция accept выполняет отклик на функцию connect на другой стороне канала. Она имеет 3 параметра. Первый параметр – идентификатор сокета. Второй параметр – указатель на структуру sockaddr_in, в которую будет записан адрес подключаемого. Третий параметр – указатель на целочисленную беззнаковую переменную, куда заносится размер структуры. В случае успеха, функция создает виртуальный сокет и возвращает его идентификатор, который следует использовать в последующих функциях, в случае ошибки INVALID_SOCKET. Для определения момента вызова функции accept следует использовать функцию WSAAsyncSelect с параметрами: WSAAsyncSelect(TCPSocket, (HWND)(this->Handle.ToInt32( )), WSA_NETACCEPT, FD_ACCEPT). Тогда в момент подключения будет генерироваться Windows сообщение WSA_ ACCEPT с lParam равным FD_ ACCEPT. Следует перехватить и обработать это сообщение, для этого нужно переопределить функцию обработки сообщений WndProc. Она может быть такой: protected: virtual void WndProc (Message% m) override { int rc, l=sizeof(CallAddress); char Buf[64*1024+1]; if (m.Msg == WSA_NETACCEPT) { if (m.LParam.ToInt32() == FD_ACCEPT) { TmpSocket = accept((SOCKET)m.WParam.ToInt32(), (PSOCKADDR)&CallAddress, (int *)&l); if (TmpSocket == INVALID_SOCKET) { rc = WSAGetLastError(); // Ошибка accept с кодом rc return; } // if // Канал создан } // if } // if Form::WndProc( m ); } // WndProc Для передачи данных по каналу используется функция send. Она имеет 4 параметра, которые полностью совпадают с первыми четырьмя параметрами функции sendto. Для приема данных по каналу используется функция recv. Она имеет 4 параметра, которые полностью совпадают с первыми четырьмя параметрами функции recvfrom. Так же, как и для функции recvfrom, нужно знать момент вызова этой функции. Для этого следует воспользоваться функцией WSAAsyncSelect. 23 2.3. Пример программ, исполь ующих протокол UDP В качестве примера рассмотрим две программы, обменивающиеся сообщениями с использованием протокола UDP. Программу, инициирующую передачу данных, назовем клиентом, а программу, ожидающую соединения, сервером. Дизайн программ приведен на рис. 2.1 и 2.2. Рис. 2.1. Дизайн программы клиент Рис. 2.2. Дизайн программы сервер 24 Рассмотрим основные действия программ. При старте программ (событие Load формы) выполняются следующие действия: инициализация библиотеки WindowsSocket с помощью функции WSAStartup, создание сокета (socket), привязка адреса к сокету (bind), инициализация преобразования сетевых сообщений в Windows сообщения (WSAAsyngSelect,) определение IP-адреса и имени компьютера (gethostname и gethostbyname). При завершении программ (событие FormClosed): закрытие сокета (closesocket), освобождение библиотеки WindowsSocket (WSACleanup). При нажатии кнопки «Отправить» программы «Клиент»: преобразование содержимого полей ввода из типа String в массив типа char, если заполнено поле IP-адрес, то преобразуем адрес в 4-х байтное число (inet_addr), если поле IP-адрес не заполнено, но заполнено поле имя компьютера, то определим адрес по имени компьютера (gethostbyname), отправка сообщения (sendto). При нажатии кнопки «Отправить» программы «Сервер»: преобразование содержимого поля ввода из типа String в массив типа char, отправка сообщения клиенту, который последним отправил сообщение серверу (sendto). Для приема сообщений в программах переопределена функция обработки Windows сообщений. Если код сообщения равен WSA_NETEVENT, а параметр сообщения FD_READ, то получаем текст пришедшего сообщения с помощью функции recvfrom. В конце переопределенной функции вызывается старая функция обработки сообщений Windows. После вызова функции проверяется код возврата, если он окажется ошибочным, то выдается соответствующее сообщение. Текст программы «Клиент» приведен в приложении 1, «Сервер» в приложении 2. 2.4. Пример программ, исполь ующих протокол TCP Аналогично предыдущему примеру, данный пример состоит из двух программ «Клиент» и «Сервер». Дизайн программы «Клиент» приведен на рис. 2.3, дизайн программы «Сервер» полностью совпадает с дизайном 25 программы, использующим протокол UDP и приведенным ранее на рис. 2.2. Рис. 2.3. Дизайн программы клиент, использующей протокол ТСР Рассмотрим основные действия программ. При старте программ «Клиент» (событие Load формы) выполняются следующие действия: инициализация библиотеки WindowsSocket с помощью функции WSAStartup, создание сокета (socket), привязка адреса к сокету (bind), определение IP-адреса и имени компьютера (gethostname и gethostbyname). При старте программ «Сервер»: инициализация библиотеки WindowsSocket с помощью функции WSAStartup, создание сокета (socket), привязка адреса к сокету (bind), инициализация преобразования сетевых сообщений в Windows сообщения (WSAAsyngSelect), Установка сокета в режим ожидания подключения (listen), определение IP-адреса и имени компьютера (gethostname и gethostbyname). 26 При завершении программ (событие FormClosed): закрытие сокета (closesocket), освобождение библиотеки WindowsSocket (WSACleanup). При нажатии кнопки «Подключиться» программы «Клиент»: преобразование содержимого полей ввода из типа String в массив типа char, если заполнено поле IP-адрес, то преобразуем адрес в 4-х байтное число (inet_addr), если поле IP-адрес не заполнено, но заполнено поле имя компьютера, то определим адрес по имени компьютера (gethostbyname), подключение к серверу (connect), инициализация преобразования сетевых сообщений в Windows сообщения (WSAAsyngSelect). При нажатии кнопки «Отправить» обеих программ: преобразование содержимого поля ввода из типа String в массив типа char. отправка сообщения (send). При нажатии кнопки «Отключиться» программы «Клиент»: отключение от сервера (shutdown). Для определения момента подключения или отключения и для приема сообщений в программах переопределена функция обработки Windows сообщений. Если код сообщения равен WSA_ACCEPT, а параметр сообщения FD_ACCEPT, то это означает, что к серверу пытается подключиться клиент. В этом случае для создание канала вызываем функцию accept, в качестве кода возврата будет указан виртуальный сокет, все последующие операции производятся именно с этим сокетом. В качестве первой операции выполняется инициализация преобразования сетевых сообщений в Windows сообщения (WSAAsyngSelect). Если код сообщения равен WSA_NETEVENT, а параметр сообщения FD_READ, то получаем текст пришедшего сообщения с помощью функции recv. В конце переопределенной функции вызывается старая функция обработки сообщений Windows. После вызова функции проверяется код возврата, если он окажется ошибочным, то выдается соответствующее сообщение. Текст программы «Клиент», использующей протокол ТСР, приведен в приложении 3, «Сервер» в приложении 4. 27 2.5. Контрольные вопросы. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. Для чего используется функция WSAStartup. Для чего используется функция WSACleanup. Для чего используется функция WSAGetLastError. Для чего используется функция socket. Для чего используется функция bind. Для чего используется функция gethostname. Для чего используется функция gethostbyname. Для чего используется функция inet_addr. Для чего используется функция sendto. Для чего используется функция recvfrom. Для чего используется функция listen. Для чего используется функция accept. Для чего используется функция connect. Для чего используется функция send. Для чего используется функция recv. Для чего используется функция WSAAsyncSelect. С помощью какой функции происходит инициализация библиотеки Windows Socket. С помощью какой функции можно получить код ошибки последней операции. С помощью какой функции создается сокет. С помощью какой функции осуществляется привязка адреса к сокету. С помощью какой функции осуществляется определение имени собственного компьтера. С помощью какой функции осуществляется определение IP-адреса компьютера по доменному имени. С помощью какой функции осуществляется передача данных без канала с использованием сокетов. С помощью какой функции осуществляется прием данных без канала с использованием сокетов. С помощью какой функции создается канал со стороны клиента с использованием сокетов. С помощью какой функции создается канал со стороны сервера с использованием сокетов. С помощью какой функции осуществляется передача данных с каналом с использованием сокетов. С помощью какой функции осуществляется прием данных с каналом с использованием сокетов. 28 3. Исполь ование каналов Mailslot и Pipe для ра работки сетевых программ Недостатком использования протоколов для обмена информацией между программами, запущенными на разных компьютерах являются: наличие различных протоколов и необходимость для различных протоколов разрабатывать различные участки программ; достаточно сложная работа с сокетами; неудобная адресация. Альтернативой использованию протоколов обмена информацией являются канала mailslot и pipe. 3.1. Исполь ование каналов mailslot Работа с данными каналами аналогична работе с файлами. Mailslot, определенным образом, аналогичен передаче данных как без канала, так и с каналом. По аналогии с бесканальным обменом информацией, можно использовать широковещательные адреса, а по аналогии с канальным – необходимо сначала подключиться, а затем передавать и принимать данные, в конце передачи данных нужно отключиться. К сожалению mailslot способен передавать информацию только в одну сторону, для двухсторонней передачи данных нужно создавать 2 mailslot-a в противоположных направлениях. Для создания mailslot-a на сервере используется функция CreateMailslot. Она имеет 4 параметра. Первый параметр является наиболее важным, он задает имя mailslot-a. Формат этого параметра следующий: \\.\mailslot\имя, имя придумываем сами, от одной до тридцати одной буквы, цифры и знака подчеркивания (в языка С, С++ и их наследниках обратный слеш нужно удваивать). Второй параметр определяет максимальный размер сообщений, для широковещательных сообщений от 0 до 400 байт, для остальных от 0 до 4000, если указан 0, то подразумевается максимальное значение. С помощью третьего параметра можно задать время ожидания для операции чтения в миллисекундах, по истечении которого функция завершится с ошибкой, если указать в этом параметре значение MAILSLOT_WAIT_FOREVER, ожидание будет бесконечным. Последний четвертый параметр задает адрес структуры защиты, который позволяет подключаться к каналу использую авторизацию, к сожалению объем данного пособия мал, чтобы рассмотреть вопросы авторизации, данные вопросы описаны в системе документации MSDN, а мы будем работать без авторизации, для этого будем использовать в этом параметре значение NULL. В случае успешного выполнения функцией CreateMailslot возвращается целое положительное число типа HANDLE – идентификатор mailslot-а, это 29 значение нужно использовать в последующих функциях, при ошибке – значение INVALID_HANDLE_VALUE, код ошибки можно определить при помощи функции GetLastError. В качестве примера использования функции приведем следующий текст: hMailslot = CreateMailslot("\\\\.\\mailslot\\MyMailslot", 0, MAILSLOT_WAIT_FOREVER, NULL); После создания mailslot-a сервером, клиент должен к нему подключиться. Для подключения применяется функция CreateFile. Эта функция, кроме подключения к mailslot-у может выполнять много других функций, из-за ее универсальности у нее 7 параметров, большинство из которых для mailslot-а может принимать строго определенные значения, поэтому мы рассмотрим только эти значения, остальные возможности данной функции рассмотрены в MSDN. Первый параметр задает имя компьютера и имя mailslot-a, к которому подключаемся. Формат этого параметра следующий: \\ИмяКомпьютера\mailslot\ИмяMailslota. При использовании широковещательного сообщения вместо имени компьютера следует использовать имя домена или *. Если используется имя домена, то сообщения будут передаваться всем компьютерам домена, а если *, то все компьютерам сети. Второй параметр для mailslot-a должен быть GENERIC_WRITE. Третий параметр – FILE_SHARE_READ. Четвертые параметр – NULL. Пятый параметр – OPEN_EXISTING. Шестой параметр – 0. Седьмой параметр – NULL. Также, как и функция CreateMailslot, функция CreateFile в случае успешного выполнения возвращает целое положительное число типа HANDLE – идентификатор mailslot-а, при ошибке – значение INVALID_HANDLE_VALUE, код ошибки можно определить при помощи функции GetLastError. В качестве примера использования функции приведем следующий текст: hMailslot = CreateFile("\\\\MyServer\\mailslot\\MyMailslot", GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); Для отправки сообщения по mailslot-у используется функция WriteFile. Эта функция имеет 5 параметров. Первый параметр – идентификатор mailslot-а, значение, которое вернула функция CreateMailslot или CreateFile. Второй параметр – указатель на область передаваемых данных. Третий параметр – размер передаваемых данных в байтах. Четвертый параметр – указатель на целочисленную переменную, в которую заносится количество реально отправленных байт. Пятый параметр нужен для асинхронной передачи данных, мы ей пользоваться не будем, поэтому используем значение NULL. Возвращает функция WriteFile логическое 30 значение – истина, если данные отправлены без ошибки, ложь – если при отправке произошла ошибка, код ошибки можно получить с помощью функции GetLastError. В качестве примера использования функции WriteFile можно привести следующий фрагмент кода. char a[100] = "Проба пера”; unsigned int n; bool l; l = WriteFile(hMailslot, a, strlen(a)+1, &n, NULL); if (l) { // отправлено n байт } else { int e = GetLastError(); // произошла ошибка с кодом e } Для получения сообщения используется функция ReadFile, она имеем такие же параметры и возвращаемое значение как и функция WriteFile. Для определения момента получения данных можно использовать функцию GetMailslotInfo, которая предназначена для определения состояния mailslot-а. Эта функция имеет 5 параметров. Первый параметр – идентификатор mailslot-а. Остальные 4 параметра – указатели на целочисленные беззнаковые переменные, которые после выполнения функции будут иметь следующие значения: максимальный размер сообщения; размер следующего сообщения; количества сообщений; времени ожидания. Наиболее важным значением является количество сообщений, если оно больше нуля, то значит, что-то пришло и можно вызывать функцию ReadFile. Если некоторый из вышеперечисленных значений нас не интересуют, то можно поставить NULL. В качестве примера использования функций GetMailslotInfo и ReadFile можно привести следующий фрагмент кода. char a[4001]; unsigned int n = 0; bool l; GetMailslotInfo(hMailslot, NULL, NULL, &n, NULL); if (n > 0) { l = ReadFile(hMailslot, a, 4000, &n, NULL); if (l) { // получено n байт } else { int e = GetLastError(); 31 // произошла ошибка с кодом e }/ } else { // ничего не получено } Данный код можно вызывать в цикле или по таймеру. Для закрытия канала используется функция CloseHandle, в качестве единственного параметра следует указать идентификатор mailslot-а. На рис. 3.1 показан дизайн программ, использующих mailslot. Программы две: клиент и сервер, но дизайн у них одинаковый, отличие только в заголовке окна, поэтому рисунок один. Рис. 3.1. Дизайн программ клиент и сервер, использующих mailslot Рассмотрим основные действия программ. При старте программ (событие Load формы) выполняются создание mailslot-а с именем client для клиента и server для сервера (функция CreateMailslot). При нажатии кнопки «Отправить»: преобразование содержимого полей ввода из типа String в массив типа char, формирование строки подключения вида \\ИмяКоппьютера\mailslot\ИмяMailslot-a, подключение к mailslot-у (CreateFile), отправка сообщения (WriteFile). отключение от mailslot-а (CloseHandle). 32 Для определения момента приема сообщений в программах предусмотрен таймер, функция обработки которого выполняет следующие действия: проверка наличия полученных сообщений (GetMailslotInfo); если полученные сообщения есть, то получение данных (ReadFile). После вызова функции проверяется код возврата, если он окажется ошибочным, то выдается соответствующее сообщение. Текст программы «Клиент», использующей mailslot, приведен в приложении 5, программа «Сервер» практически полностью совпадает с программой «Клиент» с той лишь разницей, что следует заманить слово client на слово сервер и обратно. 3.2. Исполь ование каналов pipe В предыдущем пункте мы рассмотрели канал передачи данных mailslot. К достоинствам данного канала можно отнести: возможность широковещательной посылки данных; относительную простоту разработки приложений. Однако у mailslot-а имеются существенные недостатки: маленький объем передаваемых данных (всего 4 Кбайт); односторонность передачи данных; невозможность проследить подключении и отключении. В принципе эти недостатки можно обойти, однако это существенно усложняет приложение и поэтому возможно следует использовать другой вид канала – pipe. Существуют две разновидности каналов Pipe – именованные (Named Pipes) и анонимные (Anonymous Pipes). Однако анонимные каналы используются только в пределах одного компьютера и нам не подходят. Мы будем использовать исключительно именованные каналы. Как и в случае канала mailslot, работа с каналом pipe аналогична работе с файлами, некоторые функции те же самые, однако возможны другие параметры, поэтому рассмотрим их подробно. В отличии от mailslot-а, pipe является двухсторонним каналом, однако «Клиент» и «Сервер» достаточно сильно отличаются друг от друга. «Сервер» создает pipe, а «Клиент» подключается к нему. Для создания pipe-a на сервере используется функция CreateNamedPipe. Она имеет 8 параметров. Первый параметр является наиболее важным, он задает имя pipe-a. Формат этого параметра следующий: \\.\ pipe\имя, как и в случае с mailslotом, имя придумываем сами, от одной до тридцати одной буквы, цифры и знака подчеркивания (в языка С, С++ и их наследниках обратный слеш нужно удваивать). Второй параметр – режим открытия канала, вариантов режимов открытия достаточно много, подробно о них можно прочитать в MSDN, 33 рекомендуемое значение PIPE_ACCESS_DUPLEX, оно означает, что канал будет двухсторонний. Третий параметр – режим работы канала, также много вариантов, рекомендуемый вариант PIPE_TYPE_BYTE|PIPE_NOWAIT, он означает, что прием и передача данных будет происходить байтами без ожидания. Вариант «без ожидания» означает, что любая операция приема или передачи данных лишь начинает операцию, выполнение операции будет происходить параллельно с продолжение работы программы. При таком режиме работы не происходит «зависание» программы, как в случае с ожиданием, когда при выполнении операции получения или передачи данных происходит останов выполнения программы до момента окончания операции. Четвертый параметр – максимальное количество клиентов, подключенных к серверу, рекомендуемое значение PIPE_UNLIMITED_INSTANCES, оно означает, что количество клиентов неограниченно. Пятый и шестой параметры означают размер выходного и входного буферов в байтах, рекомендуемое значение – 1024. Седьмой параметр – время ожидания окончания в миллисекундах. Данный параметр имеет смысл только в режиме с ожидание, рекомендуемое значение – 5000. Последний восьмой параметр задает адрес структуры защиты, как и в случае с mailslot-ом, позволяет подключаться к каналу использую авторизацию, мы будем работать без авторизации, для этого будем использовать в этом параметре значение NULL. В случае успешного выполнения функцией CreateNamedPipe возвращается целое положительное число типа HANDLE – идентификатор pip-а, это значение нужно использовать в последующих функциях, при ошибке – значение INVALID_HANDLE_VALUE, код ошибки можно определить при помощи функции GetLastError. В качестве примера использования функции приведем следующий текст: hPipe = CreateNamedPipe(L"\\\\.\\pipe\\MyServ", PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE|PIPE_NOWAIT, PIPE_UNLIMITED_INSTANCES, 1000, 1000, 5000, NULL); После создания pipe-a сервером, клиент должен к нему подключиться. Для подключения применяется функция CreateFile. Мы уже встречались с этой функцией, когда рассматривали mailslot, эта функция имеет 7 параметров, большинство из которых для pipe-а может принимать строго определенные значения, поэтому мы рассмотрим только эти значения, остальные возможности данной функции рассмотрены в MSDN. Первый параметр задает имя компьютера и имя pipe-a, к которому подключаемся. Формат этого параметра следующий: \\ИмяКомпьютера\ 34 pipe \ИмяMailslota. Второй параметр для pipe-a должен быть GENERIC_READ|GENERIC_WRITE. Третий параметр – FILE_SHARE_READ. Четвертые параметр – NULL. Пятый параметр – OPEN_EXISTING. Шестой параметр – FILE_FLAG_OVERLAPPED. Седьмой параметр – NULL. Также, как функции CreateMailslot и CreateNamedPipe, функция CreateFile в случае успешного выполнения возвращает целое положительное число типа HANDLE – идентификатор pipe-а, при ошибке – значение INVALID_HANDLE_VALUE, код ошибки можно определить при помощи функции GetLastError. В качестве примера использования функции приведем следующий текст: hPipe = CreateFile(L"\\\\MyComp\\pipe\\MyServ", GENERIC_READ| GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING,FILE_FLAG_OVERLAPPED, NULL); В ответ на вызов клиентом CreateFile, сервер должен вызвать функцию ConnectNamedPipe, данная функция создает реализацию канала. Она имеет два параметра. Первый параметр – идентификатор pipe-а. Второй параметр мы использовать не будем и подставим NULL. Возвращает функция логическое значение, однако оно не очень интересно, больший интерес вызывает значение, Которое вернет функция GetLastError. Эта функция может вернуть значение: ERROR_PIPE_LISTENING – если с момента предыдущего вызова функции ConnectNamedPipe к серверу не подключился ни один клиент; ERROR_PIPE_CONNECTED – если клиент подключился; ERROR_NO_DATA – если клиент отключился; иное значение – ошибка. Если произошло отключение клиента, то следует вызвать функцию DisconnectNamedPipe с единственным параметром – идентификатором pipe-a, для освобождения ресурсов реализации канала с данным клиентом. В качестве примера использования функций ConnectNamedPipe и DisconnectNamedPipe можно привести следующий код программы: unsigned int fConnect = 0; ConnectNamedPipe(hPipe, NULL); unsigned int LastError = GetLastError(); if (LastError == ERROR_PIPE_LISTENING) { // Нет подключения fConnect = 0 } // if if (LastError == ERROR_PIPE_CONNECTED) { if (fConnect == 0) { // Клиент подключен } // if fConnect = 1; 35 } // if if (LastError == ERROR_NO_DATA) { // Клиент отключен DisconnectNamedPipe(hPipe); fConnect = 0; } // if Для отправки сообщения по pipe-у так же, как и по mailslot-у, используется функция WriteFile. Эта функция имеет 5 параметров. Первый параметр – идентификатор mailslot-а, значение, которое вернула функция CreateMailslot или CreateFile. Второй параметр – указатель на область передаваемых данных. Третий параметр – размер передаваемых данных в байтах. Четвертый параметр – указатель на целочисленную переменную, в которую заносится количество реально отправленных байт. Пятый параметр нужен для асинхронной передачи данных, мы ей пользоваться не будем, поэтому используем значение NULL. Возвращает функция WriteFile логическое значение – истина, если данные отправлены без ошибки, ложь – если при отправке произошла ошибка, код ошибки можно получить с помощью функции GetLastError. В качестве примера использования функции WriteFile можно привести следующий фрагмент кода. char a[100] = "Проба пера”; unsigned int n; bool l; l = WriteFile(hMailslot, a, strlen(a)+1, &n, NULL); if (l) { // отправлено n байт } else { int e = GetLastError(); // произошла ошибка с кодом e } Для получения сообщения используется функция ReadFile, она имеем такие же параметры и возвращаемое значение, как и функция WriteFile, за исключением последнего параметра, который является указателем на структуру OVERLAPPED. Данная структура нужна для получения данных «без ожидания», в нее записывается информация о процессе получения данных, т.к. функция ReadFile только начинает процесс получения данных. Для наблюдения за процессом необходимо в цикле или по таймеру вызывать функцию GetOverlappedResult, которая имеет 4 параметра. Первый параметр – идентификатор pipe-a. Второй параметр – указатель на структуру OVERLAPPED, такой же, как в функции ReadFile. Третий параметр – указатель на целую беззнаковую переменную, в которую будет 36 занесено количество полученных байт. Четвертый параметр мы использовать не будем и укажем там NULL. Для закрытия канала используется функция CloseHandle, в качестве единственного параметра следует указать идентификатор pipe-а. Пример программ приема и передачи данных по каналу pipe, аналогично примерам с протоколами UDP и TCP, данный пример состоит из двух программ «Клиент» и «Сервер». Дизайн программы «Клиент» приведен на рис. 3.2, дизайн программы «Сервер» приведен на рис. 3.3. Рис. 3.2. Дизайн программы «Клиент», использующей канал pipe Рис. 3.3. Дизайн программы «Сервер», использующей канал pipe Рассмотрим основные действия программ. При старте программ «Сервер» (событие Load) создается канал pipe (функция CreateNamedPipe). 37 При завершении программ «Сервер» (событие FormClosed) канал закрывается (функция CloseHandle). При нажатии кнопки «Подключиться» программы «Клиент» выполняются следующие действия: преобразование содержимого поля ввода «Имя коипьютера» из типа String в массив типа char; формирование строки подключения в виде "\\\\ИмяСервера\\pipe\\ИмяPipe"; подключение к серверу (функция CreateFile); инициализация чтения данных (ReadFile), данных в этот момент времени еще нет, но операция «без ожидания», поэтому здесь мы ее начнем, а программа продолжит выполнение дальше, как данные поступят, так программа их и считает; включение таймера для проверки поступления знаков. При нажатии кнопки «Отправить» обеих программ: преобразование содержимого поля ввода «Текст сообщения» из типа String в массив типа char; отправка сообщения (WriteFile). При нажатии кнопки «Отключиться» программы «Клиент» – отключение от сервера (CloseHahdle). Для определения момента подключения или отключения и для приема сообщений в программах создан таймер и определена функция обработки таймера. В программе «Сервер» данная функция выполняет следующие действия: вызывает функцию подключения клиента (ConnectNamedPipe); если код ошибки функции подключения клиента равен ERROR_PIPE_LISTENING, то подключения нет; если код равен ERROR_PIPE_CONNECTED, то произошло подключение; если код равен ERROR_NO_DATA, то произошло отключение, следует вызвать функцию DisconnectNamedPipe для освобождения ресурсов подключения; если есть подключение, то инициализация чтения данных (ReadFile). В программе «Клиент» по таймеру только проверяется ход выполнения чтения (функция GetOverlappedResult). После вызова функции проверяется код возврата, если он окажется ошибочным, то выдается соответствующее сообщение. Текст программы «Клиент» приведен в приложении 6, «Сервер» в приложении 7. 38 3.3. Контрольные вопросы 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. Для чего используется функция CreateMailslot. Для чего используется функция CreateNamedPipe. Для чего используется функция CreateFile. Для чего используется функция ReadFile. Для чего используется функция WriteFile. Для чего используется функция CloseHandle. Для чего используется функция GetMailslotInfo. Для чего используется функция ConnectedNamePipe Для чего используется функция DisconnectedNamedPipe. С помощью какой функции создается Maislot со стороны сервера С помощью какой функции создается Pipe со стороны сервера С помощью какой функции осуществляется подключение к Maislot-у со стороны клиенты С помощью какой функции осуществляется подключение к Pipe-у со стороны клиенты С помощью какой функции осуществляется передача данных по Mailslot-у С помощью какой функции осуществляется передача данных по Pipe-у С помощью какой функции осуществляется прием данных по Pipe-у С помощью какой функции осуществляется прием данных по Mailslot-у С помощью какой функции осуществляется отключение по Mailslot-у С помощью какой функции осуществляется отключение по Pipe-у С помощью какой функции можно определить пришло ли сообщение по Mailslot-у С помощью какой функции можно определить пришло ли сообщение по Pipe-у Что входит в адрес в Mailslot-e Что входит в адрес в Pipe-e Что обозначает собственный компьютер в Mailslot-e Что обозначает собственный компьютер в Pipe-e Что обозначает широковещательный адрес в Mailslot-e Что обозначает широковещательный адрес в Pipe-e 39 4. Исполь ование библиотеки WinInet для ра работки программ, работающих в глобальной сети В разделах 2 и 3 мы рассмотрели несколько средства передачи данных между программами, запущенными на разных компьютерах. Все эти средства хорошо работают в локальной сети. Протоколы UDP и TCP работают как в локальной, так и в глобальной сети, но при условии единого пространства всех IP-адресов. Это условие означает, что в глобальной сети IP-адреса всех компьютеров, входящих в систему, должны быть истинными. Такое требование не всегда выполнимо, часто бывает так, что количество истинные IP-адресов ограничено, поэтому хотелось бы, чтобы истинный IP-адрес был только у одного компьютера, на котором работает программа «Сервер», а на остальных компьютерах были бы внутренние IP-адреса. Такую конфигурацию позволяет создать библиотека WinInet (сокращение от Windows Internet). Ниже рассмотрим основные функции библиотеки. 4.1. Функция InternetOpen Функция InternetOpen инициализирует WinInet и возвращает идентификатор, который необходим для вызова других функций WinInet. В случае неудачи возвращается NULL. Функция имеет 5 параметров. Первый параметр задает имя нашего приложения, которое будет использовать библиотеку WinInet, в качестве этого параметра можно использовать любой текст, например “MyApplication”. Второй параметр задаёт необходимый тип доступа (прямой или через прокси). Возможные варианты данного параметра: INTERNET_OPEN_TYPE_DIRECT – прямой доступ в Internet, без прокси; INTERNET_OPEN_TYPE_PROXY – доступ в интернет через прокси; INTERNET_OPEN_TYPE_PRECONFIG – тип доступа установлен в реестре. Третий параметр указывает имя или IP-адрес и порт прокси-сервера. Записывается в виде “Server1:8080” или “192.168.0.1:8080”, где до двоеточия имя или IP-адрес прокси-сервера, а после двоеточия порт. Четвертые параметр указывает список компьютеров, для которых не следует использовать прокси-сервер, например строка “192.168.0.*” говорит о том, что для доступа к компьютерам, чьи адреса начинаются с 192.168.0 не следует использовать прокси-сервер, а ко всем остальным следует. Третий и четвертый параметры следует указывать только, если второй параметр равен INTERNET_OPEN_TYPE_PROXY, иначе следует указывать NULL. Пятый параметр – флаг, здесь можно использовать нулевое значение. 40 В качестве примера использования функции InternetOpen можно привести можно привести следующий фрагмент кода: hInet = InternetOpen(L"MyApplicat", INTERNET_OPEN_TYPE_PROXY, “192.168.0.1:8080”, “192.168.0.*”, 0); if (hInet == NULL) { // Ошибка InternetOpen } // if 4.2. Функция InternetConnect Функция InternetOpen создает интернет-сессию для заданного сайта и возвращает идентификатор сессии, который необходим для вызова некоторых последующих функций. В случае неудачи возвращается NULL. Функция имеет 8 параметров. Первый параметр – идентификатор, который вернула функция InternetOpen. Второй параметр – имя или IP-адрес сервера. Третий параметр – порт сервера. Четвертый параметр – имя пользователя. Пятый параметр – пароль. Шестой параметр – тип сервиса, возможны следующие варианты: INTERNET_SERVICE_HTTP – HTTP сервис, INTERNET_SERVICE_FTP – FTP сервис и некоторые другие. Мы будем использовать сервис INTERNET_SERVICE_HTTP. Седьмой параметр – флаг, можно использовать нулевое значение. Восьмой параметр – контекст, также можно использовать нулевое значение. В качестве примера использования функции InternetConnect можно привести следующий фрагмент кода: hSession = InternetConnect(hInet, “www.sartasov.com”, SERVER_PORT, L"Sartasov", L"123", INTERNET_SERVICE_HTTP, 0, 0); if (hSession == NULL) { // Ошибка InternetConnect } // if В данном примере сервер, имя пользователя и пароль вымышлены. 4.3. Функция HttpOpenRequest Функция HttpOpenRequest создает запрос к серверу и возвращает идентификатор запроса, который необходим для вызова некоторых последующих функций. В случае неудачи возвращается NULL. Функция имеет 8 параметров. Первый параметр – идентификатор сессии, который вернула функция InternetConnection. Второй параметр – имя команды запроса возможны следующие варианты: "GET" – запрос на получение данных от сервера и "POST" – запрос на отправку данных серверу с возможным получением ответных данных. Мы будем использовать "POST". 41 Третий параметр – имя целевого объекта, отправляемого на сервер. Мы будем использовать пустую строку. Четвертый параметр – используемая версия HTTP протокола. Мы будем использовать "HTTP/1.1" Пятый параметр – адрес предыдущего запроса, в данный момент не используется, рекомендуется подставлять пустую строку. Шестой параметр – Определяет тип содержимого допускаемого клиентской стороной. Мы будем указывать NULL, это означает, что любой тип допустим. Седьмой параметр – флаг. Возможных флагов много, рекомендуется использовать комбинацию следующих флагов: INTERNET_FLAG_EXISTING_CONNECT – используем существующее соединение; INTERNET_FLAG_NO_AUTO_REDIRECT – не переадресовываемся с сервера; INTERNET_FLAG_KEEP_CONNECTION – по окончании запроса сохраняем соединение. Восьмой параметр – контекст, также можно использовать нулевое значение. В качестве примера использования функции HttpOpenRequest можно привести следующий фрагмент кода: hRequest = HttpOpenRequest(hSession, L"POST", L"", L"HTTP/1.1", L"", NULL, INTERNET_FLAG_EXISTING_CONNECT | INTERNET_FLAG_NO_AUTO_REDIRECT | INTERNET_FLAG_KEEP_CONNECTION, 0); if (hRequest == NULL) { // Ошибка HttpOpenRequest } // if 4.4. Функция HttpSendRequest Функция HttpSendRequest отправляет запрос на сервер. Она имеет 5 параметров. Первый параметр – идентификатор запроса, который вернула функция HttpOpenRequest. Второй параметр – указатель на дополнительные заголовки, можно использовать значение NULL. Третий параметр – размер дополнительных заголовков в байтах. Четвертые параметр – указатель на область передаваемых данных. Пятый параметр – размер передаваемых данных в байтах. В качестве примера использования функции HttpSendRequest можно привести следующий фрагмент кода: Code = HttpSendRequest(hRequest, NULL, 0, “Проба пера”, 12); if (!Code) { Code = GetLastError(); 42 // Ошибка HttpSendRequest с кодом Code } 4.5. Функция HttpQueryInfo После того, как запрос был отправлен серверу через интернет, следует ожидать ответ. Однако интернет – сеть относительно медленная, поэтому ответ придет возможно не слишком быстро и возможно по частям. Однако и анализировать ответ тоже можно по частям. Функция HttpQueryInfo показывает, какие части ответа уже пришли. Эта функция имеет 5 параметров. Первый параметр – идентификатор запроса. Второй параметр – флаги частей ответа. Основными флагами являются: HTTP_QUERY_STATUS_CODE – код ошибки ответа (0–299 – нет ошибки, 300–399 – предупреждение, 400–499 – ошибка, 500–599 – фатальная ошибка, более подробную информацию по кодам ошибок можно получить в MSDN, на интернет странице http://msdn.microsoft.com/en-us/library/windows/desktop/aa384325 (v=vs.85).aspx); HTTP_QUERY_RAW_HEADERS_CRLF – заголовок ответа; HTTP_QUERY_CONTENT_LENGTH – размер ответа в байтах. С остальными флагами можно познакомиться в MSDN, на интернет странице http://msdn.microsoft.com/en-us/library/windows/desktop/aa385351 (v=vs.85).aspx. Третий параметр – указатель на область памяти, в которую будет записана часть ответа. Четвертый параметр – указатель на целочисленную беззнаковую переменную, в которую заносится размер области памяти. Пятый параметр – индекс, рекомендуемое значение NULL. В случае успешной работы функция возвращает значение истина, при ошибке – ложь. В качестве примера использования функции HttpQueryInfo можно привести следующий фрагмент кода, получающий код возврата запроса: char Buf[11]; unsigned int Len = 10; int Code = HttpQueryInfo(hRequest, HTTP_QUERY_STATUS_CODE, Buf, &Len, NULL); if (!Code) { // Ошибка HttpQueryInfo при определении кода возврата. return; } // if Code = _wtoi(Buf); if (Code >= 300) { // От сервера получен ошибочный код возврата Code 43 return; } // if 4.6. Функция InternetReadFile Рассмотренная в предыдущем пункте, функция HttpQueryInfo позволяет получать части ответа, но только служевные части, а основную часть ответа можно получить с помощью функции InternetReadFile. Данная функция имеет имеет 4 параметра. Первый параметр – идентификатор запроса. Второй параметр –указатель на область памяти, в которую будет записаны полученные данные. Третий параметр – размер области. Четвертый параметр – указатель на переменную, в которую будет записано кол-во реально пришеджих байт. В случае успешной работы функция возвращает значение истина, при ошибке – ложь. В качестве примера использования функции можно привести следующий фрагмент кода: char Buf[11], *Buf2; int Len = 10, Len2; Code = HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_LENGTH, Buf, &Len, NULL); if (!Code) { // Ошибка HttpQueryInfo(HTTP_QUERY_CONTENT_LENGTH) return; } // if Len = _wtoi(Buf); if (Len <= 0) { // Ошибка в размере ответа return; } // if Buf2 = (char *)malloc(Len+1); if (Buf2 == NULL) { // Недостаточно памяти return; } // if Code = InternetReadFile(hRequest, Buf2, Len, &Len2); if (!Code) { // Ошибка при получении ответа return; } // if if (Len != Len2) { 44 // Должно было прийти Len байт } // if 4.7. Пример программ, исполь ующих библиотеку WinInet Аналогично всем предыдущим примерам, данный пример состоит из двух программ «Клиент» и «Сервер». Дизайн программы «Клиент» приведен на рис. 4.1, дизайн программы «Сервер» на рис. 4.2. Рис. 4.1. Дизайн программы «Клиент», использующих библиотеку WinInet Рис. 4.2. Дизайн программы «Сервер», использующих библиотеку WinInet Рассмотрим основные действия программы «Клиент». При нажатии кнопки «Отправить» выполняются следующие действия: преобразование содержимого полей ввода из типа String в массив типа char; инициализация библиотеки WinInet (функция InternetOpen); 45 создание интернет-сессии (InternetConnect); создание запроса к серверу (HttpOpenRequest); отправка запроса (HttpSendRequest); включение таймера для приема ответа. По таймеру выполняются следующие действия: получаем код ответа, если код ответа больше или равен 300, то ошибка (функция HttpQueryInfo с параметром HTTP_QUERY_STATUS_CODE); получаем заголовок ответа (функция HttpQueryInfo с параметром HTTP_QUERY_RAW_HEADERS_CRLF); получает размер ответа (функция HttpQueryInfo с параметром HTTP_QUERY_CONTENT_LENGTH); получаем ответ (функция InternetReadFile). Программа «Сервер» аналогична соответствующей программе, использующей протокол TCP. Основные действия этой программы описаны в пункте 2.4, поэтому здесь мы их рассматривать не будем. Текст программы «Клиент», использующей библиотеку WinInet, приведен в приложении 8, «Сервер» в приложении 9. 4.8. Контрольные вопросы. 1. Для чего используется функция InternetOpen. 2. Для чего используется функция InternetConnect. 3. Для чего используется функция HttpOpenRequest. 4. Для чего используется функция HttpSendRequest. 5. Для чего используется функция HttpQueryInfo. 6. Для чего используется функция InternetReadFile. 7. С помощью какой функции инициализируется библиотека WinInet. 8. С помощью какой функции создается интернет-сессия. 9. С помощью какой функции создается интернет-запрос. 10. С помощью какой функции можно послать данные с использованием библиотеки WinInet. 11. С помощью какой функции можно получить информацию о пришедших данных с использованием библиотеки WinInet. 12. С помощью какой функции можно получить пришедшие данные с использованием библиотеки WinInet. 46 Заключение В данное учебное пособие посвящено разработке вычислительных систем, работающих на нескольких компьютеров. В качестве средств передачи информации между компьютерами используются компьютерные сети и телекоммуникации. Пособие состоит из четырех глав. В первой главе пособия описаны аппаратные и программные средства, необходимые для построения, настройки и оптимизации сети. Во второй главе рассматриваются вопросы разработки вычислительных систем с использованием сетевых протоколов передачи информации UDP и TCP. В третьей главе описаны способы передачи и приема информации с помощью каналов Mailslot и Pipe. В четвертой главе рассматриваются отличия передачи информации в локальных и глобальных компьютерных сетях, для работы с глобальными сетями используется библиотека функций WinInet. В пособии рассмотрены практические примеры разработки элементов вычислительных сетей, работающих на различных компьютерах, связанных друг с другом сетью. Пособие ориентировано на студентов направления 230700.62 «Прикладная информатика», изучающих предмет «Вычислительные системы, сети и телекоммуникации». Для изучения данного предмета требуются знания следующих предметов: «Информатика и программирование» и «Высокоуровневые методы информатики и программирования». Знания, полученные при изучении данного предмета, могут быть использованы для изучения других предметов: «Интернет-программирование» и «Управление информационными системами». 47 Библиографический список Основной: 1. Пятибратов, А. П. Вычислительные системы, сети и телекоммуникации: учебник для вузов по специальности "Приклад. информатика в экономике" / А. П. Пятибратов, Л. П. Гудыно, А. А. Кириченко ; под ред. А. П. Пятибратова. – М. : Финансы и статистика : ИНФРА-М , 2008. – 733 с. 2. Бройдо, В. Л. Вычислительные системы, сети и телекоммуникации: учебное пособие для вузов по направлениям "Приклад. информатика", "Информ. системы в экономике" / В. Л. Бройдо – СПб.: Питер , 2008. – 766 с. 3. Олифер, В.Г. Основы компьютерных сетей / В.Г. Олифер, Н.А. Олифер. – СПб. : Питер, 2009. – 352 с. 4. Олифер, В.Г. Компьютерные сети. Принципы, технологии, протоколы: учебное пособие / В.Г. Олифер, Н.А. Олифер. – СПб. : Питер, 2007. – 958 с. Дополнительный: 5. Компьютерные системы и сети : Учеб. пособие по экон. специальностям вузов / В. П. Косарев, Л. В. Еремин, О. В. Машникова и др.; Под ред.: В. П. Косарева, Л. В. Еремина. – М. : Финансы и статистика, 1999. – 96 с. 6. Галкин, В.А. Телекоммуникации и сети : учебное пособие / В.А. Галкин, Ю.А. Григорьев. – М. : МГТУ им. Н.Э. Баумана, 2003. – 608 с. 7. Кузин, А.В. Компьютерные сети: учебное пособие / А.В. Кузин, В.М. Дёмин. – М. : ФОРУМ, 2005. – 192 с. 8. Мелехин, В.Ф. Вычислительные машины, системы и сети: учебник / В.Ф. Мелехин, Е.Г. Павловский. – М. : Академия, 2006. – 560 с. 9. Фролов, А.В. Библиотека системного программиста. В 34 томах. Т. 23: Глобальные сети компьютеров. Практическое введение в Internet, EMail, FTP, WWW и HTML, программирование для Windows Sockets. Т 27: Программирование для Windows NT / А.В. Фролов, Г.В. Фролов. – М.: "ДИАЛОГ-МИФИ", 1996-1997. – 234 с. 48 Приложения Приложение 1 Текст программы «Клиент», исполь ующей протокол UDP #pragma once #include #include #define WSA_NETEVENT (WM_USER+2) #define SERVER_PORT 0x2001 #define CLIENT_PORT 0x2011 SOCKET UDPSocket; sockaddr_in CallAddress; sockaddr_in OurAddress; namespace Client { using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; public ref class Form1 : public System::Windows::Forms::Form { public: Form1(void) { InitializeComponent(); } protected: ~Form1() { if (components) { delete components; } } private: System::Windows::Forms::TextBox^ textBox1; private: System::Windows::Forms::ListBox^ listBox2; 49 private: System::Windows::Forms::ListBox^ listBox1; private: System::Windows::Forms::Button^ button2; private: System::Windows::Forms::Label^ label1; private: System::Windows::Forms::TextBox^ textBox2; private: System::Windows::Forms::Label^ label2; private: System::Windows::Forms::TextBox^ textBox3; private: System::Windows::Forms::Label^ label3; private: System::ComponentModel::Container ^components; #pragma region Windows Form Designer generated code void InitializeComponent(void) { // Текст этой функции генерируется автоматически, поэтому // он пропущен для краткости } #pragma endregion private: System::Void Form1_Load(System::Object^ sender, EventArgs^ e) { WSADATA WSAData; int rc; char Name[101], *IpAddr, Buf[1000]; PHOSTENT phe; rc = WSAStartup(MAKEWORD(2,0), &WSAData); if (rc != 0) { listBox1->Items->Add("Ошибка инициализации WSAStartup"); return; } // if UDPSocket = socket(AF_INET, SOCK_DGRAM, 0); if (UDPSocket == INVALID_SOCKET) { listBox1->Items->Add("Протокол UDP не установлен."); } // if memset(&OurAddress, 0, sizeof(OurAddress)); OurAddress.sin_family = AF_INET; OurAddress.sin_port = CLIENT_PORT; rc = bind(UDPSocket, (LPSOCKADDR)&OurAddress, sizeof(sockaddr_in)); if (rc == SOCKET_ERROR) { listBox1->Items->Add("Адресная ошибка"); return; 50 } // if rc= WSAAsyncSelect(UDPSocket, (HWND)(this->Handle.ToInt32()), WSA_NETEVENT, FD_READ); if (rc != 0) { listBox1->Items->Add("Ошибка WSAAsyncSelect"); return; } // if gethostname(Name, 101); strcpy(Buf, "Имя компьютера "); strcat(Buf, Name); String ^ s= gcnew String(Buf); listBox1->Items->Add(s); phe = gethostbyname(Name); if (phe != NULL) { memcpy((void *)&(OurAddress.sin_addr), phe->h_addr, phe->h_length); IpAddr = inet_ntoa(OurAddress.sin_addr); strcpy(Buf, "IP-Адрес "); strcat(Buf, IpAddr); String ^ s2= gcnew String(Buf); listBox1->Items->Add(s2); } // if listBox1->Items->Add(L"Клиент запущен"); } // ------------------------------------------------------------------------------------------------protected: virtual void WndProc (Message% m) override { int rc, l=sizeof(CallAddress); wchar_t Buf[500]; if (m.Msg == WSA_NETEVENT) { if (m.LParam.ToInt32() == FD_READ) { rc = recvfrom((SOCKET)m.WParam.ToInt32(), (char *)Buf, sizeof(Buf)-1, 0, (PSOCKADDR)&CallAddress, &l); if (rc == SOCKET_ERROR) { rc = WSAGetLastError(); listBox1->Items->Add(String::Format( "Ошибка recv " "{0}", rc)); return; 51 } // if if (rc >= 1) { String ^ s= gcnew String(Buf); listBox2->Items->Add(s); } // if } // if } // if Form::WndProc( m ); } // WndProc // ------------------------------------------------------------------------------------------------private: System::Void button2_Click(System::Object^ sender, EventArgs^ e) { int rc, l, i; char Buf[500]; wchar_t Buf2[1000]; PHOSTENT phe; memset(&CallAddress, 0, sizeof(CallAddress)); CallAddress.sin_family = AF_INET; CallAddress.sin_port = SERVER_PORT; l = textBox2->Text->Length; if (l > 0) { for (i=0; i < l; i++) { Buf[i] = textBox2->Text->default[i]; Buf[i+1] = 0; } // for CallAddress.sin_addr.s_addr = inet_addr(Buf); } else { l = textBox3->Text->Length; if (l > 0) { for (i=0; i < l; i++) { Buf[i] = textBox3->Text->default[i]; Buf[i+1] = 0; } // for phe = gethostbyname(Buf); if (phe != NULL) { memcpy((void *)&(CallAddress.sin_addr), phe->h_addr, phe->h_length); } // if } else { return; } // else 52 } // else l = textBox1->Text->Length; if (l > 0) { for (i=0; i < l; i++) { Buf2[i] = textBox1->Text->default[i]; Buf2[i+1] = 0; } // for rc = sendto(UDPSocket, (char *)Buf2, 2*l+2, 0, (LPSOCKADDR)&CallAddress, sizeof(CallAddress)); if (rc == SOCKET_ERROR) { rc = WSAGetLastError(); listBox1->Items->Add(String::Format("Ошибка recv " "{0}", rc)); return; } // if String ^ s= gcnew String(Buf2); listBox2->Items->Add(s); } // if }// ---------------------------------------------------------------------------------------private: System::Void Form1_FormClosed(System::Object^ sender, System::Windows::Forms::FormClosedEventArgs^ e) { closesocket(UDPSocket); WSACleanup(); } //Form1_FormClosed }; } 53 Приложение 2 Текст программы «Сервер», исполь ующей протокол UDP #pragma once #include #include #define WSA_NETEVENT (WM_USER+2) #define SERVER_PORT 0x2001 #define CLIENT_PORT 0x2011 SOCKET UDPSocket; sockaddr_in CallAddress; sockaddr_in OurAddress; namespace Server { using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; using namespace System::Security::Permissions; public ref class Form1 : public System::Windows::Forms::Form { public: Form1(void) { InitializeComponent(); } protected: ~Form1() { if (components) { delete components; } } private: System::Windows::Forms::ListBox^ listBox1; private: System::Windows::Forms::ListBox^ listBox2; 54 private: System::Windows::Forms::TextBox^ textBox1; private: System::Windows::Forms::Button^ button1; private: System::ComponentModel::Container ^components; #pragma region Windows Form Designer generated code void InitializeComponent(void) { // Текст этой функции генерируется автоматически, поэтому // он пропущен для краткости } #pragma endregion // ------------------------------------------------------------------------------------------------private: System::Void Form1_Load(System::Object^ sender, EventArgs^ e) { WSADATA WSAData; int rc; char Name[101], *IpAddr, Buf[1000]; PHOSTENT phe; rc = WSAStartup(MAKEWORD(2,0), &WSAData); if (rc != 0) { listBox1->Items->Add("Ошибка инициализации WSAStartup"); return; } // if UDPSocket = socket(AF_INET, SOCK_DGRAM, 0); if (UDPSocket == INVALID_SOCKET) { listBox1->Items->Add("Протокол UDP не установлен."); } // if memset(&OurAddress, 0, sizeof(OurAddress)); OurAddress.sin_family = AF_INET; OurAddress.sin_port = SERVER_PORT; rc =bind(UDPSocket, (LPSOCKADDR)&OurAddress, sizeof(sockaddr_in)); if (rc == SOCKET_ERROR) { listBox1->Items->Add("Адресная ошибка"); return; } // if rc = WSAAsyncSelect(UDPSocket, (HWND)(this->Handle.ToInt32()), WSA_NETEVENT, FD_READ); 55 if (rc != 0) { listBox1->Items->Add("Ошибка WSAAsyncSelect"); return; } // if gethostname(Name, 101); strcpy(Buf, "Имя компьютера "); strcat(Buf, Name); String ^ s= gcnew String(Buf); listBox1->Items->Add(s); phe = gethostbyname(Name); if (phe != NULL) { memcpy((void *)&(OurAddress.sin_addr), phe->h_addr, phe->h_length); IpAddr = inet_ntoa(OurAddress.sin_addr); strcpy(Buf, "IP-Адрес "); strcat(Buf, IpAddr); String ^ s2= gcnew String(Buf); listBox1->Items->Add(s2); } // if listBox1->Items->Add(L"Сервер запущен"); } // Form1_Load // ------------------------------------------------------------------------------------------------protected: virtual void WndProc (Message% m) override { int rc, l=sizeof(CallAddress); wchar_t Buf[500]; if (m.Msg == WSA_NETEVENT) { if (m.LParam.ToInt32() == FD_READ) { rc = recvfrom((SOCKET)m.WParam.ToInt32(), (char *)Buf, sizeof(Buf)- 1, 0, (PSOCKADDR)&CallAddress, &l); if (rc == SOCKET_ERROR) { rc = WSAGetLastError(); listBox1->Items->Add(String::Format("Ошибка " "recvfrom {0}", rc)); return; } // if if (rc >= 1) { String ^ s= gcnew String(Buf); 56 listBox2->Items->Add(s); } // if } // if } // if Form::WndProc( m ); } // WndProc // ------------------------------------------------------------------------------------------------private: System::Void button1_Click(System::Object^ sender, EventArgs^ e) { int rc, i, l; wchar_t Buf[501]; l = textBox1->Text->Length; if (l == 0) return; for (i=0; i < l; i++) { Buf[i] = textBox1->Text->default[i]; Buf[i+1] = 0; } // for rc = sendto(UDPSocket, (char *)Buf, 2*l+2, 0, (PSOCKADDR)&CallAddress, sizeof(CallAddress)); if (rc == SOCKET_ERROR) { rc = WSAGetLastError(); listBox1->Items->Add(String::Format( "Ошибка sendto {0}", rc )); return; } // if listBox1->Items->Add(textBox1->Text); } // button1_Click // ------------------------------------------------------------------------------------------------private: System::Void Form1_FormClosed(System::Object^ sender, System::Windows::Forms::FormClosedEventArgs^ e) { closesocket(UDPSocket); WSACleanup(); } //Form1_FormClosed }; } 57 Приложение 3 Текст программы «Клиент», исполь ующей протокол ТСP #pragma once #include #include #define WSA_NETEVENT (WM_USER+2) #define SERVER_PORT 0x2021 #define CLIENT_PORT 0x2031 SOCKET TCPSocket; sockaddr_in CallAddress; sockaddr_in OurAddress; namespace Client { using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; public ref class Form1 : public System::Windows::Forms::Form { public: Form1(void) { InitializeComponent(); } protected: ~Form1() { if (components) { delete components; } } private: System::Windows::Forms::TextBox^ textBox1; private: System::Windows::Forms::ListBox^ listBox2; private: System::Windows::Forms::ListBox^ listBox1; 58 private: System::Windows::Forms::Button^ button2; private: System::Windows::Forms::Label^ label1; private: System::Windows::Forms::TextBox^ textBox2; private: System::Windows::Forms::Label^ label2; private: System::Windows::Forms::TextBox^ textBox3; private: System::Windows::Forms::Label^ label3; private: System::Windows::Forms::Button^ button1; private: System::Windows::Forms::Button^ button3; private: System::ComponentModel::Container ^components; #pragma region Windows Form Designer generated code void InitializeComponent(void) { // Текст этой функции генерируется автоматически, поэтому // он пропущен для краткости } #pragma endregion private: System::Void Form1_Load(System::Object^ sender, EventArgs^ e) { WSADATA WSAData; int rc; char Name[101], *IpAddr, Buf[1000]; PHOSTENT phe; rc = WSAStartup(MAKEWORD(2,0), &WSAData); if (rc != 0) { listBox1->Items->Add("Ошибка инициализации WSAStartup"); return; } // if TCPSocket = socket(AF_INET, SOCK_STREAM, 0); if (TCPSocket == INVALID_SOCKET) { listBox1->Items->Add("Протокол TCP не установлен."); return; } // if memset(&OurAddress, 0, sizeof(OurAddress)); OurAddress.sin_family = AF_INET; OurAddress.sin_port = CLIENT_PORT; rc = bind(TCPSocket, (LPSOCKADDR)&OurAddress, sizeof(sockaddr_in)); if (rc == SOCKET_ERROR) { 59 listBox1->Items->Add("Адресная ошибка"); return; } // if gethostname(Name, 101); strcpy(Buf, "Имя компьютера "); strcat(Buf, Name); String ^ s= gcnew String(Buf); listBox1->Items->Add(s); phe = gethostbyname(Name); if (phe != NULL) { memcpy((void *)&(OurAddress.sin_addr), phe->h_addr, phe->h_length); IpAddr = inet_ntoa(OurAddress.sin_addr); strcpy(Buf, "IP-Адрес "); strcat(Buf, IpAddr); String ^ s2= gcnew String(Buf); listBox1->Items->Add(s2); } // if listBox1->Items->Add(L"Клиент запущен"); } //-------------------------------------------------------------------------------------------------protected: virtual void WndProc (Message% m) override { int rc, l=sizeof(CallAddress); wchar_t Buf[500]; if (m.Msg == WSA_NETEVENT) { if (m.LParam.ToInt32() == FD_READ) { rc = recv((SOCKET)m.WParam.ToInt32(), (char *)Buf, sizeof(Buf)-1, 0); if (rc == SOCKET_ERROR) { rc = WSAGetLastError(); listBox1->Items->Add(String::Format("Ошибка recv " "{0}", rc )); return; } // if if (rc >= 1) { String ^ s= gcnew String(Buf); listBox2->Items->Add(s); } // if } // if 60 } // if Form::WndProc( m ); } // WndProc // ------------------------------------------------------------------------------------------------private: System::Void button2_Click(System::Object^ sender, EventArgs^ e) { int rc, l, i; char Buf[500]; wchar_t Buf2[1000]; PHOSTENT phe; l = textBox1->Text->Length; if (l > 0) { for (i=0; i < l; i++) { Buf2[i] = textBox1->Text->default[i]; Buf2[i+1] = 0; } // for rc = send(TCPSocket, (char *)Buf2, 2*l+2, 0); if (rc == SOCKET_ERROR) { rc = WSAGetLastError(); listBox1->Items->Add(String::Format("Ошибка recv {0}",rc)); return; } // if String ^ s= gcnew String(Buf2); listBox2->Items->Add(s); } // if } // ------------------------------------------------------------------------------------------------private: System::Void button3_Click(System::Object^ sender, EventArgs^ e) { shutdown(TCPSocket); } // ------------------------------------------------------------------------------------------------private: System::Void button1_Click(System::Object^ sender, EventArgs^ e) { int rc, l, i; char Buf[500]; PHOSTENT phe; memset(&CallAddress, 0, sizeof(CallAddress)); CallAddress.sin_family = AF_INET; CallAddress.sin_port = SERVER_PORT; l = textBox2->Text->Length; if (l > 0) { 61 for (i=0; i < l; i++) { Buf[i] = textBox2->Text->default[i]; Buf[i+1] = 0; } // for CallAddress.sin_addr.s_addr = inet_addr(Buf); } else { l = textBox3->Text->Length; if (l > 0) { for (i=0; i < l; i++) { Buf[i] = textBox3->Text->default[i]; Buf[i+1] = 0; } // for phe = gethostbyname(Buf); if (phe != NULL) { memcpy((void *)&(CallAddress.sin_addr), phe->h_addr, phe->h_length); } // if } else { return; } // else } // else rc = connect(TCPSocket, (PSOCKADDR)&CallAddress, sizeof(CallAddress)); if (rc == SOCKET_ERROR) { rc = WSAGetLastError(); listBox1->Items->Add(String::Format( "Ошибка connect {0}", rc )); return; } // if listBox1->Items->Add("Канал создан"); rc = WSAAsyncSelect(TCPSocket, (HWND)(this->Handle.ToInt32()), WSA_NETEVENT, FD_READ|FD_CLOSE); if (rc != 0) { listBox1->Items->Add("Ошибка WSAAsyncSelect"); return; } // if } }; } 62 Приложение 4 Текст программы «Сервер», исполь ующей протокол ТСP #pragma once #include #include #define WSA_NETEVENT #define WSA_NETACCEPT #define SERVER_PORT 0x2021 #define CLIENT_PORT 0x2031 (WM_USER+2) (WM_USER+3) SOCKET TCPSocket, TmpSocket; sockaddr_in CallAddress; sockaddr_in OurAddress; namespace Server { using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; using namespace System::Security::Permissions; public ref class Form1 : public System::Windows::Forms::Form { public: Form1(void) { InitializeComponent(); } protected: ~Form1() { if (components) { delete components; } } private: System::Windows::Forms::ListBox^ listBox1; private: System::Windows::Forms::ListBox^ listBox2; private: System::Windows::Forms::TextBox^ textBox1; private: System::Windows::Forms::Button^ button1; private: System::ComponentModel::Container ^components; #pragma region Windows Form Designer generated code 63 void InitializeComponent(void) { // Текст этой функции генерируется автоматически, поэтому // он пропущен для краткости } #pragma endregion // ------------------------------------------------------------------------------------------------private: System::Void Form1_Activated(Object^ sender, System::EventArgs^ e) { WSADATA WSAData; int rc; char Name[101], *IpAddr, Buf[1000]; PHOSTENT phe; rc = WSAStartup(MAKEWORD(2,0), &WSAData); if (rc != 0) { listBox1->Items->Add("Ошибка инициализации WSAStartup"); return; } // if TCPSocket = socket(AF_INET, SOCK_STREAM, 0); if (TCPSocket == INVALID_SOCKET) { listBox1->Items->Add("Протокол TCP установлен."); return; } // if memset(&OurAddress, 0, sizeof(OurAddress)); OurAddress.sin_family = AF_INET; OurAddress.sin_port = SERVER_PORT; rc =bind(TCPSocket, (LPSOCKADDR)&OurAddress, sizeof(sockaddr_in)); if (rc == SOCKET_ERROR) { listBox1->Items->Add("Адресная ошибка"); return; } // if rc = WSAAsyncSelect(TCPSocket, (HWND)(this->Handle.ToInt32()), WSA_NETACCEPT, FD_ACCEPT); if (rc != 0) { listBox1->Items->Add("Ошибка WSAAsyncSelect"); return; } // if rc = listen(TCPSocket, 1); if (rc == SOCKET_ERROR) { listBox1->Items->Add("Ошибка listen"); 64 return; } // if gethostname(Name, 101); strcpy(Buf, "Имя компьютера "); strcat(Buf, Name); String ^ s= gcnew String(Buf); listBox1->Items->Add(s); phe = gethostbyname(Name); if (phe != NULL) { memcpy((void *)&(OurAddress.sin_addr), phe->h_addr, phe->h_length); IpAddr = inet_ntoa(OurAddress.sin_addr); strcpy(Buf, "IP-Адрес "); strcat(Buf, IpAddr); String ^ s2= gcnew String(Buf); listBox1->Items->Add(s2); } // if listBox1->Items->Add(L"Сервер запущен"); } // ----------------------------------------------------------------------------------------------protected: virtual void WndProc (Message% m) override { int rc, l=sizeof(CallAddress); wchar_t Buf[500]; if (m.Msg == WSA_NETACCEPT) { if (m.LParam.ToInt32() == FD_ACCEPT) { TmpSocket = accept((SOCKET)m.WParam.ToInt32(), (PSOCKADDR)&CallAddress, (int *)&l); if (TmpSocket == INVALID_SOCKET) { rc = WSAGetLastError(); listBox1->Items->Add(String::Format("Ошибка accept " "{0}", rc)); return; } // if rc = WSAAsyncSelect(TmpSocket, (HWND)(this->Handle. ToInt32()),WSA_NETEVENT, FD_READ|FD_CLOSE); if (rc != 0) { listBox1->Items->Add("Ошибка WSAAsyncSelect"); return; } // if listBox1->Items->Add("Канал создан"); } // if } // if if (m.Msg == WSA_NETEVENT) { if (m.LParam.ToInt32() == FD_READ) { 65 rc = recv((SOCKET)m.WParam.ToInt32(), (char *)Buf, sizeof(Buf)-1, 0); if (rc == SOCKET_ERROR) { rc = WSAGetLastError(); listBox1->Items->Add(String::Format( "Ошибка recv " "{0}", rc )); return; } // if if (rc >= 1) { String ^ s= gcnew String(Buf); listBox2->Items->Add(s); } // if } else { listBox1->Items->Add("Канал разорван"); } // else } // if Form::WndProc( m ); } // WndProc // ------------------------------------------------------------------------------------------------private: System::Void button1_Click(System::Object^ sender, EventArgs^ e) { int rc, i, l; wchar_t Buf[501]; l = textBox1->Text->Length; if (l == 0) return; for (i=0; i < l; i++) { Buf[i] = textBox1->Text->default[i]; Buf[i+1] = 0; } // for rc = send(TmpSocket, (char *)Buf, 2*l+2, 0); if (rc == SOCKET_ERROR) { rc = WSAGetLastError(); listBox1->Items->Add(String::Format( "Ошибка send {0}", rc )); return; } // if listBox1->Items->Add(textBox1->Text); } // button1_Click // ------------------------------------------------------------------------------------------------private: Void Form1_FormClosed(Object^ sender, FormClosedEventArgs^ e) { closesocket(TCPSocket); WSACleanup(); } //Form1_FormClosed }; } 66 Приложение 5 Текст программы «Клиент», исполь ующей mailslot #include #include #pragma once HANDLE ReceiveMailslotHandle, SendMailslotHandle; namespace Client { using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; public ref class Form1 : public System::Windows::Forms::Form { public: Form1(void) { InitializeComponent(); } protected: ~Form1() { if (components) { delete components; } } private: System::Windows::Forms::ListBox^ listBox1; private: System::Windows::Forms::Label^ label1; private: System::Windows::Forms::TextBox^ textBox1; private: System::Windows::Forms::Label^ label2; private: System::Windows::Forms::TextBox^ textBox2; private: System::Windows::Forms::Button^ button1; private: System::Windows::Forms::Timer^ timer1; private: System::ComponentModel::IContainer^ components; #pragma region Windows Form Designer generated code void InitializeComponent(void) { 67 // Текст этой функции генерируется автоматически, поэтому // он пропущен для краткости } #pragma endregion private: System::Void Form1_Load(System::Object^ sender, EventArgs^ e) { ReceiveMailslotHandle = CreateMailslot(L"\\\\.\\mailslot\\client", 0, MAILSLOT_WAIT_FOREVER, NULL); if (ReceiveMailslotHandle == INVALID_HANDLE_VALUE) { listBox1->Items->Add("Ошибка при создании mailslot"); timer1->Enabled = false; } else { listBox1->Items->Add("Mailslot создан"); timer1->Enabled = true; } // else } private: Void Form1_FormClosed(Object^ sender, FormClosedEventArgs^ e) { CloseHandle(ReceiveMailslotHandle); } private: System::Void timer1_Tick(System::Object^ sender, EventArgs^ e) { int rc; unsigned long cbMsgNumber, cbRead; wchar_t Buf[1001]; // Определяем состояние канала Mailslot rc = GetMailslotInfo(ReceiveMailslotHandle, NULL, NULL, &cbMsgNumber, NULL); if (!rc) { listBox1->Items->Add("Ошибка GetMailslotInfo"); } // if // Если в канале есть Mailslot сообщения, читаем первое из них if (cbMsgNumber != 0) { if (ReadFile(ReceiveMailslotHandle, Buf, 1000, &cbRead, NULL)) { String ^ s= gcnew String(Buf); listBox1->Items->Add(L"Получено: "+s); } else { listBox1->Items->Add("Ошибка ReadFile"); } // else } // if } private: System::Void button1_Click(System::Object^ sender, EventArgs^ e) { wchar_t Buf[1001], Buf2[1001]; unsigned long cbWritten; int i, l; 68 l = textBox1->Text->Length; if (l == 0) return; for (i=0; i < l; i++) { Buf[i] = textBox1->Text->default[i]; Buf[i+1] = 0; } // for wcscpy(Buf2, L"\\\\"); wcscat(Buf2, Buf); wcscat(Buf2, L"\\mailslot\\server"); SendMailslotHandle = CreateFile(Buf2, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (SendMailslotHandle == INVALID_HANDLE_VALUE) { listBox1->Items->Add("Ошибка при создании mailslot"); } // if l = textBox2->Text->Length; if (l == 0) return; for (i = 0; i < l; i++) { Buf[i] = textBox2->Text->default[i]; Buf[i+1] = 0; } // for if (WriteFile(SendMailslotHandle, Buf, 2*l+2, &cbWritten, NULL)) { listBox1->Items->Add(L"Отправлено: "+textBox2->Text); } else { listBox1->Items->Add("Ошибка WriteFile"); } // else CloseHandle(SendMailslotHandle); } }; } 69 Приложение 6 Текст программы «Клиент», исполь ующей pipe #include #pragma once HANDLE hPipe; DWORD cbRead, LastError; OVERLAPPED Over = {0, 0, 0, 0, NULL}; wchar_t Buf3[256]; namespace Client { using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; public ref class Form1 : public System::Windows::Forms::Form { public: Form1(void) { InitializeComponent(); } protected: ~Form1() { if (components) { delete components; } } private: System::Windows::Forms::ListBox^ listBox1; private: System::Windows::Forms::TextBox^ textBox1; private: System::Windows::Forms::Label^ label1; private: System::Windows::Forms::Button^ button1; private: System::Windows::Forms::Button^ button2; private: System::Windows::Forms::Label^ label2; private: System::Windows::Forms::TextBox^ textBox2; 70 private: System::Windows::Forms::Button^ button3; private: System::Windows::Forms::Timer^ timer1; private: System::ComponentModel::IContainer^ components; #pragma region Windows Form Designer generated code void InitializeComponent(void) { // Текст этой функции генерируется автоматически, поэтому // он пропущен для краткости } #pragma endregion private: System::Void button1_Click(Object^ sender, EventArgs^ e) { wchar_t Buf[1001], Buf2[1001]; unsigned long cbWritten; int i, l; l = textBox1->Text->Length; if (l == 0) return; for (i=0; i < l; i++) { Buf[i] = textBox1->Text->default[i]; Buf[i+1] = 0; } // for wcscpy(Buf2, L"\\\\"); wcscat(Buf2, Buf); wcscat(Buf2, L"\\pipe\\Server"); hPipe = CreateFile(Buf2, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED,NULL); if (hPipe != INVALID_HANDLE_VALUE) { listBox1->Items->Add("Pipe создан, клиент подключен"); } else { listBox1->Items->Add("Ошибка при создании pipe"); } // else ReadFile(hPipe, Buf3, 512, &cbRead, &Over); timer1->Enabled = true; } private: System::Void button2_Click(System::Object^ sender, EventArgs^ e) { CloseHandle(hPipe); listBox1->Items->Add("Клиент отключен"); timer1->Enabled = false; } private: System::Void button3_Click(System::Object^ sender, EventArgs^ e) { 71 wchar_t Buf[1001]; unsigned long cbWritten; int i, l; l = textBox2->Text->Length; if (l == 0) return; for (i=0; i < l; i++) { Buf[i] = textBox2->Text->default[i]; Buf[i+1] = 0; } // for if (WriteFile(hPipe, Buf, 2*l+2, &cbWritten, NULL)) { listBox1->Items->Add(L"Отправлено: "+textBox2->Text); } else { listBox1->Items->Add("Ошибка WriteFile"); } // else } private: System::Void timer1_Tick(System::Object^ sender, EventArgs^ e) { GetOverlappedResult(hPipe, &Over, &cbRead, FALSE); if (cbRead > 0) { String ^ s= gcnew String(Buf3); listBox1->Items->Add(L"Получено: "+s); ReadFile(hPipe, Buf3, 512, &cbRead, &Over); } // if } }; } 72 Приложение 7 Текст программы «Сервер», исполь ующей pipe #include #include #pragma once HANDLE hPipe; int fConnect; namespace Server { using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; public ref class Form1 : public System::Windows::Forms::Form { public: Form1(void) { InitializeComponent(); } protected: ~Form1() { if (components) { delete components; } } private: System::Windows::Forms::ListBox^ listBox1; private: System::Windows::Forms::TextBox^ textBox1; private: System::Windows::Forms::Button^ button1; private: System::Windows::Forms::Timer^ timer1; private: System::ComponentModel::IContainer^ components; #pragma region Windows Form Designer generated code void InitializeComponent(void) { // Текст этой функции генерируется автоматически, поэтому 73 // он пропущен для краткости } #pragma endregion private: System::Void Form1_Load(System::Object^ sender, EventArgs^ e) { hPipe = CreateNamedPipe(L"\\\\.\\pipe\\Server", PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE|PIPE_NOWAIT, PIPE_UNLIMITED_INSTANCES, 1000, 1000, 5000, NULL); if (hPipe != INVALID_HANDLE_VALUE) { listBox1->Items->Add("Pipe создан"); } else { listBox1->Items->Add("Ошибка при создании pipe"); } // else } private: System::Void timer1_Tick(System::Object^ sender, EventArgs^ e) { wchar_t Buf[512]; DWORD cbRead, LastError; ConnectNamedPipe(hPipe, NULL); LastError = GetLastError(); if (LastError == ERROR_PIPE_LISTENING) { fConnect = 0; } // if if (LastError == ERROR_PIPE_CONNECTED) { if (fConnect == 0) { listBox1->Items->Add("Клиент подключен"); } // if fConnect = 1; } // if if (LastError == ERROR_NO_DATA) { listBox1->Items->Add("Клиент отключен"); DisconnectNamedPipe(hPipe); fConnect = 0; } // if if (fConnect) { if (ReadFile(hPipe, Buf, 512, &cbRead, NULL)) { String ^ s= gcnew String(Buf); listBox1->Items->Add(s); } else { LastError = GetLastError(); if (LastError != ERROR_NO_DATA) { listBox1->Items->Add(String::Format( "Ошибка ReadFile" "LastError= {0}", LastError )); 74 } // if } // if } // if } private: Void Form1_FormClosed(Object^ sender, FormClosedEventArgs^ e) { CloseHandle(hPipe); } private: System::Void button1_Click(System::Object^ sender, EventArgs^ e) { wchar_t Buf[1001]; unsigned long cbWritten; int i, l; l = textBox1->Text->Length; if (l == 0) return; for (i=0; i < l; i++) { Buf[i] = textBox1->Text->default[i]; Buf[i+1] = 0; } // for if (WriteFile(hPipe, Buf, 2*l+2, &cbWritten, NULL)) { listBox1->Items->Add(L"Отправлено: "+textBox1->Text); } else { listBox1->Items->Add(L"Ошибка WriteFile"); } // else } }; } 75 Приложение 8 Текст программы «Клиент», исполь ующей библиотеку WinInet #pragma once #include #include #include HINTERNET hInet, hSession, hRequest; #define SERVER_PORT 3021 int State=0; namespace Client { using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; public ref class Form1 : public System::Windows::Forms::Form { public: Form1(void) { InitializeComponent(); } protected: ~Form1() { if (components) { delete components; } } private: System::Windows::Forms::TextBox^ textBox1; private: System::Windows::Forms::ListBox^ listBox1; private: System::Windows::Forms::Button^ button2; private: System::Windows::Forms::Label^ label2; private: System::Windows::Forms::TextBox^ textBox3; private: System::Windows::Forms::Label^ label3; 76 private: System::Windows::Forms::Timer^ timer1; private: System::ComponentModel::IContainer^ components; #pragma region Windows Form Designer generated code void InitializeComponent(void) { // Текст этой функции генерируется автоматически, поэтому // он пропущен для краткости } #pragma endregion private: System::Void Form1_Load(System::Object^ sender, EventArgs^ e) { listBox1->Items->Add(L"Клиент запущен"); } // ------------------------------------------------------------------------------------------------private: System::Void button2_Click(System::Object^ sender, EventArgs^ e) { int rc, l1, l3, i, Code; wchar_t Buf[1001], Buf1[1001], Buf3[1001], Buf4[1001]; l3 = textBox3->Text->Length; if (l3 < 0) { listBox1->Items->Add("Не задано имя сервера"); return; } // if for (i = 0; i < l3; i++) { Buf3[i] = textBox3->Text->default[i]; Buf3[i+1] = 0; } // for l1 = textBox1->Text->Length; if (l1 < 0) { listBox1->Items->Add("Не задано имя сервера"); return; } // if for (i = 0; i < l1; i++) { Buf1[i] = textBox1->Text->default[i]; Buf1[i+1] = 0; } // for hInet = InternetOpen(L"MyAgent", INTERNET_OPEN_TYPE_DIRECT /* INTERNET_OPEN_TYPE_PRECONFIG*/, 77 NULL, NULL, 0); if (hInet == NULL) { listBox1->Items->Add(L"Ошибка InternetOpen"); return; } // if hSession = InternetConnect(hInet, Buf3, SERVER_PORT, L"Sartasov", L"123", INTERNET_SERVICE_HTTP, 0, 0); if (hSession == NULL) { listBox1->Items->Add(L"Ошибка InternetConnect"); return; } // if hRequest = HttpOpenRequest(hSession, L"POST", L"", L"HTTP/1.1", L"", NULL, INTERNET_FLAG_EXISTING_CONNECT | INTERNET_FLAG_NO_AUTO_REDIRECT | INTERNET_FLAG_KEEP_CONNECTION, 0); if (hRequest == NULL) { Code = GetLastError(); listBox1->Items->Add(L"Ошибка HttpOpenRequest"); return; } // if wcscpy(Buf, L""); wcscat(Buf, Buf1); wcscat(Buf, L""); Code = HttpSendRequest(hRequest, NULL, 0, Buf, 2*(13+l1)); if (!Code) { Code = GetLastError(); listBox1->Items->Add(L"Ошибка HttpSendRequest"); return; } State = 4; timer1->Enabled = true; } private: System::Void timer1_Tick(System::Object^ sender, EventArgs^ e) { DWORD Len=1000; int Code; wchar_t Buf[1001], *Begin, *End; 78 if (State == 1) { // Получить код ответа Len = 10; Code = HttpQueryInfo(hRequest, HTTP_QUERY_STATUS_CODE, Buf, &Len, NULL); if (!Code) { listBox1->Items->Add(L"Ошибка HttpQueryInfo (status_code)" ); return; } // if Code = _wtoi(Buf); if (Code >= 300) { listBox1->Items->Add(L"От сервера получен ошибочный" "код возврата"); State = 0; timer1->Enabled = false; return; } // if State = 2; } // if if (State == 2) { // Получить заголовок ответа Len = 1000; Code = HttpQueryInfo(hRequest, HTTP_QUERY_RAW_HEADERS_CRLF,Buf, &Len, NULL); if (!Code) { listBox1->Items->Add(L"Ошибка HttpQueryInfo(HEADERS)"); return; } // if State = 4; Len = 1000; } // if if (State == 3) { // Получить размер ответа Len = 10; Code = HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_LENGTH,Buf, &Len, NULL); if (!Code) { listBox1->Items->Add(L"Ошибка " "HttpQueryInfo(CONTENT_LENGTH)"); return; } // if 79 Len = _wtoi(Buf); if (Len <= 0) { listBox1->Items->Add(L"Ошибка в размере ответа"); State = 0; timer1->Enabled = false; return; } // if State = 4; } // if if (State == 4) { // Получить ответ Code = InternetReadFile(hRequest, Buf, Len, &Len); if (!Code) { listBox1->Items->Add(L"Ошибка при получении ответа"); return; } // if } // if State = 0; timer1->Enabled = false; Begin = wcsstr(Buf, L""); if (Begin == NULL) { listBox1->Items->Add(L"Нет текста "); return; } // if Begin += 7; End = wcsstr(Buf, L""); if (End == NULL) { listBox1->Items->Add(L"Нет текста "); return; } // if *End = '\0'; String ^ s= gcnew String(Begin); listBox1->Items->Add(L"Получено " + s); } }; } 80 Приложение 9 Текст программы «Сервер», исполь ующей библиотеку WinInet #pragma once #include #include #define WSA_NETEVENT (WM_USER+2) #define WSA_NETACCEPT (WM_USER+3) #define SERVER_PORT 3021 SOCKET TCPSocket, TmpSocket; sockaddr_in CallAddress; sockaddr_in OurAddress; namespace Server { using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; using namespace System::Security::Permissions; public ref class Form1 : public System::Windows::Forms::Form { public: Form1(void) { InitializeComponent(); } protected: ~Form1() { if (components) { delete components; } } private: System::Windows::Forms::ListBox^ listBox1; private: System::Windows::Forms::ListBox^ listBox2; private: System::Windows::Forms::TextBox^ textBox1; 81 private: System::Windows::Forms::Button^ button1; private: System::ComponentModel::Container ^components; #pragma region Windows Form Designer generated code void InitializeComponent(void) { // Текст этой функции генерируется автоматически, поэтому // он пропущен для краткости } #pragma endregion // ------------------------------------------------------------------------------------------------private: System::Void Form1_Load(System::Object^ sender, EventArgs^ e) { WSADATA WSAData; int rc; char Name[101], *IpAddr, Buf[1000]; PHOSTENT phe; listBox1->Items->Add("WSAStartup"); rc = WSAStartup(MAKEWORD(2,0), &WSAData); if (rc != 0) { listBox1->Items->Add("Ошибка инициализации WSAStartup"); return; } // if TCPSocket = socket(AF_INET, SOCK_STREAM, 0); if (TCPSocket == INVALID_SOCKET) { listBox1->Items->Add("Протокол TCP не установлен."); } // if memset(&OurAddress, 0, sizeof(OurAddress)); OurAddress.sin_family = AF_INET; OurAddress.sin_port = htons(SERVER_PORT); rc =bind(TCPSocket, (LPSOCKADDR)&OurAddress, sizeof(sockaddr_in)); if (rc == SOCKET_ERROR) { listBox1->Items->Add("Адресная ошибка"); return; } // if rc = WSAAsyncSelect(TCPSocket, (HWND)(this->Handle.ToInt32()), WSA_NETACCEPT, FD_ACCEPT); if (rc != 0) { listBox1->Items->Add("Ошибка WSAAsyncSelect"); 82 return; } // if rc = listen(TCPSocket, 1); if (rc == SOCKET_ERROR) { listBox1->Items->Add("Ошибка listen"); return; } // if gethostname(Name, 101); strcpy(Buf, "Имя компьютера "); strcat(Buf, Name); String ^ s= gcnew String(Buf); listBox1->Items->Add(s); phe = gethostbyname(Name); if (phe != NULL) { memcpy((void *)&(OurAddress.sin_addr), phe->h_addr, phe->h_length); IpAddr = inet_ntoa(OurAddress.sin_addr); strcpy(Buf, "IP-Адрес "); strcat(Buf, IpAddr); String ^ s2= gcnew String(Buf); listBox1->Items->Add(s2); } // if listBox1->Items->Add(L"Сервер запущен"); } // Form1_Load // ------------------------------------------------------------------------------------------------protected: virtual void WndProc (Message% m) override { int rc, l=sizeof(CallAddress); char Buf[10001]; wchar_t *Buf2, *Begin, *End; if (m.Msg == WSA_NETACCEPT) { if (m.LParam.ToInt32() == FD_ACCEPT) { TmpSocket = accept((SOCKET)m.WParam.ToInt32(), (PSOCKADDR)&CallAddress, (int *)&l); if (TmpSocket == INVALID_SOCKET) { rc = WSAGetLastError(); listBox1->Items->Add(String::Format("Ошибка accept " "{0}", rc)); 83 return; } // if rc = WSAAsyncSelect(TmpSocket, (HWND)(this->Handle. ToInt32()),WSA_NETEVENT, FD_READ|FD_CLOSE); if (rc != 0) { listBox1->Items->Add("Ошибка WSAAsyncSelect"); return; } // if listBox1->Items->Add("Канал создан"); } // if } // if if (m.Msg == WSA_NETEVENT) { if (m.LParam.ToInt32() == FD_READ) { rc = recv((SOCKET)m.WParam.ToInt32(), (char *)Buf, sizeof(Buf)-1, 0); if (rc == SOCKET_ERROR) { rc = WSAGetLastError(); listBox1->Items->Add(String::Format("Ошибка recv " "{0}", rc )); return; } // if if (rc >= 1) { l = strlen(Buf); Buf2 = (wchar_t *)(Buf+l-1); Begin = wcsstr(Buf2, L""); if (Begin == NULL) { listBox1->Items->Add(L"Нет текста "); return; } // if Begin += 7; End = wcsstr(Buf2, L""); if (End == NULL) { listBox1->Items->Add(L"Нет текста "); return; } // if *End = '\0'; String ^ s= gcnew String(Begin); listBox2->Items->Add(L"Получено " + s); } // if } else { listBox1->Items->Add("Канал разорван"); 84 } // else } // if Form::WndProc( m ); } // WndProc // ------------------------------------------------------------------------------------------------private: System::Void button1_Click(System::Object^ sender, EventArgs^ e) { int rc, i, l; wchar_t Buf[1001], Buf2[1001]; l = textBox1->Text->Length; if (l == 0) return; for (i=0; i < l; i++) { Buf[i] = textBox1->Text->default[i]; Buf[i+1] = 0; } // for wcscpy(Buf2, L""); wcscat(Buf2, Buf); wcscat(Buf2, L""); rc = send(TmpSocket, (char *)Buf2, 2*(13+l), 0); if (rc == SOCKET_ERROR) { rc = WSAGetLastError(); listBox1->Items->Add(String::Format( "Ошибка send {0}", rc )); return; } // if listBox1->Items->Add(textBox1->Text); closesocket(TmpSocket); } // button1_Click // ------------------------------------------------------------------------------------------------private: Void Form1_FormClosed(Object^ sender, FormClosedEventArgs^ e) { closesocket(TCPSocket); WSACleanup(); } //Form1_FormClosed }; } 85 Оглавление Введение ............................................................................................................... 3 1. Компьютерные сети 1.1. Основные определения .............................................................................. 4 1.2. Аппаратные средства компьютерных сетей ............................................. 5 1.3. Программные средства компьютерных сетей .......................................... 9 1.3.1. Сетевые драйверы .............................................................................. 10 1.3.2. Системное программное обеспечение. ............................................. 13 1.3.3. Прикладное программное обеспечение. ........................................... 14 1.4. Семиуровневая модель сетевого взаимодействия. ................................. 14 1.5. Контрольные вопросы ............................................................................. 16 2. Использование протоколов обмена информацией для разработки сетевых программ 2.1. Использование протоков UDP и TCP ..................................................... 17 2.2. Библиотека функций Windows Socket .................................................... 17 2.3. Пример программ, использующих протокол UDP ................................. 24 2.4. Пример программ, использующих протокол TCP ................................. 25 2.5. Контрольные вопросы. ............................................................................ 28 3. Использование каналов Mailslot и Pipe для разработки сетевых программ 3.1. Использование каналов mailslot .............................................................. 29 3.2. Использование каналов pipe .................................................................... 33 3.3. Контрольные вопросы ............................................................................. 39 4. Использование библиотеки WinInet для разработки программ, работающих в глобальной сети 4.1. Функция InternetOpen .............................................................................. 40 4.2. Функция InternetConnect .......................................................................... 41 4.3. Функция HttpOpenRequest ....................................................................... 41 4.4. Функция HttpSendRequest ........................................................................ 42 4.5. Функция HttpQueryInfo ............................................................................ 43 4.6. Функция InternetReadFile ......................................................................... 44 4.7. Пример программ, использующих библиотеку WinInet ........................ 45 4.8. Контрольные вопросы. ............................................................................ 46 Заключение ......................................................................................................... 47 Библиографический список ............................................................................... 48 Приложения ........................................................................................................ 49 86
«Вычислительные системы, сети и телекоммуникации» 👇
Готовые курсовые работы и рефераты
Купить от 250 ₽
Решение задач от ИИ за 2 минуты
Решить задачу
Найди решение своей задачи среди 1 000 000 ответов
Найти
Найди решение своей задачи среди 1 000 000 ответов
Крупнейшая русскоязычная библиотека студенческих решенных задач

Тебе могут подойти лекции

Смотреть все 462 лекции
Все самое важное и интересное в Telegram

Все сервисы Справочника в твоем телефоне! Просто напиши Боту, что ты ищешь и он быстро найдет нужную статью, лекцию или пособие для тебя!

Перейти в Telegram Bot