Имя Пароль
Зарегистрироваться


* При перепечатке материалов ссылка на www.SeoLiga.ru обязательна! RSS



Дополнительные функции
19 марта 2009

В этом разделе мы рассмотрим некоторые функции, относящиеся в 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 даёт альтернативный способ проверки того, что дейтаграмма не поместилась в буфер, и в некоторых случаях этот способ может оказаться удобным.
Если при вызове функции WSARecvEx флаг Msg_Partial установлен программой, но дейтаграмма поместилась в буфер целиком, функция сбрасывает этот флаг.
В описании функции WSARecvEx в MSDN'е можно прочитать, что если дейтаграмма прочитана частично, то следующий вызов функции позволит прочитать оставшуюся часть дейтаграммы. Это не относится к протоколу UDP и имеет место быть только при использовании протоколов типа SPX, в которых одна дейтаграмма может разбиваться на несколько сетевых пакетов, и поэтому возможна такая ситуация, когда в буфере сокета окажется только часть дейтаграммы. В UDP, напомню, дейтаграмма всегда посылается одним IP-пакетом и помещается в буфер сразу целиком.
WSARecvEx не позволяет программе определить, с какого адреса прислана дейтаграмма, а аналога функции RecvFrom с такими же возможностями в WinSock нет.
Мы уже упоминали о том, что в WinSock 1 существует перекрытый ввод-вывод, но только для систем линии NT. Также в WinSock 1 определена функция AcceptEx, которая является более мощным эквивалентом функции Accept, и позволяет принимать входящие соединения в режиме перекрытого ввода-вывода. В WinSock 1 эта функция не поддерживается в Windows 95, в WinSock 2 она доступна во всех системах. Её прототип выглядит следующим образом:
function AcceptEx(
sListenSocket,sAcceptSocket:TSocket;
lpOutputBuffer:Pointer;
ReceiveDataLength,LocalAddressLength,RemoteAddressLength:DWORD;
var lpdwBytesReceived:DWORD;
lpOverlapped:POverlapped):BOOL;

Функция AcceptEx позволяет принять новое подключение со стороны клиента и сразу же получить от него первую порцию данных. Функция работает только в режиме перекрытого ввода-вывода.
Параметр sListenSocket определяет сокет, который должен находиться в режиме ожидания подключения. Параметр sAcceptSocket - сокет, через который будет осуществляться связь с подключившимся клиентом. Напомню, что функции Accept и WSAAccept сами создают новый сокет. При использовании же AcceptEx программа должна заранее создать сокет и, не привязывая его к адресу, передать в качестве параметра sAcceptSocket.
Параметр lpOutputBufer задаёт указатель на буфер, в который будут помещены, во-первых, данные, присланные клиентом, а во-вторых, адреса подключившегося клиента и адрес, к которому привязывается сокет sAcceptSocket. Параметр ReceiveDataLength задаёт количество байт в буфере, зарезервированных для данных, присланных клиентом, LocalAddressLength - для адреса привязки сокета sAcceptSocket, RemoteAddressLength - адреса подключившегося клиента. Если параметр ReceiveDataLength равен нулю, функция не ждёт, пока клиент пришлёт данные, и считает операцию завершившейся сразу после подключения клиента, как функция Accept. Для адресов требуется резервировать как минимум на 16 байт больше места, чем реально требуется. Так как размер структуры TSockAddr составляет 16 байт, на каждый из адресов требуется зарезервировать как минимум 32 байта.
Параметр lpdwBytesReceived используется функцией, чтобы вернуть количество байт, присланных клиентом.
Параметр lpOverlapped указывает на структуру TOverlapped, определённую в модуле Windows следующим образом:
type POverlapped=^TOverlapped;
_OVERLAPPED=record
Internal:DWORD;
InternalHigh:DWORD;
Offset:DWORD;
OffsetHigh:DWORD;
hEvent:THandle;
end;
TOverlapped=_OVERLAPPED;

