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



Передача данных при использовании UDP
23 февраля 2009

Мы наконец-то добрались до изучения того, ради чего сокеты и создавались: как передавать и получать с их помощью данные. По традиции начнём рассмотрение с более простого протокола UDP. Функции, которые рассматриваются в этом разделе, могут быть использованы и с другими протоколами, и от этого их поведение может меняться. Мы здесь описываем только их поведение при использовании UDP.
Для передачи данных удалённому сокету используется функция SendTo, описанная следующим образом:
function SendTo(S:TSocket;var Buf;Len,Flags:Integer;
var AddrTo:TSockAddr;ToLen:Integer):Integer;
Первый параметр данной функции задаёт сокет, который используется для передачи данных. Здесь нужно указать значение, полученное ранее от функции Socket . Параметр Buf задаёт буфер, в котором хранятся данные для отправки, а параметр len - размер этих данных в байтах. Параметр Flags позволяет указать некоторые дополнительные опции, которых мы здесь касаться не будем, т.к. в большинстве случаев они не нужны. Пока следует запомнить, что параметр Flags в функции SendTo , а также в других функциях, где он встречается, должен быть равен нулю. Параметр AddrTo задаёт адрес (состоящий из IP-адреса и порта) удалённого сокета, который должен получить эти данные. Значение параметра AddrTo должно формироваться по тем же правилам, что и значение аналогичного параметра функции Bind, за исключением того, что IP-адрес и порт должны быть заданы явно (т.е. не допускается использование значения InAddr_Any и нулевого номера порта). Параметр ToLen задаёт длину буфера, отведённого для адреса, и должен быть равен SizeOf(TSockAddr).
Один вызов функции SendTo приводит к отправке одной дейтаграммы. Данные, переданные в SendTo , никогда не разбиваются на несколько дейтаграмм, и данные, переданные последовательными вызовами SendTo , никогда не объединяются в одну дейтаграмму.
Функцию SendTo можно использовать с сокетами, не привязанными к адресу. В этом случае внутри библиотеки сокетов будет неявно вызвана функция Bind для привязки сокета к адресу InAddr_Any и нулевому порту (т.е. адрес и порт будут выбраны системой).
Если выходной буфер сокета имеет ненулевой размер, SendTo кладёт данные в этот буфер и сразу возвращает управление программе, а собственно отправка данных осуществляется библиотекой сокетов в фоновом режиме. Поэтому успешное завершение SendTo гарантирует только то, что данные скопированы в буфер и что на момент их копирования не обнаружено никаких проблем, которые делали бы невозможной их отправку. Но такие проблемы могут возникнуть позже, поэтому даже в случае успешного завершения SendTo отправитель не получает гарантии, что данные посланы.
Если в выходном буфере сокета не хватает места для новой порции данных, SendTo не возвращает управление программе до тех пор, пока в буфере за счёт фоновой отправки не появится достаточно места или не будет обнаружена ошибка.
Если размер выходного буфера сокета равен нулю, функция SendTo копирует данные сразу в сеть, без промежуточной буферизации. Когда функция вернёт управление программе, программа может быть уверена, что информация уже успешно передана в сеть. Однако даже в этом случае успешное завершение SendTo не гарантирует того, что информация доставлена: дейтаграмма может потеряться по дороге.
В случае успешного завершения функция SendTo возвращает количество байт, скопированных в буфер (или переданных напрямую в сеть, если буфера нет). Для протокола UDP это значение может быть равно только значению параметра Len , хотя для некоторых других протоколов (например, TCP), возможны ситуации, когда в буфер сокета копируется только часть данных, переданных программой, и тогда SendTo возвращает значение в диапазоне от 1 до Len. Если при выполнении SendTo возникает ошибка, она возвращает значение Socket_Error (эта константа имеет отрицательное значение).
Для получения данных, присланных сокету, используется функция RecvFrom , имеющая следующий прототип:
function RecvFrom(S:TSocket;var Buf;Len,Flags:Integer;
var From:TSockAddr;var FromLen:Integer):Integer;
Параметр S задаёт сокет, из входного буфера которого будут извлекаться данные, параметр Buf - буфер, в который эти данные будут копироваться, а параметр Len - размер этого буфера. Параметр Flags задаёт дополнительные опции и в большинстве случаев должен быть равен нулю. Параметр From является выходным параметром: в него помещается адрес, с которого была послана дейтаграмма. Параметр FromLen задаёт размер в байтах буфера для адреса отправителя. При вызове функции значение переменной, подставляемой в качестве фактического параметра, должно быть равно SizeOf(TSockAddr). Функция меняет это значение на ту длину, которая реально потребовалось для хранения адреса отправителя (в случае UDP это значение также будет равно SizeOf(TSockAddr)).
В оригинале параметры From и FromLen передаются как указатели, и программа может использовать вместо них нулевые указатели, если её не интересует адрес отправителя. Разработчики модуля WinSock заменили указатели параметрами-переменными, что в большинстве случаев удобнее. Однако возможность передавать нулевые указатели при этом оказалась потерянной.
Функция RecvFrom всегда читает только одну дейтаграмму, даже если размер переданного ей буфера достаточен для чтения нескольких дейтаграмм. Если на момент вызова RecvFrom дейтаграммы во входном буфере сокета отсутствуют, функция будет ждать, пока они там появятся, и до этого момента не вернёт управление вызвавшей её программе. Если в буфере находится несколько дейтаграмм, то они читаются в порядке очерёдности поступления в буфер. Напомним, что дейтаграммы могут поступать в буфер не в том порядке, в котором они были отправлены. Кроме того, буфер может содержать
Значение, возвращаемое функцией RecvFrom , равно длине прочитанной дейтаграммы. Это значение может быть равно нулю, т.к. UDP позволяет отправлять дейтаграммы нулевой длины (для этого при вызове SendTo надо задать параметр Len равным нулю). Если обнаружена какая-то ошибка, возвращается значение Socket_Error .
Если размер буфера, определяемого параметром Buf , меньше, чем первая лежащая во входном буфере сокета дейтаграмма, то копируется только часть дейтаграммы, помещающаяся в буфере, а RecvFrom завершается с ошибкой ( WSAGetLastError при этом вернёт ошибку WSAEMsgSize ). Оставшаяся часть дейтаграммы при этом безвозвратно теряется, при следующем вызове RecvFrom будет прочитана следующая дейтаграмма. Этой проблемы легко избежать, т.к. длина дейтаграммы в UDP не может превышать 65507 байт. Достаточно подготовить буфер соответствующей длины, и в него гарантированно поместится любая дейтаграмма.
Другой способ избежать подобной проблемы - использовать флаг Msg_Peek . В этом случае дейтаграмма не удаляется из входного буфера сокета, а значение, возвращаемое функцией RecvFrom , равно длине дейтаграммы. При этом в буфер, заданный параметром Buf, копируется та часть дейтаграммы, которая в нём помещается. Программа может действовать следующим образом: вызвать RecvFrom с флагом Msg_Peek , выделить память, требуемую для хранения дейтаграммы, вызвать RecvFrom без флага Msg_Peek , чтобы удалить прочитать дейтаграмму целиком и удалить её из входного буфера сокета. Этот метод сложнее, а 65507 байт - не очень большая по нынешним меркам память, поэтому легче всё-таки использовать буфер фиксированной длины.
Функцию RecvFrom нельзя использовать с теми сокетами, которые ещё не привязаны к адресу, поэтому перед вызовом этой функции должна быть вызвана либо функция Bind, либо функция, которая осуществляет неявную привязку сокета к адресу (например, SendTo).
Протокол UDP не поддерживает соединения в том смысле, в котором их поддерживает TCP, но библиотека сокетов позволяет частично имитировать такое соединения. Для этого служит функция Connect , имеющая следующий прототип:
function Сonnect(S:TSocket;var Name:TSockAddr;NameLen:Integer):Integer;
Параметр S задаёт сокет, который должен быть "соединён" с удалённым адресом. Адрес задаётся параметром Name аналогично тому, как он задаётся в параметре Addr функции SendTo . Параметр NameLen содержит длину структуры, описывающей адрес, и должен быть равен SizeOf(NameLen). Функция возвращает ноль в случае успешного завершения и Socket_Error в случае ошибки.
Вызов функции Connect в случае использования UDP устанавливает фильтр для входящих дейтаграмм. Дейтаграммы, адрес отправителя которых не совпадает с адресом, заданным в функции Connect, игнорируются: новые дейтаграммы не помещаются во входной буфер сокета, а те, которые лежали там на момент вызова Connect, удаляются из него. Connect не проверяет, существует ли адрес, с которым сокет "соединяется", и может успешно завершиться, даже если узла с таким IP-адресом не существует.
Программа может вызывать Connect неограниченное число раз с разными адресами. Если параметр Name задаёт IP-адрес InAddr_Any и нулевой порт, то сокет "отсоединяется", т.е. все фильтры для него снимаются, и он ведёт себя так же, как сокет, для которого не была вызвана функция Connect . Для сокетов, не привязанных к адресу, Connect неявно вызывает Bind.
После вызова Connect для отправки данных можно использовать функцию Send со следующим прототипом:
function Send(S:TSocket;var Buf;Len,Flags:Integer):Integer;
От функции SendTo она отличается отсутствием параметров AddrTo и ToLen . При использовании Send дейтаграмма отправляется по адресу, заданному при вызове Connect . В остальном эти функции ведут себя одинаково. Функция SendTo при использовании с "соединённым" сокетом ведёт себя так же, как с несоединённым, т.е. отправляет дейтаграмму по адресу, определяемому параметром AddrLen , а не по адресу, заданному при вызове Connect .
Для получения данных через "соединённые" сокеты можно использовать функцию Recv , имеющую следующий прототип:
function Recv(S:TSocket;var Buf;Len,Flags:Integer):Integer;
От своего аналога RecvFrom она отличается только отсутствием параметров From и FromLen , через которые передаётся адрес отправителя дейтаграммы. Строго говоря, функцию Recv можно использовать и для несоединённых сокетов, но при этом программе остаётся неизвестным адрес отправителя. В случае же "соединённых" сокетов адрес отправителя заранее известен - это адрес, заданный в функции Connect , а дейтаграммы всех других отправителей будут отбрасываться. Функцию RecvFrom также можно использовать для "соединённых" сокетов, но адрес отравителя, который она возвращает, в данном случае может быть только тот, который определён в функции Connect .
Таким образом, функция Connect при использовании протокола UDP позволяет, во-первых, выполнить фильтрацию входящих дейтаграмм по адресу средствами самой библиотеки сокетов, а во-вторых, использовать более лаконичные альтернативы RecvFrom и SendTo - Recv и Send .


Теги: UDP, TSocket, SendTo, WSAGetLastError Borland Delphi

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

Свойство PaperWidth
Событие OnPreview
Сабклассинг
Перекрытый ввод-вывод
Интеграция OLAP и ИАД
Свойство BandType
Поиск пространства имен
Упрощение интерфейса
Компьютерные видеоконференции
Свойство ShowProgress
EmptyTable
Панель Spacing
Сетевые технологии Fast Ethernet и Gigabit Ethernet
DisableResyncOnPost
Дополнительные функции
| 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 29 30 31


     



Rambler's Top100

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

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