Работа с winsocket
Несмотря на то что в интернете довольно таки много материала на эту тему, я решил написать еще одну статью, в ней я проведу параллели между двумя самыми популярными языками — С и Delphi и рассмотрю работу с блокируемыми синхронными сокетами. Работа с сокетами практически не отличается в этих языках, но вся же я буду давать примеры на С и Дельфи.
Для чего нужны сокеты?
Всем очень часто приходится писать программы, прямо или косвенно работающие с сетями. Можно воспользоваться компонентами и классами, написанными за нас и уложиться в 100 строчек при написании простенького клиент — сервера. Но иногда программа должна иметь очень маленький размер и воспользоваться компонентами не представляется возможным. Тут то мы и вспоминаем о WinSockets API. Преимущества API это не только минимальный размер файла, но и полная базонезависимость кода и полное понимание логики работы программы.
Что такое Сокеты?
Сокеты (Sockets) — это высокоуровневый интерфейс взаимодействия с различными сетевыми протоколами. Для работы с сокетами есть класс API функций — Windows Sockets 2 API, описанные в заголовочном файле winsock2.h для С или WinSock.pas для Delphi. Следовательно для работы с WinSokets API в С нужно подключить заголовочный файл директивой
и слинковать (подключить) проект с lib — файлом (обычно это libws2_32.lib). В Delphi нужно добавить WinSock в список Uses.
Начинаем работать
Чтобы работать с сокетами, надо для начала инициализировать функции библиотеки WinSock. Для этого надо вызвать функцию int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData); передав в старшем байта слова wVersionRequested номер требуемой версии, а в младшем – номер подверсии. Аргумент lpWSAData указывает на структуру WSADATA, в которую при успешной инициализации будет занесена информация о производителе библиотеки. Если инициализация провалилась, функция возвращает ненулевое значение.
Для С вызов будет таким:
Затем надо создать объект «сокет». Для этого вызывается функция SOCKET socket(int af, int type, int protocol); Аргумент af — семейство используемых протоколов. Для интернет — приложений он должен иметь значение AF_INET. Аргумент type — тип создаваемого сокета — потоковый — SOCK_STREAM или дейтаграммный — SOCK_DGRAM.
Аргумент protocol уточняет какой транспортный протокол следует использовать. Нулевое значение соответствует выбору по умолчанию:
TCP — для потоковых сокетов;
UDP для дейтаграммных;
Если функция завершилась успешно она возвращает дескриптор a.k.a. хэндл a.k.a. указатель на сокет, в противном случае INVALID_SOCKET.
Дальнейшие действия различаются в зависимости от того, серверное приложение или клиентское.
Клиент
Чтобы установить соединение с удаленным компьютером, клиент должен вызвать функцию int connect(SOCKET s, const struct sockaddr FAR* name, int namelen); Аргумент s — дескриптор сокета, возращенный функцией socket. Аргумент name — указатель на структуру sockaddr, содержащую в себе адрес и порт удаленного узла с которым устанавливается соединение. Но смотрите внимательнее, функция принимает указатель на структуру sockaddr, а нам надо передать структуру sockaddr_in. Приведем её к типу sockaddr и все получиться. Аргумент namelen — размер структуры sockaddr. Объявление структуры выглядит так:
Среди производителей процессоров нет единого мнения на счет порядка следования младших и старших байт. Так например, у микропроцессоров Intel младшие байты располагаются по меньшим адресам, а у микропроцессоров Motorola 68000 — наоборот. Это вызывает проблемы при межсетевом взаимодействии, поэтому был введен специальный сетевой порядок байт, предписывающий старший байт передавать первым (противоположно тому, как сделано у Intel).
Для преобразований чисел из сетевого формата в формат локального хоста и наоборот предусмотрено четыре функции — первые две манипулируют короткими целыми (16-битными словами), а две последние — длинными (32-битными двойными словами):
Так как номер порта находиться в диапазоне [0..65536], то он занимает одно слово (2 байта или 16 бит), поэтому мы используем функцию u_short htons (u_short hostshort ); для перевода в сетевой порядок байт.
После вызова функции connect система пытается установить соединение с указанным узлом. Если это сделать не удастся, функция возвратит ненулевое значение.
На этом клиентская часть завершена и мы можем обмениваться данными.
Сервер
Прежде, чем сервер сможет использовать сокет, он должен связать его с локальным адресом. Связывание осуществляется вызовом функции
int bind(SOCKET s, const struct sockaddr FAR* name, int namelen);
Аргумент s — дескриптор сокета, возращенный функцией socket.
Аргумент name — указатель на структуру sockaddr.
Аргумент namelen — размер структуры sockaddr.
Если все прошло успешно, функция возвращает ненулевое значение. Чтобы связать сокет с с каким либо локальным адресом, необходимо заполнить структуру sockaddr_in. Локальный, как, впрочем, и любой другой адрес Интернета, состоит из IP-адреса узла и номера порта.
Если сервер имеет несколько IP адресов или мы биндимся на локальный компьютер, то для этого вместо IP-адреса следует указать константу INADDR_ANY равную нулю.
В дельфи тут все тоже самое, поэтому код приводить не стану.
Выполнив связывание, потоковый сервер переходит в режим ожидания подключений, вызывая функцию
int listen (SOCKET s, int backlog );
где s – дескриптор сокета,
а backlog – максимально допустимый размер очереди сообщений.
Если все проходит успешно, функция возвращает ноль.
После этого, сервер должен извлекать запросы из очереди сообщений функцией SOCKET accept (SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen); Она создает новый сокет, выполняет связывание и возвращает дескриптор сокета, в структуру sockaddr_in функция ip адрес и порт подключившегося клиента. Если мы вызовем accept, а очередь будет пуста, функция не возвратит управление, пока не подключиться клиент. Если при выполнение произошла ошибка, функция возвратит отрицательное значение.
Для параллельной работы с несколькими клиентами нужно создавать новый поток или процесс, передавая ему дескриптор созданного функцией accept сокета. Если этого не сделать, то одновременно сервер не сможет обслужить более одного клиента.
На этом создание серверной части завершена и я распишу обмен данными.
Обмен данными
После того, как мы установили соединение, сокеты могут обмениваться с удаленным компьютером данными, вызывая функции
int send(SOCKET s, const char FAR * buf, int len,int flags); //Для посылки данных
и
int recv(SOCKET s, char FAR* buf, int len, int flags); //Для приема данных
Функция send возвращает управление сразу же после ее выполнения, независимо от того, получила ли принимающая сторона наши данные или нет. При успешном завершении функция возвращает количество передаваемых данных.
Функция recv возвращает управление после того, как получит хотя бы один байт. Каждый вызов recv за один раз получает столько байтов, сколько их было послано функцией send. Поэтому выделяйте ей большой буфер, чтобы избегать переполнения и ошибок.
Чтобы завершить работу с сокетом и закрыть соединение, надо вызвать функцию int closesocket(SOCKET s); передав ей дескриптор сокета.
Перед выходом из программы необходими вызывать функцию int WSACleanup(void); которая деинициализирует библиотеку.
P.S.
Вот я и рассмотрел всего лишь мизерную часть работы с сокетами. Чтобы рассмотреть все, нужна целая книга.
ГЛАВА 12
Сетевое программирование с помощью сокетов Windows
Именованные каналы пригодны для организации межпроцессного взаимодействия как в случае процессов, выполняющихся на одной и той же системе, так и в случае процессов, выполняющихся на компьютерах, связанных друг с другом локальной или глобальной сетью. Эти возможности были продемонстрированы на примере клиент-серверной системы, разработанной в главе 11, начиная с программы 11.2.
Однако как именованные каналы, так и почтовые ящики (в отношении которых для простоты мы будем использовать далее общий термин — «именованные каналы», если различия между ними не будут играть существенной роли) обладают тем недостатком, что они не являются промышленным стандартом. Это обстоятельство усложняет перенос программ наподобие тех, которые рассматривались в главе 11, в системы, не принадлежащие семейству Windows, хотя именованные каналы не зависят от протоколов и могут выполняться поверх многих стандартных промышленных протоколов, например TCP/IP.
Возможность взаимодействия с другими системами обеспечивается в Windows поддержкой сокетов (sockets) Windows Sockets — совместимого и почти точного аналога сокетов Berkeley Sockets, де-факто играющих роль промышленного стандарта. В этой главе использование API Windows Sockets (или «Winsock») показано на примере модифицированной клиент-серверной системы из главы 11. Результирующая система способна функционировать в глобальных сетях, использующих протокол TCP/IP, что, например, позволяет серверу принимать запросы от клиентов UNIX или каких-либо других, отличных от Windows систем.
Читатели, знакомые с интерфейсом Berkeley Sockets, при желании могут сразу же перейти непосредственно к рассмотрению примеров, в которых не только используются сокеты, но также вводятся новые возможности сервера и демонстрируются дополнительные методы работы с библиотеками, обеспечивающими безопасную многопоточную поддержку.
Привлекая средства обеспечения взаимодействия между разнородными системами, ориентированные на стандарты, интерфейс Winsock открывает перед программистами возможность доступа к высокоуровневым протоколам и приложениям, таким как ftp, http, RPC и СОМ, которые в совокупности предоставляют богатый набор высокоуровневых моделей, обеспечивающих поддержку межпроцессного сетевого взаимодействия для систем с различной архитектурой.
В данной главе указанная клиент-серверная система используется в качестве механизма демонстрации интерфейса Winsock, и в процессе того, как сервер будет модифицироваться, в него будут добавляться новые интересные возможности. В частности, нами будут впервые использованы точки входа DLL (глава 5) и внутрипроцессные серверы DLL. (Эти новые средства можно было включить уже в первоначальную версию программы в главе 11, однако это отвлекло бы ваше внимание от разработки основной архитектуры системы.) Наконец, дополнительные примеры покажут вам, как создаются безопасные реентерабельные многопоточные библиотеки.
Поскольку интерфейс Winsock должен соответствовать промышленным стандартам, принятые в нем соглашения о правилах присвоения имен и стилях программирования несколько отличаются от тех, с которыми мы сталкивались в процессе работы с описанными ранее функциями Windows. Строго говоря, Winsock API не является частью Win32/64. Кроме того, Winsock предоставляет дополнительные функции, не подчиняющиеся стандартам; эти функции используются лишь в случае крайней необходимости. Среди других преимуществ, обеспечиваемых Winsock, следует отметить улучшенную переносимость результирующих программ на другие системы.
Сокеты Windows
Winsock API разрабатывался как расширение Berkley Sockets API для среды Windows и поэтому поддерживается всеми системами Windows. К преимуществам Winsock можно отнести следующее:
• Перенос уже имеющегося кода, написанного для Berkeley Sockets API, осуществляется непосредственно.
• Системы Windows легко встраиваются в сети, использующие как версию IPv4 протокола TCP/IP, так и постепенно распространяющуюся версию IPv6. Помимо всего остального, версия IPv6 допускает использование более длинных IP-адресов, преодолевая существующий 4-байтовый адресный барьер версии IPv4.
• Сокеты могут использоваться совместно с перекрывающимся вводом/выводом Windows (глава 14), что, помимо всего прочего, обеспечивает возможность масштабирования серверов при увеличении количества активных клиентов.
• Сокеты можно рассматривать как дескрипторы (типа HANDLE) файлов при использовании функций ReadFile и WriteFile и, с некоторыми ограничениями, при использовании других функций, точно так же, как в качестве дескрипторов файлов сокеты применяются в UNIX. Эта возможность оказывается удобной в тех случаях, когда требуется использование асинхронного ввода/вывода и портов завершения ввода/вывода.
• Существуют также дополнительные, непереносимые расширения.
Инициализация Winsock
Winsock API поддерживается библиотекой DLL (WS2_32.DLL), для получения доступа к которой следует подключить к программе библиотеку WS_232.LIB. Эту DLL следует инициализировать с помощью нестандартной, специфической для Winsock функции WSAStartup, которая должна быть первой из функций Winsock, вызываемых программой. Когда необходимость в использовании функциональных возможностей Winsock отпадает, следует вызывать функцию WSACleanup. Примечание. Префикс WSA означает «Windows Sockets asynchronous …» («Асинхронный Windows Sockets …»). Средства асинхронного режима Winsock нами здесь не используются, поскольку при возникновении необходимости в выполнении асинхронных операций мы можем и будем использовать потоки.
Хотя функции WSAStartup и WSACleanup необходимо вызывать в обязательном порядке, вполне возможно, что они будут единственными нестандартными функциями, с которыми вам придется иметь дело. Распространенной практикой является применение директив препроцессора #ifdef для проверки значения символической константы _WIN32 (обычно определяется Visual C++ на стадии компиляции), в результате чего функции WSA будут вызываться только тогда, когда вы работаете в Windows). Разумеется, такой подход предполагает, что остальная часть кода не зависит от платформы.
int WSAStartup(WORD wVersionRequired, LPWSADATA ipWSAData);
wVersionRequired — указывает старший номер версии библиотеки DLL, который вам требуется и который вы можете использовать. Как правило, версии 1.1 вполне достаточно для того, чтобы обеспечить любое взаимодействие с другими системами, в котором у вас может возникнуть необходимость. Тем не менее, во всех системах Windows, включая Windows 9x, доступна версия Winsock 2.0, которая и используется в приведенных ниже примерах. Версия 1.1 считается устаревшей и постепенно выходит из употребления.
Функция возвращает ненулевое значение, если запрошенная вами версия данной DLL не поддерживается.
Младший байт параметра wVersionRequired указывает основной номер версии, а старший байт — дополнительный. Обычно используют макрос MAKEWORD; таким образом, выражение MAKEWORD (2,0) представляет версию 2.0.
ipWSAData — указатель на структуру WSADATA, которая возвращает информацию о конфигурации DLL, включая старший доступный номер версии. О том, как интерпретировать ее содержимое, вы можете прочитать в материалах оперативной справки Visual Studio.
Чтобы получить более подробную информацию об ошибках, можно воспользоваться функцией WSAGetLastError, но для этой цели подходит также функция GetLastError, а также функция ReportError, разработанная в главе 2.
По окончании работы программы, а также в тех случаях, когда необходимости в использовании сокетов больше нет, следует вызывать функцию WSACleanup, чтобы библиотека WS_32.DLL, обслуживающая сокеты, могла освободить ресурсы, распределенные для этого процесса.
Создание сокета
Инициализировав Winsock DLL, вы можете использовать стандартные (Berkeley Sockets) функции для создания сокетов и соединений, обеспечивающих взаимодействие серверов с клиентами или взаимодействие равноправных узлов сети между собой.
Используемый в Winsock тип данных SOCKET аналогичен типу данных HANDLE в Windows, и его даже можно применять совместно с функцией ReadFile и другими функциями Windows, требующими использования дескрипторов типа HANDLE. Для создания (или открытия) сокета служит функция socket.
SOCKET socket(int af, int type, int protocol);
Тип данных SOCKET фактически определяется как тип данных int, потому код UNIX остается переносимым, не требуя привлечения типов данных Windows.
af — обозначает семейство адресов, или протокол; для указания протокола IP (компонент протокола TCP/IP, отвечающий за протокол Internet) следует использовать значение PF_INET (или AF_INET, которое имеет то же самое числовое значение, но обычно используется при вызове функции bind).
type — указывает тип взаимодействия: ориентированное на установку соединения (connection-oriented communication), или потоковое (SOCK_STREAM), и дейтаграммное (datagram communication) (SOCK_DGRAM), что в определенной степени сопоставимо соответственно с именованными каналами и почтовыми ящиками.
protocol — является излишним, если параметр af установлен равным AF_INET; используйте значение 0.
В случае неудачного завершения функция socket возвращает значение INVALID_SOCKET.
Winsock можно использовать совместно с протоколами, отличными от TCP/IP, указывая различные значения параметра protocol; мы же будем использовать только протокол TCP/IP.
Как и в случае всех остальных стандартных функций, имя функции socket не должно содержать прописных букв. Это является отходом от соглашений, принятых в Windows, и продиктовано необходимостью соблюдения промышленных стандартов.
Серверные функции сокета
В нижеследующем обсуждении под сервером будет пониматься процесс, который принимает запросы на образование соединения через заданный порт. Несмотря на то что сокеты, подобно именованным каналам, могут использоваться для создания соединений между равноправными узлами сети, введение указанного различия между узлами является весьма удобным и отражает различия в способах, используемых обеими системами для соединения друг с другом.
Если не оговорено иное, типом сокетов в наших примерах всегда будет SOCK_STREAM. Сокеты типа SOCK_DGRAM рассматривается далее в этой главе.
Связывание сокета
Следующий шаг заключается в привязке сокета к его адресу и конечной точке (endpoint) (направление канала связи от приложения к службе). Вызов socket, за которым следует вызов bind, аналогичен созданию именованного канала. Однако не существует имен, используя которые можно было бы различать сокеты данного компьютера. Вместо этого в качестве конечной точки службы используется номер порта (port number). Любой заданный сервер может иметь несколько конечных точек. Прототип функции bind приводится ниже.
int bind(SOCKET s, const struct sockaddr *saddr, int namelen);
s — несвязанный сокет, возвращенный функцией socket.
saddr — заполняется перед вызовом и задает протокол и специфическую для протокола информацию, как описано ниже. Кроме всего прочего, в этой структуре содержится номер порта.
namelen — присвойте значение sizeof (sockaddr).
В случае успешного выполнения функция возвращает значение 0, иначе SOCKET_ERROR. Структура sockaddr определяется следующим образом:
typedef struct sockaddr SOCKADDR, *PSOCKADDR;
Первый член этой структуры, sa_family, обозначает протокол. Второй член, sa_data, зависит от протокола. Internet-версией структуры sa_data является структура sockaddr_in:
struct in_addr sin_addr; /* 4-байтовый IP-адрес */
typedef struct sockaddr_in SOCKADDR_IN, *PSOCKADDR IN;
Обратите внимание на использование типа данных short integer для номера порта. Кроме того, номер порта и иная информация должны храниться с соблюдением подходящего порядка следования байтов, при котором старший байт помещается в крайней позиции справа (big-endian), чтобы обеспечивалась двоичная совместимость с другими системами. В структуре sin_addr содержится подструктура s_addr, заполняемая уже знакомым нам 4-байтовым IP-адресом, например 127.0.0.1, указывающим систему, чей запрос на образование соединения должен быть принят. Обычно удовлетворяются запросы любых систем, в связи с чем следует использовать значение INADDR_ANY, хотя этот символический параметр должен быть преобразован к корректному формату, как показано в приведенном ниже фрагменте кода.
Для преобразования текстовой строки с IP-адресом к требуемому формату можно использовать функцию inet_addr, поэтому член sin_addr.s_addr переменной sockaddr_in инициализируется следующим образом:
О связанном сокете, для которого определены протокол, номер порта и IP-адрес, иногда говорят как об именованном сокете (named socket).
Перевод связанного сокета в состояние прослушивания
Функция listen делает сервер доступным для образования соединения с клиентом. Аналогичной функции для именованных каналов не существует.
int listen(SOCKET s, int nQueueSize);
Параметр nQueueSize указывает число запросов на соединение, которые вы намерены помещать в очередь сокета. В версии Winsock 2.0 значение этого параметра не имеет ограничения сверху, но в версии 1.1 оно ограничено предельным значением SOMAXCON (равным 5).
Прием клиентских запросов соединения
Наконец, сервер может ожидать соединения с клиентом, используя функцию accept, возвращающую новый подключенный сокет, который будет использоваться в операциях ввода/вывода. Заметьте, что исходный сокет, который теперь находится в состоянии прослушивания (listening state), используется исключительно в качестве параметра функции accept, а не для непосредственного участия в операциях ввода/вывода.
Функция accept блокируется до тех пор, пока от клиента не поступит запрос соединения, после чего она возвращает новый сокет ввода/вывода. Хотя рассмотрение этого и выходит за рамки данной книги, возможно создание неблокирующихся сокетов, а в сервере (программа 12.2) для приема запроса используется отдельный поток, что позволяет создавать также неблокирующиеся серверы.
SOCKET accept(SOCKET s, LPSOCKADDR lpAddr, LPINT lpAddrLen);
s — прослушивающий сокет. Чтобы перевести сокет в состояние прослушивания, необходимо предварительно вызвать функции socket, bind и listen.
lpAddr — указатель на структуру sockaddr_in, предоставляющую адрес клиентской системы.
lpAddrLen — указатель на переменную, которая будет содержать размер возвращенной структуры sockaddr_in. Перед вызовом функции accept эта переменная должна быть инициализирована значением sizeof(struct sockaddr_in).
Отключение и закрытие сокетов
Для отключения сокетов применяется функция shutdown(s, how). Аргумент how может принимать одно из двух значений: 1, указывающее на то, что соединение может быть разорвано только для посылки сообщений, и 2, соответствующее разрыву соединения как для посылки, так и для приема сообщений. Функция shutdown не освобождает ресурсы, связанные с сокетом, но гарантирует завершение посылки и приема всех данных до закрытия сокета. Тем не менее, после вызова функции shutdown приложение уже не должно использовать этот сокет.
Когда работа с сокетом закончена, его следует закрыть, вызвав функцию closesocket(SOCKET s). Сначала сервер закрывает сокет, созданный функцией accept, а не прослушивающий сокет, созданный с помощью функции socket. Сервер должен закрывать прослушивающий сокет только тогда, когда завершает работу или прекращает принимать клиентские запросы соединения. Даже если вы работаете с сокетом как с дескриптором типа HANDLE и используете функции ReadFile и WriteFile, уничтожить сокет одним только вызовом функции CloseHandle вам не удастся; для этого следует использовать функцию closesocket.
Пример: подготовка и получение клиентских запросов соединения
Ниже приводится фрагмент кода, показывающий, как создать сокет и организовать прием клиентских запросов соединения.
В этом примере используются две стандартные функции: htons («host to network short» — «ближняя связь») и htonl («host to network long» — «дальняя связь»), которые преобразуют целые числа к форме с обратным порядком байтов, требуемой протоколом IP.
Номером порта сервера может быть любое число из диапазона, допустимого для целых чисел типа short integer, но для определенных пользователем служб обычно используются числа в диапазоне 1025—5000. Порты с меньшими номерами зарезервированы для таких известных служб, как telnet или ftp, в то время как порты с большими номерами предполагаются для использования других стандартных служб.
struct sockaddr_in SrvSAddr; /* Адресная структура сервера. */
SrvSock = socket(AF_INET, SOCK_STREAM, 0);
WINSocket
No connection could be made because the target computer actively refused it. This usually results from trying to connect to a service that is inactive on the foreign host—that is, one with no server application running.
я так понял, что поднятый сервер отказывает в подключении. поправьте, пожалуйста, если ошибаюсь, и помогите, как решить))
winsocket
в общем суть проблемы: нужно приложение, которое по нажатию кнопки на одной машине откроет.
учусть WinSocket
делаю по примеру с этого форума среда Visual studio 11 #include #include .
WinSocket — нужна помощь
Народ уже полностью намучился, вот есть пример кода сервера. Вопрос следующий при получении.
Winsocket задержка при отправке
изучаю сокеты, вот какая возникла проблема: if (send(webSocket, httpRequest.c_str().
Winsocket не подключается к удаленному айпи
Доброго времени суток, знатоки! Написал две программы: сервер и клиент. Сервер открывает сокет и.
Ты не вызвал listen и accept. И в одном потоке такое не провернуть, так как listen переводит поток в ожидание.
listen — дожидается входящего соединения
accpet — создаёт сокет со стороны сервера для этого соединения
так подключится удалось. дальше пытаюсь эксепт вызвать:
вот что получается:
10.04.2010, 13:15 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
10.04.2010, 13:35 | 5 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
accept надо вызывать после listen. Тут плохо, что смешано в кучу и клиентская часть и серверная. Добавлено через 12 минут Работа с Winsocket в Visual C+Использование функций локального управления для выполнения подготовительных действий, необходимых для организации взаимодействия двух программ-партнеров. Функции установления связи, обмена данными и закрытия связи. Использование socket-интерфейса.
Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную нижеСтуденты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны. HTML-версии работы пока нет. Подобные документыЯзык манипуляции данными. Процесс отбора данных. Использование агрегатных функций и специальных операторов в условиях отбора. Создание и использование представлений и хранимых процедур. Использование триггеров, разработка интерфейса пользователя. лабораторная работа [70,6 K], добавлен 13.02.2013 Характеристика буфера обмена как области памяти, резервируемой системой Windows для организации обмена данными между приложениями. Копирование и перемещение файлов как функции буфера обмена. Изучение абсолютной и относительной адресации ячеек MS Excel. контрольная работа [13,9 K], добавлен 11.09.2011 Составление блок-схемы алгоритма решения задачи, погрешности вычисления суммы членов числового ряда. Разработка программ на языке на Visual Basic, работа с массивами. Особенности работы со строковыми данными. Варианты реализации формы приложения. контрольная работа [220,4 K], добавлен 18.06.2010 Рождение и развитие Basic. Краткое описание Visual Basic for Applications. Новые возможности Visual Basic 5.0. Пример взаимодействия Excel и Visual Basic. Программирование табличных функций. Встраивание, применение функций. Формы, средства управления OLE. реферат [20,7 K], добавлен 11.03.2010 ISDN как цифровая сеть с интеграцией обслуживания для совместимости услуги телефонной связи и обмена данными. Передача звукового сигнала в цифровой форме. Повышение скорости обмена данными по обычной телефонной сети. Минимальное число двоичных разрядов. лекция [230,6 K], добавлен 15.04.2014 Общие понятия реляционного похода к базам данных. Разработка программы для автоматизации функций руководителя салона сотовой связи. Детализация бизнес-процессов. Интерфейс для работы пользователя. Тестирование разработанной информационной системы. курсовая работа [2,2 M], добавлен 26.06.2012 Приобретение навыков самостоятельной работы по сопряжению макета на базе микроконтроллера AVR АТMEGA128 с компьютером. Принципы управления двунаправленным обменом данными. Программирование последовательного интерфейса в операционных системах Windows. лабораторная работа [365,4 K], добавлен 16.06.2011 Вычисление выражений, использование стандартных функций; работа с графикой. Порядок действий при вычислении, способы ввода данных с клавиатуры. Построение таблиц функций. Организация циклов в программе, итерационные процессы. Работа с массивами чисел. контрольная работа [614,7 K], добавлен 16.09.2012 Изучение сущности и основных функций программного интерфейса для обеспечения обмена данными между процессами, который называется сокет. Сокеты и UNIX. Атрибуты и именование сокета. Установка соединения (сервер, клиент). Обмен данными. Закрытие сокета. презентация [99,1 K], добавлен 12.05.2013 Разработка программного обеспечения для микропроцессорных систем МК51, интерфейсы в системах связи, основы асинхронной связи. Этапы решения задачи на ЭВМ, принципы тестирования программ и их отладка. Расчет затрат на разработку программного продукта. дипломная работа [270,6 K], добавлен 19.06.2010 Работы в архивах красиво оформлены согласно требованиям ВУЗов и содержат рисунки, диаграммы, формулы и т.д. Работа с winsocketИздано: 2003, BHV Самоучитель игры на WINSOCKСокеты (sockets) представляют собой высокоуровневый унифицированный интерфейс взаимодействия с телекоммуникационными протоколами. В технической литературе встречаются различные переводы этого слова — их называют и гнездами, и соединителями, и патронами, и патрубками, и т .д. По причине отсутствия устоявшегося русскоязычного термина, в настоящей статье сокеты будет именоваться сокетами и никак иначе. Программирование сокетов несложно само по себе, но довольно поверхностно описано в доступной литературе, а Windows Sockets SDK содержит многоженство ошибок как в технической документации, так и в прилагаемых к ней демонстрационных примерах. К тому же имеются значительные отличия реализаций сокетов в UNIX и в Windows, что создает очевидные проблемы. Автор постарался дать максимально целостное и связанное описание, затрагивающее не только основные моменты, но и некоторые тонкости не известные рядовым программистам. Ограниченный объем журнальной статьи не позволяет рассказать обо всем, поэтому, пришлось сосредоточиться только на одной реализации сокетов — библиотеке Winsock 2, одном языке программирования — Си/Си ++ (хотя сказанное большей частью приемлемо к Delphi, Perl и т. д.) и одном виде сокетов — блокируемых синхронных сокетах. ALMA MATERОсновное подспорье в изучении сокетов — Windows Sockets 2 SDK. SDK — это документация, набор заголовочных файлов и инструментарий разработчика. Документация не то, чтобы очень хороша — но все же написана достаточна грамотно и позволяет, пускай, не без труда, освоить сокеты даже без помощи какой-либо другой литературы. Причем, большинство книг, имеющиеся на рынке, явно уступают Microsoft в полноте и продуманности описания. Единственный недостаток SDK — он полностью на английском (для некоторых это очень существенно). Из инструментария, входящего в SDK, в первую очередь хотелось бы выделить утилиту sockeye.exe — это настоящий «тестовый стенд» разработчика. Она позволяет в интерактивном режиме вызывать различные сокет-функции и манипулировать ими по своему усмотрению. Демонстрационные программы, к сожалению, не лишены ошибок, причем порой довольно грубых и наводящих на мысли — а тестировались ли эти примеры вообще? (Например, в исходном тексте программы simples.c в вызове функций send и sendto вместо strlen стоит sizeof) В то же время, все примеры содержат множество подробных комментариев и раскрывают довольно любопытные приемы нетрадиционного программирования, поэтому ознакомится с ними все-таки стоит. Из WEB-ресуросов, посвященных программированию сокетов, и всему, что с ними связано, в первую очередь хотелось бы отметить следующие три: sockaddr.com; www.winsock.com и www.sockets.com. Обзор сокетовБиблиотека Winsock поддерживает два вида сокетов — синхронные (блокируемые) и асинхронные (неблокируемые). Синхронные сокеты задерживают управление на время выполнения операции, а асинхронные возвращают его немедленно, продолжая выполнение в фоновом режиме, и, закончив работу, уведомляют об этом вызывающий код. ОС Windows 3.x поддерживает только асинхронные сокеты, поскольку, в среде с корпоративной многозадачностью захват управления одной задачей «подвешивает» все остальные, включая и саму систему. ОС Windows 9x\NT поддерживают оба вида сокетов, однако, в силу того, что синхронные сокеты программируются более просто, чем асинхронные, последние не получили большого распространения. Эта статья посвящена исключительно синхронным сокетам (асинхронные — тема отдельного разговора). Сокеты позволяют работать со множеством протоколов и являются удобным средством межпроцессорного взаимодействия, но в данной статье речь будет идти только о сокетах семейства протоколов TCP/IP, использующихся для обмена данными между узлами сети Интернет. Все остальные протоколы, такие как IPX/SPX, NetBIOS по причине ограниченности объема журнальной статьи рассматриваться не будут. Независимо от вида, сокеты делятся на два типа — потоковые и дейтаграммные. Потоковые сокеты работают с установкой соединения, обеспечивая надежную идентификацию обоих сторон и гарантируют целостность и успешность доставки данных. Дейтаграмные сокеты работают без установки соединения и не обеспечивают ни идентификации отправителя, ни контроля успешности доставки данных, зато они заметно быстрее потоковых. Выбор того или иного типа сокетов определяется транспортным протоколом на котором работает сервер, — клиент не может по своему желанию установить с дейтаграммным сервером потоковое соединение. Замечание: дейтаграммные сокеты опираются на протокол UDP, а потоковые на TCP. Первый шаг, второй, третийДля работы с библиотекой Winsock 2.х в исходный тест программы необходимо включить директиву «#include «, а в командной строке линкера указать «ws2_32.lib». В среде разработки Microsoft Visual Studio для этого достаточно нажать , перейти к закладке «Link» и к списку библиотек, перечисленных в строке «Object/Library modules», добавить «ws2_32.lib», отделив ее от остальных символом пробела. Перед началом использования функций библиотеки Winsock ее необходимо подготовить к работе вызовом функции «int WSAStartup (WORD wVersionRequested, LPWSADATA lpWSAData)» передав в старшем байта слова wVersionRequested номер требуемой версии, а в младшем — номер подверсии. Аргумент lpWSAData должен указывать на структуру WSADATA, в которую при успешной инициализации будет занесена информация о производителе библиотеки. Никакого особенного интереса она не представляет и прикладное приложение может ее игнорировать. Если инициализация проваливается, функция возвращает ненулевое значение. Второй шаг – создание объекта «сокет». Это осуществляется функцией «SOCKET socket (int af, int type, int protocol)«. Первый слева аргумент указывает на семейство используемых протоколов. Для Интернет — приложений он должен иметь значение AF_INET. Следующий аргумент задает тип создаваемого сокета — потоковый (SOCK_STREAM) или дейтаграммный (SOCK_DGRAM) (еще существуют и сырые сокеты, но они не поддерживаются Windows — см раздел «Сырые сокеты»). Последний аргумент уточняет какой транспортный протокол следует использовать. Нулевое значение соответствует выбору по умолчанию: TCP — для потоковых сокетов и UDP для дейтаграммных. В большинстве случаев нет никакого смысла задавать протокол вручную и обычно полагаются на автоматический выбор по умолчанию. Если функция завершилась успешно она возвращает дескриптор сокета, в противном случае INVALID_SOCKET. Дальнейшие шаги зависит от того, являет приложение сервером или клиентом. Ниже эти два случая будут описаны раздельно. Клиент: шаг третий — для установки соединения с удаленным узлом потоковый сокет должен вызвать функцию «int connect (SOCKET s, const struct sockaddr FAR* name, int namelen)». Датаграмные сокеты работают без установки соединения, поэтому, обычно не обращаются к функции connect. Примечание: за словом «обычно» кроется один хитрый примем программирования — вызов connect позволяет дейтаграмному сокету обмениваться данными с узлом не только функциями sendto, recvfrom, но и более удобными и компактными send и recv. Эта тонкость описана в Winsocket SDK и широко используется как самой Microsoft, так и сторонними разработчикам. Поэтому, ее использование вполне безопасно. Первый слева аргумент — дескриптор сокета, возращенный функцией socket; второй — указатель на структуру «sockaddr«, содержащую в себе адрес и порт удаленного узла с которым устанавливается соединение. Структура sockaddr используется множеством функций, поэтому ее описание вынесено в отдельный раздел «Адрес раз, адрес два». Последний аргумент сообщает функции размер структуры sockaddr. После вызова connect система предпринимает попытку установить соединение с указанным узлом. Если по каким-то причинам это сделать не удастся (адрес задан неправильно, узел не существует или «висит», компьютер находится не в сети), функция возвратит ненулевое значение. Сервер: шаг третий – прежде, чем сервер сможет использовать сокет, он должен связать его с локальным адресом. Локальный, как, впрочем, и любой другой адрес Интернета, состоит из IP-адреса узла и номера порта. Если сервер имеет несколько IP адресов, то сокет может быть связан как со вмести ними сразу (для этого вместо IP-адреса следует указать константу INADDR_ANY равную нулю), так и с каким-то конкретным одним. Связывание осуществляется вызовом функции «int bind (SOCKET s, const struct sockaddr FAR* name, int namelen)». Первым слева аргументом передается дескриптор сокета, возращенный функций socket, за ним следуют указатель на структуру sockaddr и ее длина (см. раздел «Адрес раз, адрес два«). Строго говоря, клиент также должен связывать сокет с локальным адресом перед его использованием, однако, за него это делает функция connect, ассоциируя сокет с одним из портов, наугад выбранных из диапазона 1024-5000. Сервер же должен «садиться» на заранее определенный порт, например, 21 для FTP, 23 для telnet, 25 для SMTP, 80 для WEB, 110 для POP3 и т.д. Поэтому ему приходится осуществлять связывание «вручную». При успешном выполнении функция возвращает нулевое значение и ненулевое в противном случае. Сервер: шаг четвертый — выполнив связывание, потоковый сервер переходит в режим ожидания подключений, вызывая функцию «int listen (SOCKET s, int backlog )«, где s – дескриптор сокета, а backlog – максимально допустимый размер очереди сообщений. Размер очереди ограничивает количество одновременно обрабатываемых соединений, поэтому, к его выбору следует подходить «с умом». Если очередь полностью заполнена, очередной клиент при попытке установить соединение получит отказ (TCP пакет с установленным флагом RST). В то же время максимально разумное количество подключений определяются производительностью сервера, объемом оперативной памяти и т.д. Датаграммные серверы не вызывают функцию listen, т.к. работают без установки соединения и сразу же после выполнения связывания могут вызывать recvfrom для чтения входящих сообщений, минуя четвертый и пятый шаги. Сервер: шаг пятый – извлечение запросов на соединение из очереди осуществляется функцией «SOCKET accept (SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen)«, которая автоматически создает новый сокет, выполняет связывание и возвращает его дескриптор, а в структуру sockaddr заносит сведения о подключившемся клиенте (IP-адрес и порт). Если в момент вызова accept очередь пуста, функция не возвращает управление до тех пор, пока с сервером не будет установлено хотя бы одно соединение. В случае возникновения ошибки функция возвращает отрицательное значение. Для параллельной работы с несколькими клиентами следует сразу же после извлечения запроса из очереди порождать новый поток (процесс), передавая ему дескриптор созданного функцией accept сокета, затем вновь извлекать из очереди очередной запрос и т.д. В противном случае, пока не завершит работу один клиент, север не сможет обслуживать всех остальных. все вместе – после того как соединение установлено, потоковые сокеты могут обмениваться с удаленным узлом данными, вызывая функции «int send (SOCKET s, const char FAR * buf, int len,int flags)» и «int recv (SOCKET s, char FAR* buf, int len, int flags)» для посылки и приема данных соответственно. Функция send возвращает управление сразу же после ее выполнения независимо от того, получила ли принимающая сторона наши данные или нет. При успешном завершении функция возвращает количество передаваемых (не переданных!) данных — т. е. успешное завершение еще не свидетельствует от успешной доставке! В общем-то, протокол TCP (на который опираются потоковые сокеты) гарантирует успешную доставку данных получателю, но лишь при условии, что соединение не будет преждевременно разорвано. Если связь прервется до окончания пересылки, данные останутся не переданными, но вызывающий код не получит об этом никакого уведомления! А ошибка возвращается лишь в том случае, если соединение разорвано до вызова функции send! Функция же recv возвращает управление только после того, как получит хотя бы один байт. Точнее говоря, она ожидает прихода целой дейтаграммы. Дейтаграмма — это совокупность одного или нескольких IP пакетов, посланных вызовом send. Упрощенно говоря, каждый вызов recv за один раз получает столько байтов, сколько их было послано функцией send. При этом подразумевается, что функции recv предоставлен буфер достаточных размеров, — в противном случае ее придется вызвать несколько раз. Однако, при всех последующих обращениях данные будет браться из локального буфера, а не приниматься из сети, т.к. TCP-провайдер не может получить «кусочек» дейтаграммы, а только ею всю целиком. Работой обоих функций можно управлять с помощью флагов, передаваемых в одной переменной типа int третьим слева аргументом. Эта переменная может принимать одно из двух значений: MSG_PEEK и MSG_OOB. Флаг MSG_PEEK заставляет функцию recv просматривать данные вместо их чтения. Просмотр, в отличие от чтения, не уничтожает просматриваемые данные. Некоторые источники утверждают, что при взведенном флаге MSG_PEEK функция recv не задерживает управления если в локальном буфере нет данных, доступных для немедленного получения. Это неверно! Аналогично, иногда приходится встречать откровенно ложное утверждение, якобы функция send со взведенным флагом MSG_PEEK возвращает количество уже переданных байт (вызов send не блокирует управления). На самом деле функция send игнорирует этот флаг! Флаг MSG_OOB предназначен для передачи и приема срочных (Out Of Band) данных. Срочные данные не имеют преимущества перед другими при пересылке по сети, а всего лишь позволяют оторвать клиента от нормальной обработки потока обычных данных и сообщить ему «срочную» информацию. Если данные передавались функцией send с установленным флагом MSG_OOB, для их чтения флаг MSG_OOB функции recv так же должен быть установлен. Замечание: настоятельно рекомендуется воздержаться от использования срочных данных в своих приложениях. Во-первых, они совершенно необязательны — гораздо проще, надежнее и элегантнее вместо них создать отдельное TCP-соединение. Во-вторых, по поводу их реализации нет единого мнения и интерпретации различных производителей очень сильно отличаются друг от друга. Так, разработчики до сих пор не пришли к окончательному соглашению по поводу того, куда должен указывать указатель срочности: или на последний байт срочных данных, или на байт, следующий за последним байтом срочных данных. В результате, отправитель никогда не имеет уверенности, что получатель сможет правильно интерпретировать его запрос. Еще существует флаг MSG_DONTROUTE, предписывающий передавать данные без маршрутизации, но он не поддерживаться Winsock и, поэтому, здесь не рассматривается. Дейтаграммный сокет так же может пользоваться функциями send и recv, если предварительно вызовет connect (см. «Клиент: шаг третий«), но у него есть и свои, «персональные», функции: «int sendto (SOCKET s, const char FAR * buf, int len,int flags, const struct sockaddr FAR * to, int tolen)» и «int recvfrom (SOCKET s, char FAR* buf, int len, int flags, struct sockaddr FAR* from, int FAR* fromlen )«. Они очень похожи на send и recv, — разница лишь в том, что sendto и recvfrom требуют явного указания адреса узла принимаемого или передаваемого данные. Вызов recvfrom не требует предварительного задания адреса передающего узла — функция принимает все пакеты, приходящие на заданный UDP-порт со всех IP адресов и портов. Напротив, отвечать отправителю следует на тот же самый порт откуда пришло сообщение. Поскольку, функция recvfrom заносит IP-адрес и номер порта клиента после получения от него сообщения, программисту фактически ничего не нужно делать — только передать sendto тот же самый указатель на структуру sockaddr, который был ранее передан функции recvfrem, получившей сообщение от клиента. Еще одна деталь – транспортный протокол UDP, на который опираются дейтаграммные сокеты, не гарантирует успешной доставки сообщений и эта задача ложиться на плечи самого разработчика. Решить ее можно, например, посылкой клиентом подтверждения об успешности получения данных. Правда, клиент тоже не может быть уверен, что подтверждение дойдет до сервера, а не потеряется где-нибудь в дороге. Подтверждать же получение подтверждения — бессмысленно, т. к. это рекурсивно неразрешимо. Лучше вообще не использовать дейтаграммные сокеты на ненадежных каналах. Во всем остальном обе пары функций полностью идентичны и работают с теми самыми флагами — MSG_PEEK и MSG_OOB. Все четыре функции при возникновении ошибки возвращают значение SOCKET_ERROR (== -1). Примечание: в UNIX с сокетами можно обращаться точно так, как с обычными файлами, в частности писать и читать в них функциями write и read. ОС Windows 3.1 не поддерживала такой возможности, поэтому, при переносе приложений их UNIX в Windows все вызовы write и read должны были быть заменены на send и recv соответственно. В Windows 95 с установленным Windows 2.x это упущение исправлено, — теперь дескрипторы сокетов можно передавать функциям ReadFil, WriteFile, DuplicateHandle и др. Шаг шестой, последний – для закрытия соединения и уничтожения сокета предназначена функция «int closesocket (SOCKET s)«, которая в случае удачного завершения операции возвращает нулевое значение. Перед выходом из программы, необходимо вызвать функцию «int WSACleanup (void)» для деинициализации библиотеки WINSOCK и освобождения используемых этим приложением ресурсов. Внимание: завершение процесса функцией ExitProcess автоматически не освобождает ресурсы сокетов! Примечание: более сложные приемы закрытия соединения — протокол TCP позволяет выборочно закрывать соединение любой из сторон, оставляя другую сторону активной. Например, клиент может сообщить серверу, что не будет больше передавать ему никаких данных и закрывает соединение «клиент ( сервер», однако, готов продолжать принимать от него данные, до тех пор, пока сервер будет их посылать, т.е. хочет оставить соединение «клиент ( сервер» открытым. Для этого необходимо вызвать функцию «int shutdown (SOCKET s ,int how )», передав в аргументе how одно из следующих значений: SD_RECEIVE для закрытия канала «сервер ( клиент», SD_SEND для закрытия канала «клиент ( сервер», и, наконец, SD_BOTH для закрытия обоих каналов. Последний вариант выгодно отличается от closesocket «мягким» закрытием соединения — удаленному узлу будет послано уведомление о желании разорвать связь, но это желание не будет воплощено в действительность, пока тот узел не возвратит свое подтверждение. Таким образом, можно не волноваться, что соединение будет закрыто в самый неподходящий момент. Внимание: вызов shutdown не освобождает от необходимости закрытия сокета функцией closesocket! Дерево вызововДля большей наглядности демонстрации взаимосвязи socket-функций друг с другом, ниже приведено дерево вызовов, показывающее в каком порядке должны следовать вызовы функций в зависимости от типа сокетов (потоковый или дейтаграммный) и рода обработки запросв (клиент или сервер). Листинг 29 Последовательность вызова функций сокетов при различных операциях Адрес раз, адрес дваС адресами как раз и наблюдается наибольшая путаница, в которую не помешает внести немного ясности. Прежде всего структура sockaddr определенная так: u_short sa_family; // семейство протоколов // (как правило AF_INET) char sa_data[14]; // IP-адрес узла и порт Листинг 30 Устаревшее определение структуры sockaddr теперь уже считается устаревшей, и в Winsock 2.x на смену ей пришла структура sockaddr_in, определенная следующим образом: Листинг 31 Современное определение структуры sockaddr_in В общем-то ничего не изменилось (и стоило огород городить?), замена безнакового короткого целого на знаковое короткое целое для представления семейства протоколов ничего не дает. Зато теперь адрес узла представлен в виде трех полей — sin_port (номера порта), sin_addr (IP-адреса узла) и «хвоста» из восьми нулевых байт, который остался от четырнадцати символьного массива sa_data. Для чего он нужен? Дело в том, что структура sockaddr не привязана именно к Интернету и может работать и с другими сетями. Адреса же некоторых сетей требуют для своего представления гораздо больше четырех байт, — вот и приходится брать с запасом! Структура in_addr определяется следующим в образом: Листинг 32 Определение структуры in_addr Как видно, она состоит из одного IP-адреса, записанного в трех «ипостасях» — четырехбайтовой последовательности (S_un_b), пары двухбайтовых слов (S_un_W) и одного длинного целого (S_addr) — выбирай на вкус Но не все так просто! Во многих программах, технических руководствах и даже демонстрационных примерах, прилагающихся к Winsock SDK, встречается обращение к «таинственному» члену структуры s_addr, который явно не описан в SDK! Например, вот строка из файла «Simples.h»: «local.sin_addr.s_addr = (!interface)?INADDR_ANY:inet_addr(interface);» Это что такое?! Заглянув в файл «winsock2.h» можно обнаружить следующее: «#define s_addr S_un.S_addr». Ага, да ведь это эквивалент s_addr, т.е. IP-адресу, записанному в виде длинного целого! На практике можно с одинаковым успехом пользоваться как «устаревшей» sockaddr, так и «новомодной» sockaddr_in. Однако, поскольку, прототипы остальных функций не изменились, при использовании sockaddr_in придется постоянно выполнять явные преобразования, например так: «sockaddr_in dest_addr; connect (mysocket, (struct sockaddr*) &dest_addr, sizeof(dest_addr)». Для преобразования IP-адреса, записанного в виде символьной последовательности наподобие «127.0.0.1» в четырехбайтовую числовую последовательность предназначена функция «unsigned long inet_addr (const char FAR * cp )«. Она принимает указатель на символьную строку и в случае успешной операции преобразует ее в четырехбайтовый IP адрес или –1 если это невозможно. Возвращенный функцией результат можно присвоить элементу структуры sockaddr_in следующим образом: «struct sockaddr_in dest_addr; dest_addr.sin_addr.S_addr=inet_addr(«195.161.42.222»);«. При использовании структуры sockaddr это будет выглядеть так: «struc sockaddr dest_addr; ((unsigned int *)(&dest_addr.sa_data[0]+2))[0] = inet_addr(«195.161.42.222»);« Попытка передать inet_addr доменное имя узла приводит к провалу. Узнать IP-адрес такого-то домена можно с помощью функции «struct hostent FAR * gethostbyname (const char FAR * name);«. Функция обращается к DNS и возвращает свой ответ в структуре hostent или нуль если DNS сервер не смог определить IP-адрес данного домена. Структура hostent выглядит следующим образом: Листинг 33 Определение структуры hostent Как и в случае с in_addr, во множестве программ и прилагаемых к Winsock SDK примерах активно используется недокументированное поле структуры h_addr. Например, вот строка из файла «simplec.c» «memcpy(&(server.sin_addr),hp->h_addr,hp->h_length);» Заглянув в «winsock2.h» можно найти, что оно обозначает: «#define h_addr h_addr_list[0]«. А вот это уже интересно! Дело в том, что с некоторыми доменными именами связано сразу несколько IP-адресов. В случае неработоспособности одного узла, клиент может попробовать подключится к другому или просто выбрать узел с наибольшей скоростью обмена. Но в приведенном примере клиент использует только первый IP-адрес в списке и игнорирует все остальные! Конечно, это не смертельно, но все же будет лучше, если в своих программах вы будете учитывать возможность подключения к остальным IP-адресам, при невозможности установить соединение с первым. Функция gethostbyname ожидает на входе только доменные имена, но не цифровые IP-адреса. Между тем, правила «хорошего тона» требуют предоставления клиенту возможности как задания доменных имен, так и цифровых IP-адресов. Решение заключается в следующем — необходимо проанализировать переданную клиентом строку — если это IP адрес, то передать его функции inet_addr в противном случае — gethostbyaddr, полагая, что это доменное имя. Для отличия IP-адресов от доменных имен многие программисты используют нехитрый трюк: если первый символ строки — цифра, это IP-адрес, иначе — имя домена. Однако, такой трюк не совсем честен — доменные имя могут начинаться с цифры, например, «666.ru», могут они и заканчиваться цифрой, например, к узлу «666.ru» члены cубдомена «666» могут так и обращаться — «666». Самое смешное, что (теоретически) могут существовать имена доменов, синтаксически неотличимые от IP-адресов! Поэтому, на взгляд автора данной статьи, лучше всего действовать так: передаем введенную пользователем строку функции inet_addr, если она возвращает ошибку, то вызываем gethostbyaddr. Для решения обратной задачи – определении доменного имени по IP адресу предусмотрена функция «struct HOSTENT FAR * gethostbyaddr (const char FAR * addr, int len, int type)«, которая во всем аналогична gethostbyname, за тем исключением, что ее аргументом является не указатель на строку, содержащую имя, а указатель на четырехбайтовый IP-адрес. Еще два аргумента задают его длину и тип (соответственно, 4 и AF_INET). Определение имени узла по его адресу бывает полезным для серверов, желающих «в лицо» знать своих клиентов. Для преобразования IP-адреса, записанного в сетевом формате в символьную строку, предусмотрена функция «char FAR * inet_ntoa (struct in_addr)«, которая принимает на вход структуру in_addr, а возвращает указатель на строку, если преобразование выполнено успешно и ноль в противном случае. Сетевой порядок байтСреди производителей процессоров нет единого мнения на счет порядка следования младших и старших байт. Так например, у микропроцессоров Intel младшие байты располагаются по меньшим адресам, а у микропроцессоров Motorola 68000 — наоборот. Естественно, это вызывает проблемы при межсетевом взаимодействии, поэтому, был введен специальный сетевой порядок байт, предписывающий старший байт передавать первым (все не так, как у Intel). Для преобразований чисел из сетевого формата в формат локального хоста и наоборот предусмотрено четыре функции — первые две манипулируют короткими целыми (16-битными словами), а две последние — длинными (32-битными двойными словами): u_short ntohs (u_short netshort); u_short htons (u_short hostshort ); u_long ntohl (u_long netlong ); u_long htonl (u_long hostlong); Чтобы в них не запутаться, достаточно запомнить, что за буквой «n» скрывается сокращение «network», за «h» — «host» (подразумевается локальный), «s» и «l» соответственно короткое (short) и длинное (long) беззнаковые целые, а «to» обозначает преобразование. Например, «htons» расшифровывается так: «Host ( Network (short )» т.е. преобразовать короткое целое из формата локального хоста в сетевой формат. Внимание: все значения, возвращенные socket-функциями уже находятся в сетевом формате и «вручную» их преобразовывать нельзя! Т.к. это преобразование исказит результат и приведен к неработоспособности. Чаще всего к вызовам этих функций прибегают для преобразования номера порта согласно сетевому порядку. Например: dest_addr.sin_port = htons(110). Дополнительные возможностиДля «тонкой» настойки сокетов предусмотрена функция «int setsockopt (SOCKET s, int level, int optname, const char FAR * optval, int optlen)«. Первый слева аргумент — дескриптор сокета, который собираются настраивать, level – уровень настойки. С каждым уровнем связан свой набор опций. Всего определено два уровня — SOL_SOCKET и IPPROTO_TCP. В ограниченном объеме журнальной статьи перечислить все опции невозможно, поэтому, ниже будет рассказано только о самых интересных из них, а сведения обо всех остальных можно почерпнуть из Winsock SDK. Третий слева аргумент представляет собой указатель на переменную, содержащую значение опции. Ее размер варьируется в зависимости от рода опции и передается через четвертый слева аргумент. Уровень SOL_SOCKET:
Уровень IPPROTO_TCP
Для получения текущих значений опций сокета предусмотрена функция «int getsockopt (SOCKET s, int level, int optname, char FAR* optval, int FAR* optlen)» которая полностью аналогична предыдущей за исключением того, что не устанавливает опции, а возвращает их значения. Сырые сокетыПомимо потоковых и дейтаграммных сокетов существуют, так называемые, сырые (RAW) сокеты. Они предоставляют возможность «ручного» формирования TCP\IP-пакетов, равно как и полного доступа к содержимому заголовков полученных TCP\IP-пакетов, что необходимо многим сетевым сканерам, FireWall-ам, брандмаузерам и, разумеется, атакующим программам, например, устанавливающим в поле «адрес отправителя» адрес самого получателя. Спецификация Winsock 1.x категорически не поддерживала сырых сокетов. В Winsock 2.x положение как будто было исправлено: по крайней мере формально такая поддержка появилась, и в SDK даже входил пример, демонстрирующий использование сырых сокетов для реализации утилиты ping. Однако, попытки использования сырых сокетов для всех остальных целей с треском проваливались — система упрямо игнорировала сформированный «вручную» IP (или TCP) пакет и создавала его самостоятельно. Документация объясняла, что для самостоятельной обработки заголовков пакетов, опция IP_HDRINCL должна быть установлена. Весь фокус в том, что вызов «setsockopt(my_sock,IPPROTO_IP, IP_HDRINCL, &oki, sizeof(oki))» возвращал ошибку! Таким образом, на прикладном уровне получить непосредственный доступ к заголовкам TCP/IP невозможно. Это препятствует переносу многих приложений из UNIX в Windows, более того, определенным образом ущемляет возможности самой Windows, не позволяя ей решать целый ряд задач, требующих поддержки сырых сокетов. Законченные реализацииНиже приведено четыре подробно комментированных исходных текста, реализующих простых TCP и UDP эхо-сервера и TCP и UDP клиентов. (Эхо сервер просто возвращает клиенту, полученные от него данные). Для их компиляции с помощью Microsoft Visual C++ достаточно отдать команду: «cl.exe имя_файла.cpp ws2_32.lib». Проверка работоспособности TCP-сервера: запустите TCP-сервер и наберите в командной строке Windows «telnet.exe 127.0.0.1 666», где 127.0.0.1 обозначает локальный адрес вашего узла (это специально зарезервированный для этой цели адрес и он выглядит одинаково для всех узлов), а 666 — номер порта на который «сел» сервер. Если все работает успешно, то telnet установит соединение и на экране появится приветствие «Hello, Sailor!». Теперь можно набирать на клавиатуре некоторый текст и получать его назад от сервера. Проверка работоспособности TCP-клиента: запустите TCP-сервер и затем одну или несколько копий клиента. В каждом из них можно набирать некоторый текст на клавиатуре и после нажатия на Enter получать его обратно от сервера. Проверка работоспособности UDP сервера и клиента: запустите UDP-сервер и одну или несколько копий клиента — в каждой из них можно набирать на клавиатуре некоторые данные и получать их обратно от сервера. Внимание: работая с серверными приложениями, вы (если не предпримите дополнительных мер) предоставляете возможность воспользоваться ими каждому абоненту Интернет (если в момент работы сервера вы подключены к Интернет). Проблема в том, что ошибки реализации, в особенности переполняющиеся буфера, могут позволить удаленному злоумышленнику выполнить на вашей машине любой код, со всеми вытекающими от сюда последствиями. Будьте очень внимательны, а еще лучше, не входите в Интернет, пока не будете полностью уверенны, что сервера отлажены и не содержат ошибок! Пример реализации TCP эхо-сервераЛистинг 34 Пример реализации TCP эхо-сервера Пример реализации TCP-клиентаЛистинг 35 Пример реализации TCP-клиента Пример реализации UDP-сервераЛистинг 36 Пример реализации UDP-сервера Веб-сервер на C++ и сокетахСоздадим HTTP-сервер, который обрабатывает запросы браузера и возвращает ответ в виде HTML-страницы. Введение в HTTPДля начала разберемся, что из себя представляет HTTP. Это текстовый протокол для обмена данными между браузером и веб-сервером. Пример HTTP-запроса: Первая строка передает метод запроса, идентификатор ресурса (URI) и версию HTTP-протокола. Затем перечисляются заголовки запроса, в которых браузер передает имя хоста, поддерживаемые кодировки, cookie и другие служебные параметры. После каждого заголовка ставится символ переноса строки \r\n . У некоторых запросов есть тело. Когда отправляется форма методом POST, в теле запроса передаются значения полей этой формы. Тело запроса отделяется от заголовков одной пустой строкой. Заголовок «Content-Type» говорит серверу, в каком формате закодировано тело запроса. По умолчанию, в HTML-форме данные кодируются методом «application/x-www-form-urlencoded». Иногда необходимо передать данные в другом формате. Например, при загрузке файлов на сервер, бинарные данные кодируются методом «multipart/form-data». Сервер обрабатывает запрос клиента и возвращает ответ. Пример ответа сервера: В первой строке ответа передается версия протокола и статус ответа. Для успешных запросов обычно используется статус «200 OK». Если ресурс не найден на сервере, возвращается «404 Not Found». Тело ответа так же, как и у запроса, отделяется от заголовков одной пустой строкой. Полная спецификации протокола HTTP описывается в стандарте rfc-2068. По понятным причинам, мы не будем реализовывать все возможности протокола в рамках этого материала. Достаточно реализовать поддержку работы с заголовками запроса и ответа, получение метода запроса, версии протокола и URL-адреса. Что будет делать сервер?Сервер будет принимать запросы клиентов, парсить заголовки и тело запроса, и возвращать тестовую HTML-страничку, на которой отображены данные запроса клиента (запрошенный URL, метод запроса, cookie и другие заголовки). О сокетахДля работы с сетью на низком уровне традиционно используют сокеты. Сокет — это абстракция, которая позволяет работать с сетевыми ресурсами, как с файлами. Мы можем писать и читать данные из сокета почти так же, как из обычного файла. В этом материале мы будем работать с виндовой реализацией сокетов, которая находится в заголовочном файле . В Unix-подобных ОС принцип работы с сокетами такой же, только отличается API. Вы можете подробнее почитать о сокетах Беркли, которые используются в GNU/Linux. Создание сокетаСоздадим сокет с помощью функции socket , которая находится в заголовочном файле . Для работы с IP-адресами нам понадобится заголовочный файл . Мы подготовили все данные, которые необходимо для создания сокета и создали сам сокет. Функция socket возвращает целочисленное значение файлового дескриптора, который выделен операционной системой под сокет. Привязка сокета к адресу (bind)Следующим шагом, нам необходимо привязать IP-адрес к сокету, чтобы он мог принимать входящие соединения. Для привязки конкретного адреса к сокету используется фукнция bind . Она принимает целочисленный идентификатор файлового дескриптора сокета, адрес (поле ai_addr из структуры addrinfo ) и размер адреса в байтах (используется для поддержки IPv6). Подготовка сокета к принятию входящих соединений (listen)Подготовим сокет к принятию входящих соединений от клиентов. Это делается с помощью функции listen . Она принимает дескриптор слушающего сокета и максимальное количество одновременных соединений. В случае ошибки, функция listen возращает значение константы SOCKET_ERROR . При успешном выполнении она вернет 0. В константе SOMAXCONN хранится максимально возможное число одновременных TCP-соединений. Это ограничение работает на уровне ядра ОС. Ожидание входящего соединения (accept)Функция accept ожидает запрос на установку TCP-соединения от удаленного хоста. В качестве аргумента ей передается дескриптор слушающего сокета. При успешной установке TCP-соединения, для него создается новый сокет. Функция accept возвращает дескриптор этого сокета. Если произошла ошибка соединения, то возвращается значение INVALID_SOCKET . Получение запроса и отправка ответаПосле установки соединение с сервером, браузер отправляет HTTP-запрос. Мы получаем содержимое запроса через функцию recv . Она принимает дескриптор TCP-соединения (в нашем случае это client_socket ), указатель на буфер для сохранения полученных данных, размер буфера в байтах и дополнительные флаги (которые сейчас нас не интересуют). При успешном выполнении функция recv вернет размер полученных данных. В случае ошибки возвращается значение SOCKET_ERROR . Если соединение было закрыто клиентом, то возвращается 0. Мы создадим буфер размером 1024 байта для сохранения HTTP-запроса. После получения запроса мы сразу же отправили ответ клиенту с помощью функции send . Она принимает дескриптор сокета, строку с данными ответа и размер ответа в байтах. В случае ошибки, функция возвращает значение SOCKET_ERROR . В случае успеха — количество переданных байт. Попробуем скомпилировать программу, не забыв предварительно завершить функцию main . Если скомпилировать и запустить программу, то окно консоли «подвиснет» в ожидании запроса на установление TCP-соединения. Откройте в браузере адрес http://127.0.0.1:8000/. Сервер вернет ответ, как на рисунке ниже и завершит работу. Последовательная обработка запросовЧтобы сервер не завершал работу после обработки первого запроса, а продолжал обрабатывать новые соединения, нужно зациклить ту часть кода, которая принимает запрос на установку соединения и возвращает ответ. Когда сервер закончит обработку запроса одного клиента, он закроет соединение с ним и будет ожидать нового запроса. Во второй части этой статьи мы напишем парсер HTTP-заголовков и создадим нормальное API для управления HTTP-запросами и ответами. Примечание: если вы используете MinGW в Windows, то библиотеку Ws2_32.lib нужно вручную прописать в настройках линковщика. socket functionThe socket function creates a socket that is bound to a specific transport service provider. SyntaxParametersThe address family specification. Possible values for the address family are defined in the Winsock2.h header file. On the Windows SDK released for WindowsВ Vista and later, the organization of header files has changed and the possible values for the address family are defined in the Ws2def.h header file. Note that the Ws2def.h header file is automatically included in Winsock2.h, and should never be used directly. The values currently supported are AF_INET or AF_INET6, which are the Internet address family formats for IPv4 and IPv6. Other options for address family (AF_NETBIOS for use with NetBIOS, for example) are supported if a Windows Sockets service provider for the address family is installed. Note that the values for the AF_ address family and PF_ protocol family constants are identical (for example, AF_INET and PF_INET), so either constant can be used. The table below lists common values for address family although many other values are possible.
|