Структура TOverlapped используется, в основном, для перекрытого ввода-вывода в файловых операциях. Видно, что она отличается от уже знакомой нам структуры TWSAOverlapped только типом параметра hEvent - THandle вместо TWSAEvent. Впрочем, выше мы уже обсуждали, что TWSAEvent - это синоним THandle, так что можно сказать, что эти структуры идентичны (но компилятор подходит к этому вопросу формально и считает их разными).
Параметр lpOverlapped функции AcceptEx не может быть равным nil, а его поле hEvent должно указывать на корректное событие. Использование процедур завершения не предусмотрено. Если на момент вызова функции клиент уже подключился и прислал первую порцию данных (или место для данных в буфере не зарезервировано), AcceptEx возвращает True. Если же клиент ещё не подключился, или подключился, но не прислал данные, функция AcceptEx возвращает False, а WSAGetLastError - Error_IO_Pending. Параметр lpdwBytesReceived в этом случае остаётся без изменений.
Для контроля состояния операции можно использовать функцию GetOverlappedResult, которая является аналогом известной нам функции WSAGetOverlappedResult, за исключением того, что использует структуру TOverlapped вместо TWSAOverlapped и не предусматривает передачу флагов. С помощью этой функции можно узнать, завершилась ли операция, а также дождаться её завершения и узнать, сколько байт прислано клиентом (функция AcceptEx не ждёт, пока клиент заполнит весь буфер, предназначенный для него - для завершения операции подключения достаточно первого пакета).
Если к серверу подключаются некорректно работающие клиенты, которые не присылают данные после подключения, операция может не завершаться очень долго, что будет мешать подключению новых клиентов. MSDN рекомендует при ожидании время от времени с помощью функции GetSockOpt для сокета sAcceptSocket узнавать значение целочисленного параметра SO_Connect_Time уровня SOL_Socket. Этот параметр показывает время в секундах, прошедшее с момента подключения клиента к данному сокету (или -1, если подключения не было). Если подключившийся клиент слишком долго не присылает данных, сокет sAcceptSocket следует закрыть, что приведёт к завершению операции, начатой AcceptEx, с ошибкой. После этого можно снова вызывать AcceptEx для приёма новых клиентов.
Функция AcceptEx реализована таким образом, чтобы обеспечивать максимальную скорость подключения. Выше мы говорили, что сокеты, созданные функциями Accept и WSAAccept, наследуют параметры слушающего сокета (например, свойства асинхронного режима). Для повышения производительности сокет sAcceptSocket по умолчанию не получает свойств сокета sListenSocket. Но он может унаследовать их после завершения операции с помощью следующей команды:
SetSockOpt(sAcceptSocket,SOL_Socket,SO_Update_Accept_Context,
PChar(@sListenSocket),SizeOf(sListenSocket));

На сокет sAcceptedSocket после его подключения к клиенту накладываются ограничения: он может использоваться не во всех функциях WinSock, а только в следцющих: Send, WSASend, Recv, WSARecv, ReadFile, WriteFile, TransmitFile, CloseSocket и SetSockOpt, причём в последней - только для установки параметра So_Update_Accept_Context.
В WinSock не документируется, в какую именно часть буфера помещаются адрес клиента и принявшего его сокета. Вместо этого предоставляется функция GetAcceptExSockAddrs. В модуле WinSock она объявлена следующим образом:
procedure GetAcceptExSockaddrs(
lpOutputBuffer:Pointer;
dwReceiveDataLength,dwLocalAddressLength,dwRemoteAddressLength:DWORD;
var LocalSockaddr:TSockAddr;
var LocalSockaddrLength:Integer;
var RemoteSockaddr:TSockAddr;
var RemoteSockaddrLength:Integer);

В объявлении этой функции разработчики модуля WinSock допустили ошибку. Сравним это объявление с прототипом из MSDN'а:
VOID GetAcceptExSockaddrs(
PVOID lpOutputBuffer,
DWORD dwReceiveDataLength,
DWORD dwLocalAddressLength,
DWORD dwRemoteAddressLength,
LPSOCKADDR *LocalSockaddr,
LPINT LocalSockaddrLength,
LPSOCKADDR *RemoteSockaddr,
LPINT RemoteSockaddrLength);

Из этого объявления видно, что параметры LocalSockaddr и RemoteSockaddr являются двойными указателями на структуру SOCKADDR, поэтому, чтобы объявление функции в Delphi соответствовало этому прототипу, параметры-переменные LocalSockaddr и RemoteSockaddr должны иметь тип PSockAddr, а не TSockAddr. Из-за этой ошибки функцию GetAcceptExSockAddrs необходимо самостоятельно импортировать. Следует заметить, что во многих модулях для WinSock 2 от независимых разработчиков объявление этой функции скопировано из стандартного модуля вместе с ошибкой.
Первые четыре параметра функции GetAcceptExSockaddrs определяют буфер, в котором в результате вызова AcceptEx оказались данные от клиента и адреса, и размеры частей буфера, зарезервированных для данных и для адресов. Значения этих параметров должны совпадать со значениями аналогичных параметров в соответствующем вызове AcceptEx. Через параметр LocalSockaddrs возвращается указатель на то место в буфере, в котором хранится адрес привязки сокета sAcceptSocket, а через параметр LocalSockaddrsLength - длина адреса (16 в случае TCP). Адрес клиента и его длина возвращаются через параметры RemoteSockaddrs и RemoteSockaddrsLength. Хочу особенно подчеркнуть, что указатели LocalSockaddrs и RemoteSockaddrs указывают именно на соответствующие части буфера: память для них специально не выделяется и, следовательно, не должна освобождаться, а свою актуальность они теряют при освобождении буфера.
Последняя из дополнительных функций, TransmitFile, служит для передачи файлов по сети. Она имеет следующий прототип:
function TransmitFile(
hSocket:TSocket;
hFile:THandle;
nNumberOfBytesToWrite,nNumberOfBytesPerSend:DWORD;
lpOverlapped:POverlapped;
lpTransmitBuffers:PTransmitFileBuffers;
dwReserved:DWORD):BOOL;

