
Программирование_распределенных_систем / Использование сокетов в Delphi_2
.pdfPChar(@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.
[ К содержанию ]
Заключение
На этом мы заканчиваем рассмотрение WinSock. Многие возможности этого стандарта остались нерассмотренными и даже неупомянутыми. Но если описывать их все, получится

не статья, а большая книга. При выборе разделов, которые должны войти в данную статью,
я руководствовался следующими соображениями. Во-первых, должны быть описаны все те средства, которые используются стандартными классами VCL Delphi, реализующими сокеты. Мы будем рассматривать их в следующих статьях, и нужно будет знать те возможности системы, обёрткой для которых они являются. Во-вторых, я описал здесь также те темы, которые лично мне показались наиболее интересными с точки зрения практического применения. Второй критерий, конечно же, весьма субъективен, но я надеюсь, что всё-таки многим из читателей статья окажется полезной.
Ещё раз хочу напомнить, что желательно не ограничиваться тем описанием функций, которые приведены в этой статье, но и самостоятельно читать MSDN. Также ещё раз рекомендую прочитать книгу Э. Джонса и Д. Оланда "Программирование в сетях Microsoft Widows" (или хотя бы посмотреть её содержание в Интернете, чтобы представлять, сколько аспектов использования WinSock остались вам неизвестными после прочтения данной статьи).
Сама идея этого цикла статей возникла у меня под влиянием впечатлений, полученных при самостоятельном освоении сокетов по MSDN'у и по литературе, относящейся к сокетам Беркли (книга Джонса и Оланда тогда ещё не вышла). Самой большой проблемой для меня оказалось собрать разрозненные сведения в единую картину, а также понять, что в статьях MSDN'а имеет отношение к протоколам TCP и UDP, а что - только к протоколам из других семейств. В своих статьях я старался изложить материал последовательно, чтобы неподготовленный человек мог постигать его постепенно, а не получать мозаику, из которой надо мучительно складывать картинку. Надеюсь, что у меня это получилось и что тем, кто начнёт изучение сокетов с моих статей, будет немного легче, чем мне в своё время.
Напоследок хочу заметить, что компьютер - это строго детерминированная система, в поведении которой нет ничего непредсказуемого, если знать его внутренние механизмы. Именно поэтому я по возможности рассказывал не только о том, что надо сделать, чтобы получить результат, но и о том, как и почему такой результат получается. Чтобы человек, столкнувшийся с нестандартной ситуацией, мог разобраться, что и как он должен делать. Вполне допускаю, что большинство моих читателей будет использовать сокеты только стандартными способами, и информация о внутренней работе WinSock не будет иметь для них практического смысла. Но я глубоко убеждён, что программист, в отличие от ламера, должен понимать, что стоит за тем кодом, который он написал. И если избыточная информация отпугнёт ламеров от моих статей, я буду только рад этому.
В поиске информации для этой статьи мне помогали участники форумов сайта "Мастера
Delphi":
Rouse_ , Digitman, Verg, wisekaa и Bless.
Огромное всем спасибо!
[ К содержанию ]
Антон Григорьев,
Специально для Королевства Delphi
Смотрите также материалы по темам:
[UDP] [WinSocket]
Обсуждение материала [ 09-12-2005 10:14 ] 8 сообщений
Время на сайте: GMT минус 5 часов
Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter. Функция может не работать в некоторых версиях броузеров.
Хостинг предоставлен компанией DOTNETPARK
© При использовании любых материалов «Королевства Delphi» необходимо указывать источник информации. Все используемые на сайте торговые марки являются собственностью их производителей.