В предыдущей статье мы видели, что передача данных через сокет осуществляется одними и теми же функциями независимо от протокола. Но при этом программа должна учитывать, является ли протокол потоковым, дейтаграммным или иным. Кроме того, информация о протоколе требуется для создания сокета и для распределения ролей между клиентом и сервером при установлении соединения. Чтобы работать с любым протоколом, программа должна иметь возможность получить всю эту информацию и выполнить на основе её те или иные действия. Могут также понадобиться такие сведения, как максимальное число сокетов, поддерживаемых провайдером протокола, допустимый диапазон адресов, максимальный размер сообщений для дейтаграммных протоколов и т.д. Для хранения полного описания протокола и его провайдера в WinSock 2 предусмотрена структура WSAPROTOCOL_INFO. Она не описана в модуле WinSock, т.к. в WinSock 1 её нет. Тем, кто захочет использовать эту структуру, придётся самостоятельно добавлять её описание в программу. Как и все структуры Windows API, она реализуется без выравнивания, т.е. для её описания в Delphi требуется конструкция packed record.
В этом разделе мы рассмотрим те устаревшие функции, которые не стоит использовать в 32-разрядных программах. Рассмотрим мы их, разумеется, очень обзорно, только для того, чтобы после прочтения статьи вас не смущали упоминания этих функций и связанных с ними ошибок, которые иногда встречаются в MSDN'е. В 16-разрядных версиях Windows используется так называемая корпоративная многозадачность: каждая программа время от времени должна добровольно возвращать управление операционной системе, чтобы та могла передать управление другой программе. При этом если какая-то программа поведёт себя некорректно и не вернёт управление системе, то все остальные программы не смогут продолжать работу. Другим недостатком такой модели является то, что в ней невозможно распараллеливание работы в рамках одного процесса, т.е. создание нитей.
Каждый сокет обладает рядом параметров (опций), которые влияют на его работу. Существуют параметры уровня сокета, которые относятся к сокету как к объекту безотносительно используемого протокола, и уровня протокола. Впрочем, некоторые параметры уровня сокета применимы не ко всем протоколам. Здесь мы не будем рассматривать все параметры сокета, а ограничимся лишь изложением методов доступа к ним и познакомимся с самыми, на мой субъективный взгляд, интересными параметрами. Для получения текущего значения параметров сокета используется функция GetSockOpt, для изменения - SetSockOpt. Прототипы этих функций выглядят следующим образом: function GetSockOpt(S:TSocket;Level,OptName:Integer; OptVal:PChar; var OptLen:Integer):Integer;
function SetSockOpt(S:TSocket;Level,OptName:Integer; OptVal:PChar;OptLen:Integer):Integer; Параметры у функций почти одинаковы. Первый параметр задаёт сокет, параметры которого следует узнать или изменить. Второй параметр указывает, параметр какого уровня следует узнать или изменить. Третий параметр задаёт сам параметр сокета. Параметр OptVal содержит указатель на буфер, в котором хранится значение параметра, а OptLen - размер этого буфера (разные параметры имеют разные типы и поэтому размер буфера может быть разным). Функция GetSockOpt сохраняет значение параметра в буфере, заданном указателем OptVal. Длина буфера передаётся через параметр OptLen, и через него же возвращается размер, реально понадобившийся для хранения параметра. У функции SetSockOpt параметр OptVal содержит указатель на буфер, хранящий новое значение параметра сокета, а параметр OptLen - размер этого буфера. Чаще всего параметры сокета имеют целый или логический тип. В обоих случаях параметр OptVal должен содержать указатель на значение типа Integer. В случае логического типа любое ненулевое значение интерпретируется как True, нулевое - как False.
В предыдущей статье при рассмотрении функции WSAStartup уже упоминалось, что существуют разные версии библиотеки сокетов, которые заметно различаются по функциональности. К сожалению, полного перечня существующих на сегодняшний день версий Windows Sockets и их особенностей мне найти не удалось. Всё, что мне удалось узнать о разных версиях, показано в таблице. Версия Комментарий 1.0 Упоминается только вскользь. Видимо, настолько старая версия, что её поддержка в чистом виде в современных системах отсутствует. 1.1 Основная подверсия первой версии библиотеки. По умолчанию входила во все версии Windows до Windows 95 включительно. Ориентирована на 16-разрядные системы с корпоративной многозадачностью. 2.0 В чистом виде никуда не ставилась. Ориентирована на 32-разрадные системы с вытесняющей многозадачностью. Исключены некоторые устаревшие функции. 2.2 Основная подверсия второй версии библиотеки. Ставится по умолчанию в Windows 98/NT 4/2000, а также, видимо, в более поздних версиях. Для Windows 95 существует возможность обновления Windows Sockets до этой версии
В дальнейшем, если не оговорено иное, под WinSock 1 мы будем подразумевать версию 1.1, под WinSock 2 - версию 2.2.
Выше мы столкнулись с функциями, которые могут надолго приостановить работу вызвавшей их нити, если действие не может быть выполнено немедленно. Это функции Accept, Recv, RecvFrom, Send, SendTo и Connect (в дальнейшем в этом разделе мы не будем упоминать функции RecvFrom и SendTo, потому что они в смысле блокирования эквивалентны функциям Recv и Send соответственно, и всё, что будет здесь сказано о Recv и Send, применимо к RecvFrom и SendTo). Такое поведение не всегда удобно вызывающей программе, поэтому в библиотеке сокетов предусмотрен особый режим работы сокетов - неблокирующий. Этот режим может быть установлен или отменён для каждого сокета индивидуально с помощью функции IOCtlSocket, имеющей следующий прототип: function IOCtlSocket(S:TSocket;Cmd:DWORD;var Arg:u_long):Integer; Эта функция предназначена для выполнения нескольких логически мало связанных между собой действий. Возможно, у разработчиков первых версий библиотеки сокетов были причины экономить на количестве функций, потому что мы и дальше увидим, что иногда непохожие операции выполняются одной функцией. Но вернёмся к IOCtlSocket. Её параметр Cmd определяет действие, которое выполняет функция, а также смысл параметра Arg. Допустимы три значения параметра Cmd: SIOCatMark, FIONRead и FIONBIO. В случае задания SIOCatMark параметр Arg рассматривается как выходной: в нём возвращается ноль, если во входном буфере сокета имеются высокоприоритетные данные, и ненулевое значение, если таких данных нет (как уже было оговорено, мы в данной статье не будем касаться передачи высокоприоритетных данных). При Cmd равном FIONRead в параметре Arg возвращается размер данных, находящихся во входном буфере сокета, в байтах. При использовании TCP это количество равно максимальному количеству информации, которое можно получить на данный момент за один вызов Recv. Для UDP это значение равно суммарному размеру всех находящихся в буфере дейтаграмм (напомню, что прочитать несколько дейтаграмм за один вызов Recv нельзя). Функция IOCtlSocket с параметром FIONRead может использоваться для проверки наличия данных с целью избежать вызова Recv тогда, когда это может привести к блокированию, или для организации вызова Recv в цикле до тех пор, пока из буфера не будет извлечена вся информация.
Так как многие функции библиотеки сокетов блокируют вызвавшую их нить, если соответствующая операция не может быть выполнена немедленно, часто бывает полезно заранее знать, готов ли сокет к немедленному (без блокирования) выполнению той или иной операции. Основным средством определения этого в библиотеке сокетов служит функция Select : function Select(NFds:Integer;ReadFds,WriteFds,ExceptFds:PFDSet; Timeout:PTimeVal):LongInt; Первый параметр этой функции оставлен только для совместимости со старыми версиями библиотеки сокетов; в существующих версиях он игнорируется. Три следующих параметра содержат указатели на множества сокетов, состояние которых должно проверяться. В данном случае понятие множества не имеет ничего общего с типом множество в Delphi. В оригинальной версии библиотеки сокетов, написанной на С, определены макросы, позволяющие очищать такие множества, добавлять и удалять сокеты и определять, входит ли тот или иной сокет в множество. В модуле WinSock эти макросы заменены одноимёнными процедурами и функциями: //Удаляет сокет Socket из множества FDSet.
При программировании TCP используются те же функции, что и при программировании UDP, но их поведение при этом иное. Для передачи данных с помощью TCP необходимо сначала установить соединение, и после этого возможен обмен данными только с тем адресом, с которым это соединение установлено. Функция SendTo может использоваться для TCP-сокетов, но её параметры, задающие адрес получателя, игнорируются, а данные отправляются на тот адрес, с которым соединён сокет. Поэтому при отправке данных через TCP обычно используют функцию Send, которая даёт тот же результат. По тем же причинам обычно используется Recv, а не RecvFrom. В TCP существует разделение ролей взаимодействующих сторон на клиент и сервер. Мы начнём изучение передачи данных в TCP с изучения действий клиента. Для начала взаимодействия клиент должен соединится с сервером с помощью функции Connect. Мы уже знакомы с этой функцией, но в случае TCP она выполняет несколько иные действия. В данном случае она устанавливает реальное соединение, поэтому её действия начинаются с проверки того, существует ли по указанному адресу серверный сокет, находящийся в режиме ожидания подключения. Функция Connect завершается успешно только в том случае, если соединение установлено, и серверная сторона выполнила все необходимые для этого действия. При использовании Connect в TCP предварительный явный вызов функции Bind также не обязателен. В отличие от UDP, сокет в TCP нельзя отсоединить или соединить с другим адресом, если он уже соединён. Для нового соединения необходимо использовать новый сокет. Выше мы говорили, что TCP является надёжным протоколом, т.е. в том случае, если пакет не доставлен, отправляющая сторона уведомляется об этом. Тем не менее, успешное завершение Send, как и в случае UDP, не является гарантией того, что пакет был отослан и дошёл до получателя, а говорит только о том, что данные скопированы в выходной буфер сокета, и на момент копирования сокет был соединён. Если в дальнейшем библиотека сокетов не сможет отправить эти данные или не получит подтверждения об их доставке, соединение будет закрыто, и следующая операция с этим сокетом завершится с ошибкой.
В ближайшем будущем ни одно изделие или услуга не смогут быть проданы на мировом рынке без электронного паспорта. Необходимость организовать непрерывное сопровождение всего жизненного цикла продукта или услуги приводит к фактической замене понятия физического предприятия на так называемое виртуальное. Термин виртуальное предприятие означает группу производств, объединенных единым информационным пространством, организующим весь жизненный цикл продукта или услуги. Появление термина виртуальное предприятие произошло из-за неизбежной в современном производстве системной специализации и огромного числа партнеров. Основная проблема создания виртуального предприятия — интеграция документационных систем и информационных технологий. Тесное сотрудничество между организациями в рамках виртуального предприятия требует непрерывного сохранения интегрированности информации и единства документационной системы в процессе управления общим проектом, минуя границы и территориальное разделение организаций. Такая интеграция дает полную картину деятельности участников корпоративной системы, и они сами и их структурные подразделения становятся прозрачными как по всем видам потребляемых ресурсов в рамках виртуального предприятия, так и по результатам деятельности, в том числе промежуточным.
В этом разделе мы рассмотрим некоторые функции, относящиеся в WinSock к дополнительным. В WinSock 1 эти функции вместе со всеми остальными экспортируются библиотекой WSock32.dll, а в WinSock 2 они вынесены в отдельную библиотеку MSWSock.dll (в эту же библиотеку вынесены некоторые устаревшие функции типа EnumProtocols). Начнём мы знакомство с этими функциями с функции WSARecvEx (которая, кстати, является расширенной версией функции Recv, а отнюдь не WSARecv, как это можно заключить из её названия), имеющей следующий прототип function WSARecvEx(S:TSocket;var Buf;Len:Integer;var Flags:Integer):Integer;
Видно, что эта функция отличается от обычной функции Recv только тем, что флаги передаются через параметр-переменную вместо значения. В функции WSARecvEx этот параметр является не только входным, но и выходным; функция может модифицировать его. Ранее мы познакомились с функцией WSARecv, которая также может модифицировать переданные ей флаги, но условия, при которых эти две функции модифицируют флаги, различаются. При использовании TCP (а также любого другого потокового протокола) флаги не изменяются функцией, и результат работы WSARecvEx эквивалентен результату работы Recv. В предыдущей статье мы обсуждали, что дейтаграмма UDP должна быть прочитана из буфера сокета целиком. Если в буфере, переданном функции Recv или RecvFrom недостаточно места для получения дейтаграммы, эти функции завершаются с ошибкой. При этом в буфер помещается та часть дейтаграммы, которая может в нём поместиться, а оставшаяся часть дейтаграммы теряется. Функция WSARecvEx отличается от Recv только тем, что в случае, когда размер буфера меньше размера дейтаграммы, она завершается без ошибки (возвращая при этом размер прочитанной части дейтаграммы, т.е. размер буфера) и добавляет флаг Msg_Partial к параметру Flags. Остаток дейтаграммы при этом также теряется. Таким образом, WSARecvEx даёт альтернативный способ проверки того, что дейтаграмма не поместилась в буфер, и в некоторых случаях этот способ может оказаться удобным.
Прежде чем переходить к рассмотрению перекрытого ввода-вывода, вспомним, какие модели ввода-вывода нам уже известны. Появление разных моделей связано с тем, что операции ввода-вывода не всегда могут быть выполнены немедленно. Самая простая модель ввода-вывода - блокирующая. В блокирующем режиме, если операция не может быть выполнена немедленно, работа нити приостанавливается до тех пор, пока не возникнут условия для выполнения операции. В неблокирующей модели ввода-вывода операция, которая не может быть выполнена немедленно, завершается с ошибкой. И, наконец, в асинхронной модели ввода-вывода предусмотрена система уведомлений о том, что операция может быть выполнена немедленно. При использовании перекрытого ввода-вывода операция, которая не может быть выполнена немедленно, формально завершается ошибкой - в этом заключается сходство перекрытого ввода-вывода и неблокирующего режима. Однако, в отличие от неблокирующего режима, при перекрытом вводе-выводе WinSock начинает выполнять операцию в фоновом режиме, и после её завершения начавшая операцию программа получает уведомление об успешно выполненной операции или о возникшей при её выполнении фатальной ошибке. Несколько операций ввода-вывода могут одновременно выполняться в фоновом режиме, как бы перекрывая работу инициировавшей их нити и друг друга. Именно поэтому данная модель получила название модели перекрытого ввода-вывода.