Функция TransmitFile оправляет содержимое указанного файла через указанный сокет. При этом допускается использовать только протоколы, поддерживающие соединение, т.е. использовать данную функцию с UDP-сокетом нельзя. Сокет задаётся параметром hSocket, файл - параметром hFile. Дескриптор файла обычно получается с помощью функции стандартного API CreateFile. Файл рекомендуется открывать с флагом File_Flag_Sequential_Scan, т.к. это повышает производительность.
Параметр nNumberOfBytesToWrite определяет, сколько байт должно быть передано (позволяя, тем самым, передавать не весь файл, а только его часть). Если этот параметр равен нулю, передаётся весь файл.
Функция TransmitFile кладёт данные из файла в буфер сокета по частям. Параметр nNumberOfBytesPerSend определяет размер одной порции данных. Он может быть равен нулю - в этом случае система сама определяет размер порции. Этот параметр критичен только при использовании дейтаграммных протоколов, потому что в этом случае размер порции определяет размер дейтаграммы. В случае использования TCP данные, хранящиеся в буфере, передаются в сеть целиком или по частям в зависимости от загрузки сети, готовности принимающей стороны и т.п., а то, какими порциями они попали в буфер, на размер пакета почти не влияет. Поэтому для TCP-сокета параметр nNumberOfBytesPerSend лучше установить равным нулю.
Параметр lpOverlapped указывает на структуру TOverlapped, использующуюся для перекрытого ввода-вывода. Эту структуру мы обсуждали чуть выше, при описании функции AcceptEx. В отличие от AcceptEx, в TarnsmitFile этот параметр может быть равным nil, и тогда операция передачи файла не будет перекрытой.
Если параметр lpOverlapped равен nil, передача файла начинается с той позиции, на которую указывает файловый указатель (для только что открытого файла этот указатель указывает на его начало, а переместить его можно, например, с помощью функции SetFilePointer; также он перемещается при чтении файла с помощью ReadFile). Если же параметр lpOverlapped задан, то передача файла начинается с того байта, который определяется значениями полей Offset и OffsetHigh, которые должны содержать соответственно младшую и старшую часть 64-битного смещения стартовой позиции от начала файла.
Параметр lpTransmitBuffers является указателем на структуру TTransmitFileBuffers, объявленную следующим образом:
type PTransmitFileBuffers=^TTransmitFileBuffers;
_TRANSMIT_FILE_BUFFERS=record
Head:Pointer;
HeadLength:DWORD;
Tail:Pointer;
TailLength:DWORD;
end;
TTransmitFileBuffers=_TRANSMIT_FILE_BUFFERS;

С её помощью можно указывать буферы, содержащие данные, которые должны быть отправлены перед передачей самого файла и после него. Поле Head содержит указатель на буфер, содержащий данные, предназначенные для отправки перед файлом, HeadLength - размер этих данных. Аналогично Tail и TailLength определяют начало и длину буфера с данными, которые передаются после передачи файла. Если передача дополнительных данных не нужна, параметр lpTransmitBuffer может быть равен nil.
Допускается и обратная ситуация: параметр hFile может быть равен нулю, и тогда передаются только данные, определяемые параметром lpTransmitBuffer.
Последний параметр функции TransmitFile в модуле WinSock имеет имя Reserverd. В WinSock 1 он и в самом деле был зарезервирован и не имел смысла, но в WinSock 2 через него передаются флаги, управляющие операцией передачи файла. Мы не будем приводить здесь полный список возможных флагов (он есть в MSDN'е), а ограничимся лишь самыми важными. Указание флага TF_Use_Default_Worker или TF_Use_System_Thread позволяет повысить производительность при передаче больших файлов, а TF_Use_Kernel_APC - при передаче маленьких файлов. Вообще, при использовании функции TransmitFile чтение файла и передачу данных в сеть осуществляет ядро операционной системы, что приводит к повышению быстродействия по сравнению с использованием ReadFile и Send самой программой.
Функция TransmitFile реализована по-разному в серверных версиях NT/2000 и в остальных системах: в серверных версиях она оптимизирована по быстродействию, а в остальных - по количеству используемых ресурсов.
Данные, переданные функцией TransmitFile, удалённая сторона должна принимать обычным образом, с помощью функций Recv/WSARecv.


Теги: asus socket, IP, borland delphi, User Datagram Protocol, Unix, SQL-сервер, socket, Создание сокета, сокет, sockets Borland Delphi

Статьи по теме:

Exclusive
Важнейшие пространства имен .NET
Чтение сообщений
Свойство Font
Свойство Enabled
Протоколы в ЛВС
Свойство Lines
Подсказки в Delphi
Панель Debug
Географические информационные системы (Geographical Information System)
Создание и отправка сообщения
Сервис сетевой печати
Пример создания задания на печать
Компонент ТМето
Программирование с помощью BASM в Delphi
| Borland Delphi | Alex |
 


Пн Вт Ср Чт Пт Сб Вс
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28


     



Rambler's Top100

Данный сайт или домен продается ICQ: 403-353-727

© 2009 Seoliga.ru | Borland Delphi | Дополнительные функции. Регион сайта: Москва и Санкт-Петербург