Программа Server Socket
Сетевое программирование… думаю, что не стоит сомневается в актуальности этой темы, и так понятно, что сегодня сетевые технологии находятся на достаточно высоком уровне развития. А это значит, что программисты в этой области нужны не менее чем в других областях. Сетевое программирование кажется легче, чем оно есть на самом деле. WinSock функции, которые использовались для коммуникации через сеть… Читать ещё >
Программа Server Socket (реферат, курсовая, диплом, контрольная)
Введение
Благодаря возникновению и развитию сетей передачи данных появился новый, высокоэффективный способ взаимодействия между людьми. Сначала сети использовались для научных исследований, но потом они стали проникать во все области человеческой деятельности. Большинство сетей существовало независимо друг от друга, решая конкретные задачи для конкретных групп пользователей. В соответствии с этими задачами выбирались те или иные сетевые технологии, сетевые стандарты и протоколы, а также аппаратное обеспечение.
Для правильного взаимодействия компьютеров работающих в сетях разнообразной структуры, с использованием различного программного обеспечения необходимо наличие стандартов. Этих стандартов на данный момент существует также достаточно большое количество. Данные стандарты и протоколы строго определяют нормы и правила технической организации компьютерных сетей и программ, реализующих взаимодействие по сети.
Изучение сетевых стандартов и протоколов является на сегодняшний день обязательным для любого специалиста по информационным технологиям.
Цель проекта: Получение навыков программирования сетевых приложений
Для решения поставленной цели в курсовой работе решаются следующие задачи:
Изучение библиотеки winsosk;
Написание программы демонстрирующей сокетное соединение.
1.Теоретическая часть
1.1 Эталонная модель OSI
При связи компьютеров по сети производится множество операций, обеспечивающих передачу данных от компьютера к компьютеру. Пользователю, работающему с каким-то приложением, в общем-то безразлично, что и как при этом происходит.
Для него просто существует доступ к другому приложению или компьютерному ресурсу, расположенному на другом компьютере сети. В действительности же вся передаваемая информация проходит много этапов обработки. Прежде всего она разбивается на блоки, каждый из которых снабжается управляющей информацией. Полученные блоки оформляются в виде сетевых пакетов, эти пакеты кодируются, передаются с помощью электрических или световых сигналов по сети в соответствии с выбранным методом доступа, затем из принятых пакетов вновь восстанавливаются заключенные в них блоки данных, блоки соединяются в данные, которые и становятся доступны другому приложению. Это, конечно, очень упрощенное описание происходящих процессов. Часть из указанных процедур реализуется только программно, другая — аппаратно, а какие-то операции могут выполняться как программами, так и аппаратурой.
Упорядочить все выполняемые процедуры, разделить их на уровни и подуровни, взаимодействующие между собой, как раз и призваны модели сетей. Эти модели позволяют правильно организовать взаимодействие как абонентам внутри одной сети, так и самым разным сетям на различных уровнях. Наибольшее распространение получила в настоящее время так называемая эталонная модель обмена информацией открытой системы OSI (Open System Interchange). Под термином «открытая система» в данном случае понимается незамкнутая в себе система, имеющая возможность взаимодействия с какими-то другими системами (в отличие от закрытой системы).
Модель OSI была предложена Международной организацией стандартов ISO (International Standards Organization) в 1984 году. С тех пор ее используют (более или менее строго) все производители сетевых продуктов. Как и любая универсальная модель, модель OSI довольно громоздка, избыточна и не слишком гибка, поэтому реальные сетевые средства, предлагаемые различными фирмами, не обязательно придерживаются принятого разделения функций. Эталонная модель OSI стала основной архитектурной моделью для систем передачи сообщений. При рассмотрении конкретных прикладных телекоммуникационных систем производится сравнение их архитектуры с моделью OSI/ISO. Эта модель является наилучшим средством для изучения современной технологии связи.
Эталонная модель OSI делит проблему передачи информации между абонентами на семь менее крупных и, следовательно, более легко разрешимых задач. Конкретизация каждой задачи производилась по принципу относительной автономности. Очевидно, автономная задача решается легче.
Каждой из семи областей проблемы передачи информации ставится в соответствие один из уровней эталонной модели. Два самых низших уровня эталонной модели OSI реализуются аппаратным и программным обеспечением, остальные пять высших уровней, как правило, реализуются программным обеспечением. Эталонная модель OSI описывает, каким образом информация проходит через среду передачи (например, металлические провода) от прикладного процесса-источника (например, по передаче речи) до процесса-получателя.
Стек протоколов, представленный в виде 7-уровневой структуры, показан на рисунке 1
Структура модели OSI.Рис.1
В рамках модели OSI взаимодействие двух систем представляется фактически в виде двух моделей — горизонтальной и вертикальной:
в рамках горизонтальной модели рассматривается прямое взаимодействие (обмен данными) одинаковых уровней в двух конечных точках (хостах); для организации такого взаимодействия в каждой из конечных точек должны поддерживаться одинаковые протоколы для данного уровня;
в вертикальной модели рассматривается обмен информацией (взаимодействие) между соседними уровнями одной системы с использованием интерфейсов API; в этой модели каждый уровень может предоставлять свои услуги вышележащему уровню и пользоваться услугами нижележащего уровня (крайние уровни модели в этом смысле представляют исключение — прикладной уровень предоставляет свои услуги пользователю, а сетевой уровень не пользуется сервисом других уровней) Прикладной уровень (Application layer)
Верхний (7-й) уровень модели, обеспечивает взаимодействие сети и пользователя. Уровень разрешает приложениям пользователя доступ к сетевым службам, таким как обработчик запросов к базам данных, доступ к файлам, пересылке электронной почты. Также отвечает за передачу служебной информации, предоставляет приложениям информацию об ошибках и формирует запросы к уровню представления.
Уровень представления (Presentation layer)
Этот уровень отвечает за преобразование протоколов и кодирование/декодирование данных. Запросы приложений, полученные с уровня приложений, он преобразует в формат для передачи по сети, а полученные из сети данные преобразует в формат, понятный приложениям. На этом уровне может осуществляться сжатие/распаковка или кодирование/декодирование данных, а также перенаправление запросов другому сетевому ресурсу, если они не могут быть обработаны локально.
Сеансовый уровень (Session layer)
Отвечает за поддержание сеанса связи, позволяя приложениям взаимодействовать между собой длительное время. Уровень управляет созданием/завершением сеанса, обменом информацией, синхронизацией задач, определением права на передачу данных и поддержанием сеанса в периоды неактивности приложений. Синхронизация передачи обеспечивается помещением в поток данных контрольных точек, начиная с которых возобновляется процесс при нарушении взаимодействия Транспортный уровень (Transport layer)
4-й уровень модели, предназначен для доставки данных без ошибок, потерь и дублирования в той последовательности, как они были переданы. При этом неважно, какие данные передаются, откуда и куда, то есть он предоставляет сам механизм передачи. Блоки данных он разделяет на фрагменты, размер которых зависит от протокола, короткие объединяет в один, а длинные разбивает. Протоколы этого уровня предназначены для взаимодействия типа точка-точка.
Сетевой уровень (Network layer)
3-й уровень сетевой модели OSI, предназначен для определения пути передачи данных. Отвечает за трансляцию логических адресов и имён в физические, определение кратчайших маршрутов, коммутацию и маршрутизацию пакетов, отслеживание неполадок и заторов в сети. На этом уровне работает такое сетевое устройство, как маршрутизатор.
Канальный уровень (Data Link layer)
Этот уровень предназначен для обеспечения взаимодействия сетей на физическом уровне и контроля за ошибками, которые могут возникнуть. Полученные с физического уровня данные он упаковывает в кадры данных, проверяет на целостность, если нужно исправляет ошибки и отправляет на сетевой уровень. Канальный уровень может взаимодействовать с одним или несколькими физическими уровнями, контролируя и управляя этим взаимодействием. Спецификация IEEE 802 разделяет этот уровень на 2 подуровня — MAC (Media Access Control) регулирует доступ к разделяемой физической среде, LLC (Logical Link Control) обеспечивает обслуживание сетевого уровня. На этом уровне работают коммутаторы, мосты.
В программировании этот уровень представляет драйвер сетевой платы, в операционных системах имеется программный интерфейс взаимодействия канального и сетевого уровня между собой, это не новый уровень, а просто реализация модели для конкретной ОС. Примеры таких интерфейсов: ODI, NDIS.
Физический уровень (Physical layer)
Самый нижний уровень модели, предназначен непосредственно для передачи потока данных. Осуществляет передачу электрических или оптических сигналов в кабель и соответственно их приём и преобразование в биты данных в соответствии с методами кодирования цифровых сигналов. Другими словами, осуществляет интерфейс между сетевым носителем и сетевым устройством. На этом уровне работают концентраторы, повторители (ретрансляторы) сигнала и сетевые адаптеры.
Взаимодействие уровней Уровни взаимодействуют сверху вниз и снизу вверх посредством интерфейсов и могут еще взаимодействовать с таким же уровнем другой системы с помощью протоколов.
Протоколы, использующиеся на каждом уровне модели OSI, представлены в таблице 1.
Протоколы уровней модели OSI
Уровень OSI | Протоколы | |
Прикладной | HTTP, gopher, Telnet, DNS, SMTP, SNMP, CMIP, FTP, TFTP, SSH, IRC, AIM, NFS, NNTP, NTP, SNTP, XMPP, FTAM, APPC, X.400, X.500, AFP, LDAP, SIP, ITMS, Modbus TCP, BACnet IP, IMAP, POP3, SMB, MFTP, BitTorrent, eD2k, PROFIBUS | |
Представления | HTTP, ASN.1, XML-RPC, TDI, XDR, SNMP, FTP, Telnet, SMTP, NCP, AFP | |
Сеансовый | ASP, ADSP, DLC, Named Pipes, NBT, NetBIOS, NWLink, Printer Access Protocol, Zone Information Protocol, SSL, TLS, SOCKS | |
Транспортный | TCP, UDP, NetBEUI, AEP, ATP, IL, NBP, RTMP, SMB, SPX, SCTP, DCCP, RTP, TFTP | |
Сетевой | IP, IPv6, ICMP, IGMP, IPX, NWLink, NetBEUI, DDP, IPSec, ARP, RARP, DHCP, BootP, SKIP, RIP | |
Канальный | STP, ARCnet, ATM, DTM, SLIP, SMDS, Ethernet, FDDI, Frame Relay, LocalTalk, Token ring, StarLan, L2 °F, L2TP, PPTP, PPP, PPPoE, PROFIBUS | |
Физический | RS-232, RS-422, RS-423, RS-449, RS-485, ITU-T, xDSL, ISDN, T-carrier (T1, E1), модификации стандарта Ethernet: 10BASE-T, 10BASE2, 10BASE5, 100BASE-T (включает 100BASE-TX, 100BASE-T4, 100BASE-FX), 1000BASE-T, 1000BASE-TX, 1000BASE-SX | |
Табл.1
Следует понимать, что подавляющее большинство современных сетей в силу исторических причин лишь в общих чертах, приближённо, соответствуют эталонной модели ISO/OSI.
Реальный стек протоколов OSI, разработанный как часть проекта, был воспринят многими как слишком сложный и фактически нереализуемый. Он предполагал упразднение всех существующих протоколов и их замену новыми на всех уровнях стека. Это сильно затруднило реализацию стека и послужило причиной для отказа от него многих поставщиков и пользователей, сделавших значительные инвестиции в другие сетевые технологии. В дополнение, протоколы OSI разрабатывались комитетами, предлагавшими различные и иногда противоречивые характеристики, что привело к объявлению многих параметров и особенностей необязательными. Поскольку слишком многое было необязательно или предоставлено на выбор разработчика, реализации различных поставщиков просто не могли взаимодействовать, отвергая тем самым саму идею проекта OSI.
В результате попытка OSI договориться об общих стандартах сетевого взаимодействия была вытеснена стеком протоколов TCP/IP, используемым в Интернете, и его более простым, прагматичным подходом к компьютерным сетям. Подход Интернета состоял в создании простых протоколов с двумя независимыми реализациями, требующимися для того, чтобы протокол мог считаться стандартом. Это подтверждало практическую реализуемость стандарта. Например, определения стандартов электронной почты X.400 состоят из нескольких больших томов, а определение электронной почты Интернета (SMTP) — всего несколько десятков страниц в RFC 821. Всё же стоит заметить, что существуют многочисленные RFC, определяющие расширения SMTP. Поэтому на данный момент полная документация по SMTP и расширениям также занимает несколько больших книг.
Большинство протоколов и спецификаций стека OSI уже не используются, такие как электронная почта X.400. Лишь немногие выжили, часто в значительно упрощённом виде. Структура каталогов X.500 до сих пор используется, в основном, благодаря упрощению первоначального громоздкого протокола DAP, получившему название LDAP и статус стандарта Интернета.
Свёртывание проекта OSI в 1996 году нанесло серьёзный удар по репутации и легитимности участвовавших в нём организаций, особенно ISO. Наиболее крупным упущением создателей OSI был отказ увидеть и признать превосходство стека протоколов TCP/IP.
Сокеты
WinSock (Windows Socket) — это Windows API, который взаимодействует с сетью.(Socket переводится с английского языка как «разьем».) Программного интерфейса для обеспечения обмена данными между процессами. Процессы при таком обмене могут исполняться как на одной ЭВМ, так и на различных ЭВМ, связанных между собой сетью. Сокет — абстрактный объект, представляющий конечную точку соединения.
Следует различать клиентские и серверные сокеты. Клиентские сокеты грубо можно сравнить с конечными аппаратами телефонной сети, а серверные — с коммутаторами. Клиентское приложение (например, браузер) использует только клиентские сокеты, а серверное (например, веб-сервер, которому браузер посылает запросы) — как клиентские, так и серверные сокеты.
Выделяют два типа socket’ов: потоковый socket (SOCK_STREAM) и, так называемый, дейтаграммный socket (datagram socket, SOCK_DGRAM). Потоковый вариант разработан для приложений, нуждающихся в надежном соединении и часто использующем продолжительные потоки данных. Протокол, использующийся для данного типа socket’ов — TCP. В этом материале будет использоваться только потоковый тип socket’ов, т.к. он чаще всего используется в хорошо известных протоколах, таких как SMTP, POP3, HTTP, TCP.
Дейтаграммные socket’ы используют UDP протокол и имеют низкий сигнал соединения и большой размер буфера данных. Они применяются в приложениях, которые отправляют данные малых размеров и не нуждаются в идеальной надежности. В отличии от потоковых socket’ов, дейтаграммные socket’ы не гарантируют стопроцентной передачи данных получателю, как и не гарантируют передачи данных в нужном порядке. Данный тип socket’ов полезнее для приложений, где надежность не является высоким приоритетом, таким как скорость (например аудио или видео трансляция). В приложениях, которые нуждаются в надежности, целесообразней использовать потоковые сокеты.
Связывание (binding) socket’ов. Связать socket значит «прикрепить» определенный адрес (IP адрес и номер порта) к данному socket’у. Это можно сделать вручную, используя связывающую функцию, но в некоторых случаем WinSock сам автоматически свяжет socket.
Соединение Способ использования socket’ов зависит от того, где ты их используешь: на клиентской или серверной части. Клиентская часть создает соединение путем создания socket’а и вызовом соединяющей функции с определенной адресной информацией. До того как socket не соединится, он не будет связан с адресом. Это связано с тем, что клиент может использовать любой адрес (IP адрес и номер порта) для соединения с сервером.
Когда соединение вызвано, WinSock выберет IP адрес и номер порта для соединения и свяжет с ними socket до того, как клиент фактически соединится с сервером. Номером порта может быть любой номер, который свободен в момент соединения, с выбором IP адреса надо быть аккуратнее. Компьютеры могут иметь более одного IP адреса. Например, компьютер, подключенный к локальной сети и к интернету, имеет три IP адреса: внешний для использования интернета; адрес в локальной сети (192.168.x.x или 10.0.x.x и т. д.); адрес, так называемой «внутренней петли"(loopback), для обозначения «локального хоста» в сети из одного компьютера (127.0.0.1). Здесь выбор IP адреса, с которым связан socket, имеет значение, т.к. также определяет сеть, которую ты используешь для соединения. Если ты хочешь подключиться к локальному компьютеру 192.168.0.4, ты не сможешь сделать это через сеть интернет провайдера. Тебе потребуется связать socket с Вашим IP адресом в такой же сети (192.168.0.1, например).
К счастью, WinSock сам выберет IP адрес на твоем компьютере, который может использоваться для соединения с нужным адресом. Ничего не мешает тебе связать socket самостоятельно, но помни, что ты должен взять ситуацию, описанную выше, во внимание. Так же связывающая функция дает пользователю возможность установить IP адрес или номер порта в нулевое значение. В этом случаем нулевое значение значит «пускай WinSock выберет что-нибудь для меня». Это полезно, когда ты хочешь подключиться, используя определенный IP адрес, но, не указывая значение порта.
Прослушивание На «стороне» сервера дела обстоят немного иначе. Сервер ждет входящих соединений и клиенту необходимо знать IP адрес и номер порта сервера, чтобы установить соединение. Чтобы упростить дело, на сервере всегда используется фиксированный номер порта (обычно это — порт, предусмотренный протоколом по умолчанию).
Ожидание входящего соединения по определенному адресу называется прослушиванием (listening). Обычно, перед тем как «войти» в режим прослушивания, socket должен быть связан с определенным адресом. Когда номер порта этого адреса установлен и зафиксирован (т.е. не изменится), сервер начинает ждать входящие соединения по этому порту. Например, 80 порт (порт по умолчанию для HTTP) прослушивается большинством серверов.
Когда клиент запрашивает соединение с сервером, сервер разрешит ему (или нет) и породит новый socket, который будет конечной точкой связи. Благодаря этому, socket, по которому происходило прослушивание, не используется для передачи данных и может находиться в режиме прослушивания дальше, «принимая» новых клиентов.
Описание функций WinSock.
В большинстве случаев используется версия WinSock 2. х, обычно называемая WinSock 2, т.к. различия небольшие. Последней популярной версией до второй, был WinSock 1.1. Некоторые могут сказать, что надо использовать именно эту версию, т.к. Windows 95 поддерживает только ее, но кто в наши дни пользуется Windows 95? Поэтому я рекомендую тебе использовать WinSock версии 2. Две основных версии WinSock «проживают» в двух .DLL — wsock32. dll и ws232.dll. В первой — версия 1.1, а во второй — WinSock2. В С++ достаточно подключить windows. h и winsock2. h для использования функций WinSock в своей программе. Далее в материале будет рассмотрен WinSock второй версии.
WinSock обеспечивает два интерфейса: API и SPI (Service Provider Interface — Интерфейс Обеспечения Служб). В этом материале будет рассмотрен только API, в нем содержатся все необходимые функции для использования нужных протоколов. SPI — интерфейс, который добавляет «поставщиков
передачи данных" (Data Transport Providers) как, например TCP/IP или IPX/SPX. Также SPI добавляет «поставщиков именных служб» (Name Space Service Providers), таких как DNS. Но все эти добавления «прозрачны» для пользователей API и не видны им.
SAStartup и WSACleanup
int WSAStartup (WORD wVersionRequested, LPWSADATA lpWSAData);
int WSACleanup ();
Перед вызовом любой WinSock функции, необходимо инициализировать библиотеку Ws232.dll. Это делается с помощью WSAStartup. Функция принимает два параметра:
— wVersionRequested — Максимальная версия Windows Socket, которую может использовать вызывающая программа. Старший байт содержит младшую часть номера версии, младший байт содержит старшую часть номера версии.
— lpWSAData — Указатель на структуру WSADATA, которая, в результате выполнения функции, будет содержать детали реализации Windows Sockets.
В случае успешного выполнения, функция WSAStartup возвращает 0. В противном случае возвращается один из кодов ошибки, приведенных в таблице. Если выполнение функции WSAStartup окончилось неудачей, невозиожно определить код ошибки с помощью WSAGetLastError. Это получается потому, что в случае сбоя, библиотека Ws232.dll не будет загружена, и область памяти, где сохраняется информация о последней ошибке, недоступна. Но при желании можно попробовать получить код ошибки с помощью API функции GetLastError.
Это справедливо для приложений, написанных с использованием младших версий, для успешной работы со старшими версиями библиотеки. В этом случае приложение гарантирует доступ только к функциональности, совместимой по синтаксису с текущей версией. Для полного доступа к новому синтаксису будущих реализаций приложение должно полностью соответствовать этой реализации — откомпилировано с новыми заголовочными файлами, слинковано с новыми библиотеками.
Пример использования этих функций:
const int iReqWinsockVer = 2; // Минимальная требуемая версия
WSADATA wsaData;
if (WSAStartup (MAKEWORD (iReqWinsockVer, 0), &wsaData)==0)
{
// Проверяем если старшая версия больше или равна требуемой
if (LOBYTE (wsaData.wVersion) >= iReqWinsockVer)
{
/* Вызываем тут различные WinSock функции */
}
else
{
// Требуемая версия недоступна.
}
// Освобождаем WinSock
if (WSACleanup ()≠0)
{
// Освобождение не удалось
}
}
else
{
// Инициализация не удалась
}
socket ()
SOCKET socket (int af, int type, int protocol);
Функция socket () создает новый socket и возвращает его дескриптор. Тип этого дескриптора SOCKET, и он используется во всех функциях, работающих с socket’ами. Единственным недействительным значением дескриптора socket’а является INVALID_SOCKET. Функция принимает три параметра:
af — address family, так называемое, адресное семейство. Этот параметр накладывает определенные ограничения на формат используемых процессом адресов и их интерпретацию. Установи этот параметр в значение AF_INET, чтобы использовать TCP и UDP «семейство».
type — тип создаваемого socket’а. Используй SOCK_STREAM для создания потокового socket’а и SOCK_DGRAM для создания дейтаграммного socket’а.
protocol — протокол, который будет использоваться socket’ом. Этот параметр зависит от «адресного семейства». Чтобы создать TCP socket, Вам нужно указать IPPROTO_TCP.
Функция возвращает дескриптор созданного socket’а, или INVALID_SOCKET, если что-то случилось не так. Функцию socket () можно использовать следующим образом:
SOCKET hSocket;
hSocket = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (hSocket==INVALID_SOCKET)
{
// Действия в случае ошибки
}
closesocket
int closesocket (SOCKET s);
Как понятно из названия, эта функция закрывает socket. Функция возвращает ноль, если все прошло успешно, иначе результатом выполнения функции будет SOCKET_ERROR. Каждый созданный тобою socket с помощью функции socket (), должен быть закрыт с помощью функции closesocket ().
В функцию передается единственный параметр — дескриптор socket’а, который необходимо закрыть. Не пытайся использовать этот socket после вызова функции closesocket (). В лучшем случае компилятор заметит ошибку.
Использование этой функции довольно простое:
closesocket (hSocket);
Однако, в реальный ситуациях, необходимо немного больше операций, что бы закрыть socket должным образом. Это мы рассмотрим немного позднее.
WinSock был разработан таким образом, что бы он мог взаимодействовать с разными протоколами, включая те, которые должны быть добавлены позднее. Поэтому был разработан общий способ адресации. Например, TCP/IP использует IP адрес и номер порта для определения адреса, но другие протоколы могут делать это по-другому. Если бы WinScok придерживался определенного типа адресации, то добавление других протоколов было бы невозможным.
Первый вариант решения этой проблемы — использование структуры sockaddr:
struct sockaddr
{
u_short sa_family;
char sa_data[14];
};
Первое поле этой структуры определяет «адресное семейство» адреса. Данные, хранящиеся в переменной sa_data, могут меняться в зависимости от «адресного семейства». В WinSock определена структура sockaddr_in, которая является TCP/IP версией структуры sockaddr.
struct sockaddr_in {
short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
Последние 8 байт структуры не используются. Они предусмотрены для того, что бы дать структуре нужный размер (такой же как у структуры sockaddr).
Перед тем, как двигаться дальше, необходимо знать про сетевой порядок байт. Под «порядком байт» будем понимать последовательность, в которой хранятся значения, охватывающие несколько байт. Например, 32-битовое целочисленное значение 0×12 345 678 охватывает 4 8-битовых байта. Некоторые компьютеры используют порядок байт, в котором менее значимые байты сохранены сначала. То есть наше число 0×12 345 678 будет храниться в такой последовательности: 0×78, 0×56, 0×34, 0×12 (порядок байтов от младшего к старшему, англ. little-endian). Однако большинство машин используют противоположный порядок, т. е. более значимый байт хранится вначале. В таких машинах наше число будет храниться в виде 0×12, 0×34, 0×56, 0×78. Поскольку протоколы, по которым будут переданы данные между двумя компьютерами, могут иметь разные байтовые порядки, то необходим стандарт, чтобы препятствовать передаче данных неправильным образом.
Поскольку такие протоколы, как TCP/IP работают между разными системами с разным порядком байтов, то был разработан стандарт — порядок от старшего к младшему (big-endian). Запись начинается со старшего байта и заканчивается младшим. Например, 16-битовый номер порта 12 345 (0×3039) в этом представлении будет выглядеть так: сначала 0×30, потом 0×39, т. е. более значимый байт идет сначала. 32-битовый IP адрес хранится аналогичным образом: каждая часть IP адреса хранится в одном байте, и первая часть хранится в первом байте. Например, IP адрес 216.239.51.100 будет храниться в такой последовательности байтов: 216,239,51,100. Этот порядок является стандартным для протоколов TCP/IP, он используется в заголовках пакетов данных и во многих протоколах более высокого уровня, разработанных для использования поверх TCP/IP. Поэтому, порядок байтов от старшего к младшему часто называют сетевым порядком байтов (network byte order).
Кроме параметров sin_family и sa_family в структурах sockaddr_in и sockaddr, соответственно, которые не являются частью протокола, но говорят WinSock, какое «адресное семейство» использовать, все остальные поля этих структура хранятся в сетевом порядке байтов.
WinSock обеспечивает несколько функций для преобразования порядка байтов локальной машины в сетевой порядок байтов:
// Преобразует u_short из порядка байтов локальной машины в сетевой порядок байтов
u_short htons (u_short hostshort);
// Преобразует u_long порядка байтов локальной машины в сетевой порядок байтов
u_long htonl (u_long hostlong);
// Преобразует u_shorth из сетевого порядка байтов в порядок байтов локальной машины
u_short ntohs (u_short netshort);
// Преобразует u_long из сетевого порядка байтов в порядок байтов локальной машины
u_long ntohl (u_long netlong);
Вернемся к структуре sockaddr_in. Как писалось выше, все параметры кроме sin_family имеют сетевой порядок байтов. Этот параметр у нас используется в значении AF_INET. sin_port — это 16-битовый номер порта, sin_addr — 32-битовый IP адрес. sin_zero не используется, этот параметр нужен, чтобы придать структуре нужный размер.
Вот пример заполнения полей структуры sockaddr_in:
sockaddr_in sockAddr1, sockAddr2;
// Устанавливаем адресное семейство
sockAddr1.sin_family = AF_INET;
// Преобразуем номер порта 80 в сетевой порядок байтов
sockAddr1.sin_port = htons (80);
/* inet_addr преобразует строку с IP адресом в long значение, которое является IP адресом в сетевом порядке байтов.
sin_addr.S_un.S_addr определяет long значение в адресном объединении */
sockAddr1.sin_addr.S_un.S_addr = inet_addr («127.0.0.1»);
// Устанавливаем адрес sockAddr2, устанавливая значение каждой из 4 байтовой части:
sockAddr2.sin_addr.S_un.S_un_b.s_b1 = 127;
sockAddr2.sin_addr.S_un.S_un_b.s_b2 = 0;
sockAddr2.sin_addr.S_un.S_un_b.s_b3 = 0;
sockAddr2.sin_addr.S_un.S_un_b.s_b4 = 1;
Функция inet_addr, в вышеприведенном примере, преобразует строковое значение IP адреса (записанного в «точечном» формате) в соответствующее 32-битовое значение в сетевом порядке байтов. Также существует функция inet_ntoa, которая делает тоже самое, только наоборот.
connect
int connect (SOCKET s, const struct sockaddr *name, int namelen);
Функция connect соединят socket с удаленным socket’ом. Эта функция используется на клиентской стороне подключения, т.к. именно клиент является инициатором подключения.
Краткое описание параметров этой функции:
s — неподключенный socket, который ты хотел бы подключить.
name — указатель на структуру sockaddr, в которой содержится имя (адрес) удаленного socket’а, к которому необходимо подключится.
namelen — размер структуры, в которой содержится имя.
Первый параметр — это клиентский socket, использующий соединение, например, только что созданный socket с помощью функции socket (). Остальные два параметра, name и namelen, используются для адресации удаленного socket’а (socket сервера, который находится в режиме прослушивания).
Эту функцию применяют для соединения с сервером. Чтобы обратиться к серверу, ты можешь использовать структуру sockaddr_in, заполнив ее IP адресом и номером порта сервера. Ты можешь поинтересоваться, как получить IP адрес сервера, например, www. vr-online.ru. Позже я покажу как это сделать. Пока что просто представь, что ты его знаешь. Представим, что сервер запущен в локальной сети, на компьютере с IP адресом 192.168.0.5, используя HTTP порт по умолчанию (80). Код, позволяющий подключится к этому серверу, выглядит примерно так:
// Этот код подразумевает, что socket был создан, и его дескриптор хранится в hSocket
sockaddr_in sockAddr;
sockAddr.sin_family = AF_INET;
sockAddr.sin_port = htons (80);
sockAddr.sin_addr.S_un.S_addr = inet_addr («192.168.0.5»);
// Подключаемся к серверу
if (connect (hSocket, (sockaddr*)(&sockAddr), sizeof (sockAddr))≠0)
{
// Действия в случае неудачного подключения
}
/* Замечение: приведение (sockaddr*) необхадимо, т.к. соединение требует переменную типа
sockaddr, а тип переменной sockAddr sockaddr_in. Преобразование безопасно, т.к. оба типа имеют схожую структуру, но компилятор видит их как два разных типа*/
bind
int bind (SOCKET s, const struct sockaddr *name, int namelen);
Связывание socket’а было рассмотрено в предыдущих главах. Напомню, что эта функция связывает адрес с socket’ом.
Параметры:
s — несвязанный socket, который требуется связать.
name — указатель на структуру sockaddr, в которой содержится адрес необходимого socket’а.
namelen — размер структуры, в которой содержится имя.
Для TCP/IP структура sockaddr_in может быть использована как обычно. Например:
sockaddr_in sockAddr;
sockAddr.sin_family = AF_INET;
sockAddr.sin_port = htons (80);
sockAddr.sin_addr.S_un.S_addr = INADDR_ANY; // используем адрес по умолчанию (т.е. любой)
// Связываемся в 80 портом
if (bind (hSocket, (sockaddr*)(&sockAddr), sizeof (sockAddr))≠0)
{
// Действия в случае ошибки
}
Как можно заметить, структура sockaddr_in заполняется необходимой информацией. «Адресное семейство» используется AF_INET для TCP/IP. В этом примере мы связали socket с 80 портом, но не с IP адресом. Указывая INADDR_ANY в качестве значение IP адреса, WinSock выберет адрес за тебя. Это может быть полезно на компьютерах с несколькими сетевыми адаптерами (в нашем примере рассматривается именно такой компьютер) или подключениями.
listen
int listen (SOCKET s, int backlog);
Эта функция устанавливает socket в режим прослушивания. Она получает два параметра:
s — связанный, неподключенный socket, который ты хочешь установить в режим прослушки
backlog — максимальное количество ожидаемых соединений.
Максимальное количество, передаваемое в параметр backlog, сильно зависит от платформы. В linux оно обрезается до SOMAXCONN. В win32, если передано SOMAXCONN, провайдер сервиса отвечает за установку backlog socket’а в максимальное разумное значение. На этой платформе нет стандарта для установки реального backlog-значения. Так как мы использует Windows, то мы смело можем установить этот параметр в значение SOMAXCONN.
Перед вызовом функции listen, socket должен быть связан с определенным адресом. Например, если Вы связали socket с 80 портом, и поставили его в режим прослушивания, то все, входящие по 80 порту, подключения будут направлены в Ваше приложение. Что бы разрешить соединение, должна быть вызвана функция accept. Она будет рассмотрена далее. Следующий фрагмент кода показывает, как вызвать функцию listen для связанного socket’а.
// Этот код подразумевает, что socket был создан, и его дескриптор хранится в hSocket
if (listen (hSocket, SOMAXCONN)≠0)
{
// Действия в случае ошибки
}
accept
SOCKET accept (SOCKET s, struct sockaddr *addr, int *addrlen);
Если socket находится в режиме прослушивание и получает входящие соединение, он может разрешить его с помощью этой функции.
s — socket, который был установлен в режим просулшивания.
addr — указатель на буфер, который получает адрес удаленного socket’а. Этот параметр — указатель на структуру sockaddr, но он требует структуру, определенную «адресным семейством».
addrlen — указатель на целое число, которое содержит длину addr. Перед вызовом функции этот параметр должен иметь размер буфера, на который указывает addr. По завершении функции, в этом параметре будет храниться размер полученных данных.
Если соединение было разрешено, на сервере создается новый socket. Этот новый socket соединяется с socket’ом клиента и все операции между сервером и клиентом проводятся именно по этому socket’у. Socket, который находился в режиме прослушивания, продолжает ждать новые соединения.
sockaddr_in remoteAddr;
int iRemoteAddrLen;
SOCKET hRemoteSocket;
iRemoteAddrLen = sizeof (remoteAddr);
hRemoteSocket = accept (hSocket, (sockaddr*)&remoteAddr, &iRemoteAddrLen);
if (hRemoteSocket==INVALID_SOCKET)
{
// Действия в случае ошибки
}
При успешном завершении функции, возвращаемое значение — дескриптор нового socket’а.
send and recv
int send (SOCKET s, const char *buf, int len, int flags);
s — подключенный socket, для передачи данных.
buf — буфер, содержащий данные для отправки.
len — размер данных для передачи.
flags — определяет способ передачи данных.
int recv (SOCKET s, char *buf, int len, int flags);
s — подключенный socket, для получения данных.
buf — буфер, в котором будут храниться полученные данные.
len — размер полученных данных.
flags — определяет способ получения данных.
Для передачи данных необходимо использовать эти две функции. Первая функция отправляет данные из буфера и возвращает количество отправленных байт. Вторая функция получает данные и сохраняет их в буфер. Последний параметр каждой функции может быть установлен в нулевое значение. В блокирующем режиме функция send «заблокирует» твое приложение, пока все данные не будут переданы.
Хотя на первый взгляд эти функции кажутся простыми, они становятся сложнее для работы в неблокирующем режиме. Когда socket находится в неблокирующем режиме, эти функции могут не выполнить операцию полностью. В следующей главе эта проблема будет рассмотрена детальным образом, а пока что пример использования этих функций в блокирующем режиме (в этом примере будут отправлены полученные данные):
//Пример использования функций send и recv
char buffer[128];
while (true)
{
// Получаем данные
int bytesReceived = recv (hRemoteSocket, buffer, sizeof (buffer), 0);
if (bytesReceived==0) // соединение закрыто
{
break;
}
else if (bytesReceived==SOCKET_ERROR)
{
// Действия при ошибке
}
// Отправляем полученные данные обратно
if (send (hRemoteSocket, buffer, bytesReceived, 0)==SOCKET_ERROR)
{
// Действия при ошибке
}
}
II Практическая часть
2.1 Описание и листинг программы Server Socket
#include
#include
#pragma comment (lib, «ws232.lib») //подключение библиотеки winsock2
void main (void) {
using namespace std;
char PCName [30], ClientName[30], Message[200];
WSAData WSADat; // Свойства WinSock (результат функции WSAStartup)
sockaddr_in sin; // Свойства (адрес) создаваемого сокета
SOCKET Sock, Client; // Серверный и клиентский сокеты
WSAStartup (0×0202,&WSADat); // Инициализация WinSock
// 0×0202 — версия WinSock. Может быть 1.0, 1.1, 2.0, 2.2
// WSADat — структура, куда будут занесены рез. инициализации
gethostname (PCName, 30); // Получение имени текущего ПК
sin.sin_family = AF_INET; // Тип адреса
sin.sin_addr.s_addr = 0; // IP-адрес сервера (пори создании сервера можно 0)
sin.sin_port = htons (2803); // Номер порта сервера
Sock = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP); // Создание сокета
bind (Sock, (sockaddr*)&sin, sizeof (sin)); // Связывание созданного сокета с адресом sin
Рис.3
// ***** Ожидание клиента
cout << «Wait of client…» << endl;
listen (Sock, SOMAXCONN); // Прослушивание сокета сервером (для подключения клиента)
Client = accept (Sock, (sockaddr*)&sin, 0); // Ожидание клиента
recv (Client, ClientName, 30, 0); // Получение имени компьютера клиента
send (Client, PCName, strlen (PCName) + 1, 0); // Отправка имени этого компьютера (сервера) Рис.4
cout << «Client («<< ClientName << «) has connected!» << endl;
// ***** Меню
int Menu;
do {
cout << «1. Send message;» << endl;
cout << «2. Get Message;» << endl;
cout << «3. Quit;» << endl;
cout << «Make your selection: «;cin >> Menu;
switch (Menu) {
case 1:
Рис.4
// Отправка сообщения клиенту
cout << «Enter message: «; cin. get ();
cin.getline (Message, 200);
if (send (Client, Message, strlen (Message) + 1, 0) ≠ SOCKET_ERROR) cout << «Sent!n» ;
else cout << «Error of sending! n» ;
break;
case 2:
Рис.5
// Приём сообщения от клиента
if (recv (Client, Message, 200, 0) ≠ SOCKET_ERROR) {
cout << Message << endl;
cin.get ();
}
else cout << «Error of getting! n» ;
break;
};
cout << endl;
} while (Menu ≠ 3);
// Закрытие сокетов и окончание работы с WinSock
closesocket (Sock);
closesocket (Client);
WSACleanup ();
}
2.2 Описание и листинг программы Client Socket
#include
#include
#pragma comment (lib, «ws232.lib»)////подключение библиотеки winsock2
void main (void) {
using namespace std;
char PCName[30], ServerName[30], Message[200], IP[16] = { 0 };
WSAData WSADat; // Свойства WinSock (результат функции WSAStartup)
sockaddr_in sin; // Свойства (адрес) создаваемого сокета
SOCKET Sock; // Клиентский сокет Рис.6
// Ввод IP-адреса сервера
cout << «Enter server’s IP: «;
cin.getline (IP, 16);
WSAStartup (0×0202, &WSADat); // Инициализация WinSock
// 0×0202 — версия WinSock. Может быть 1.0, 1.1, 2.0, 2.2
// WSADat — структура, куда будут занесены рез. Инициализации
gethostname (PCName, 30); // Получение имени текущего ПКsin. sin_family = AF_INET; // Тип адресаsin. sin_addr.s_addr = inet_addr (IP); // IP-адрес сервера (пори создании сервера можно 0)
sin.sin_port = htons (2803); // Номер порта сервера
Sock = socket (AF_INET, SOCK_STREAM, 0); // Создание сокета
// ***** Подключение к серверу
cout << «Connecting to server…» << endl;
if (connect (Sock, (sockaddr*)&sin, sizeof (sin)) == SOCKET_ERROR) {
cout << «Error of connecting! n» ;
goto End;
}
send (Sock, PCName, strlen (PCName) + 1, 0); // Отправка имени этого компьютера (клиента)
recv (Sock, ServerName, 30, 0); // Получение имени компьютера сервера
cout << «Connecting to „“ << ServerName << „“ is ready!» << endl;
// ***** Меню
int Menu;
do {
cout << «1. Send message;» << endl;
cout << «2. Get Message;» << endl;
cout << «3. Quit;» << endl;
cout << «Make your selection: «;cin >> Menu;
switch (Menu) {
case 1:
// Отправка сообщения серверу
cout << «Enter message: «; cin. get ();
cin.getline (Message, 200);
if (send (Sock, Message, strlen (Message) + 1, 0) ≠ SOCKET_ERROR) cout << «Sent!n» ;
else cout << «Error of sending! n» ;
break;
case 2:
// Приём сообщения от сервера
if (recv (Sock, Message, 200, 0) ≠ SOCKET_ERROR) {
cout << Message << endl;
cin.get ();
}
else cout << «Error of getting! n» ;
break;
};
cout << endl;
} while (Menu ≠ 3);
End:
// Закрытие сокетов и окончание работы с WinSock
closesocket (Sock);
WSACleanup ();
}
Заключение
Сетевое программирование… думаю, что не стоит сомневается в актуальности этой темы, и так понятно, что сегодня сетевые технологии находятся на достаточно высоком уровне развития. А это значит, что программисты в этой области нужны не менее чем в других областях. Сетевое программирование кажется легче, чем оно есть на самом деле. WinSock функции, которые использовались для коммуникации через сеть, довольно сложно. Сетевое программирование — это больше чем просто получение или отправление данных. Например, приходится сталкиваться с синхронизацией. Я решил начать с основ сетей и принципов работы сетевого программирования, которые я описал в теоретической части своей курсовой работы. Материал сфокусирован на аспектах сетевого программирования. В итоге была написана простая программа осуществляющая сокетное соединение с помощью библиотеки winsock.
Список использованных источников
1. Витаминюк А.И.- Создание, обслуживание и администрирование сетей на 100% - 2010 год. Санкт-Петербург 232с
2. Виснадул Б. Д., Лупин С. А., Сидоров С. В., Чумаченко П. Ю. — Основы компьютерных сетей. 2007 год. 272с Компьютерные сети — Марк А. Спортак, Ричард Пит, Джеймс Ф. Коузи. Диасофт, 1999 г.
4. Методическая разработка по дисциплине «Технологии разработки прикладных программ» на тему: «Программирование сетевых приложений в среде C++ Builder» программа протокол socket
5. http://www.vr-online.ru/content/s-setevoe-programmirovanie-chast-1−3999
6. http://www.realcoding.net/article/view/1833
7. http://www.cyberforum.ru/cpp-beginners/thread635902.html