Крисс Касперски - Самоучитель игры на winsock. ВведениеСокеты
Скачать 188.88 Kb.
|
ñ ñà àì ìî îó ó÷ ÷è èò òå åë ëü ü è èããð ðû û í íà à w wiin ns so oc ck k ñ ñà àì ìî îó ó÷ ÷è èò òå åë ëü ü è èããð ðû û í íà à w wiin ns so oc ck k К Крриисс К Ка ассп пееррссккии К Крриисс К Ка ассп пееррссккии kk@sendmail.ru Крис Касперски kk@sendmail.ru С Са ам мо оууч чи итте ел ль ь и иггр ры ы н на а W WIIN NS SO OC CK K Введение С океты (sockets) представляют собой высокоуровневый унифицированный интерфейс взаимодействия с телекоммуникационными протоколами. В технической литературе встречаются различные переводы этого слова – их назы вают и гнездами, и соединителями, и патронами, и патрубками, и т. д. По причине отсутствия устоявшегося русскоязычного термина, в настоящей статье сокеты будет именоваться сокетами и никак иначе. Программирование сокетов не сложно само по себе, но довольно поверхност но описано в доступной литературе, а Windows Sockets SDK содержит многожен ство ошибок как в технической документации, так и в прилагаемых к ней демон страционных примерах. К тому же имеются значительные отличия реализаций сокетов в UNIX и в Windows, что создает очевидные проблемы. Автор постарался дать максимально целостное и связанное описание, затра гивающее не только основные моменты, но и некоторые тонкости не известные рядовым программистам. Ограниченный объем журнальной статьи не позволяет рассказать обо всем, поэтому, пришлось сосредоточиться только на одной реализа ции сокетов – библиотеке Winsock 2, одном языке программирования – Си\Си++ (хотя сказанное большей частью приемлемо к Delphi, Perl и т. д.) и одном виде соке тов – блокируемых синхронных сокетах. ALMA MATER О сновное подспорье в изучении сокетов Windows Sockets 2 SDK. SDK – это документация, набор заголовочных файлов и инструментарий разработ чика. Документация не то, чтобы очень хороша – но все же написана достаточна грамотно и позволяет, пускай, не без труда, освоить сокеты даже без помощи какой либо другой литературы. Причем, большинство книг, имеющиеся на рынке, явно уступают Microsoft в полноте и продуманности описания. Единственный недоста ток SDK – он полностью на английском (для некоторых это очень существенно). Из инструментария, входящего в SDK, в первую очередь хотелось бы выделить утилиту sockeye.exe – это настоящий "тестовый стенд" разработчика. Она позволя ет в интерактивном режиме вызывать различные сокет функции и манипулировать ими по своему усмотрению. Крис Касперски 2 2 Демонстрационные программы, к сожалению, не лишены ошибок, причем порой довольно грубых и наводящих на мысли – а тестировались ли эти примеры вообще? (Например, в исходном тексте программы 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. Крис Касперски 3 3 С Са ам мо оууч чи итте ел ль ь и иггр ры ы н на а W WIIN NS SO OC CK K Первый шаг, второй, третий… Д ля работы с библиотекой Winsock 2.х в исходный тест программы необхо димо включить директиву " #include Перед началом использования функций библиотеки 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, так и сторонними разработчикам. Поэтому, ее использова ние вполне безопасно. Крис Касперски 4 4 С Са ам мо оууч чи итте ел ль ь и иггр ры ы н на а W WIIN NS SO OC CK K Первый слева аргумент – дескриптор сокета, возращенный функцией 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 bac klog)", где s – дескриптор сокета, а backlog – максимально допустимый размер очереди сообщений. Размер очереди ограничивает количество одновременно обрабатываемых соединений, поэтому, к его выбору следует подходить "с умом". Если очередь пол ностью заполнена, очередной клиент при попытке установить соединение полу чит отказ (TCP пакет с установленным флагом RST). В то же время максимально разумное количество подключений определяются производительностью сервера, объемом оперативной памяти и т. д. Датаграммные серверы не вызывают функцию listen, т. к. работают без уста новки соединения и сразу же после выполнения связывания могут вызывать recvfrom для чтения входящих сообщений, минуя четвертый и пятый шаги. Сервер: шаг пятый – извлечение запросов на соединение из очереди осущест вляется функцией " SOCKET accept (SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen)", которая автоматически создает новый сокет, выполняет связывание и воз вращает его дескриптор, а в структуру sockaddr заносит сведения о подключившемся Крис Касперски 5 5 С Са ам мо оууч чи итте ел ль ь и иггр ры ы н на а W WIIN NS SO OC CK K клиенте (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 игнорирует этот флаг! Крис Касперски 6 6 С Са ам мо оууч чи итте ел ль ь и иггр ры ы н на а W WIIN NS SO OC CK K Флаг 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, получившей сообщение от клиента. |