Чтение онлайн

ЖАНРЫ

О чём не пишут в книгах по Delphi

Григорьев Антон Борисович

Шрифт:

Параметр

struct
указывает тип сокета и может принимать одно из двух значений:
SOCK_STREAM
(для потоковых протоколов) и
SOCK_DGRAM
(для дейтаграммных протоколов).

Параметр

protocol
позволяет указать, какой именно протокол будет использоваться сокетом. Этот параметр можно оставить равным нулю — тогда будет выбран протокол по умолчанию, отвечающий условиям, заданным первыми двумя параметрами. Для стека TCP/IP потоковый протокол по умолчанию — TCP, дейтаграммный — UDP. В некоторых примерах можно увидеть значение третьего параметра равно
IPPROTO_IP
. Значение этой константы равно 0, и ее использование только повышает читабельность кода, но приводит к тому же результату: будет выбран протокол по умолчанию. Если требуется протокол, отличный от протокола по умолчанию (например, в некоторых реализациях стека TCP/IP существует
протокол RDP — Reliable Datagram Protocol, надежный дейтаграммный протокол), следует указать здесь соответствующую константу (для RDP это будет
IPPROTO_RDP
). Можно также явно задать TCP или UDP с помощью констант
IPPROTO_TCP
и
IPPROTO_UDP
соответственно.

Тип

TSocket
предназначен для хранения дескриптора сокета. Формально он совпадает с 32-битным беззнаковым целым типом, но об этом лучше не вспоминать, т.к. любые операции над значениями типа
TSocket
бессмысленны. Значение, возвращаемое функцией
socket
, следует сохранить в переменной соответствующего типа и затем использовать для идентификации сокета при вызове других функций. Если по каким-то причинам создание сокета невозможно, функция вернет значение
INVALID_SOCKET
. Причину ошибки можно узнать с помощью функции
WSAGetLastError
.

Сокет, созданный с помощью функции

socket
, не привязан ни к какому адресу. Привязка осуществляется с помощью функции
bind
, имеющей следующий прототип:

function bind(s: TSocket; var addr: TSockAddr; namelen: Integer): Integer;

Первый параметр этой функции — дескриптор сокета. который привязывается к адресу. Здесь, как и в остальных подобных случаях, требуется передать значение, которое вернула функция

socket
. Второй параметр содержит адрес, к которому требуется привязать сокет, а третий — длину структуры, содержащей адрес.

Функция

bind
предназначена для сокетов, реализующих разные протоколы из разных стеков, поэтому кодирование адреса в ней сделано достаточно универсальным. Впрочем, следует отметить, что разработчики модуля
WinSock
для Delphi выбрали не лучший способ перевода прототипа этой функции на Паскаль, поэтому универсальность в значительной мере утрачена. В оригинале прототип функции
bind
имеет следующий вид:

int bind(SOCKET s, const struct sockaddr FAR* name, int namelen);
 

Видно, что второй параметр — это указатель на структуру

sockaddr
. Однако C/C++ позволяет при вызове функции в качестве параметра передать указатель на любую другую структуру, если будет выполнено явное приведение типов. Для каждого семейства адресов предусмотрена своя структура, и в качестве фактического параметра передастся указатель на эту структурy. Если бы авторы модуля WinSock описали второй параметр как параметр-значение типа указатель, можно было бы поступать точно так же. Однако они описали этот параметр как параметр-переменную. В результате на двоичном уровне ничего не изменилось: и там, и там в стек помещается указатель. Однако компилятор при вызове функции
bind
не допустит использования никакой другой структуры, кроме
TSockAddr
, а эта структура не универсальна и удобна, по сути дела, только при использовании стека TCP/IP. В других случаях наилучшим решением будет самостоятельно импортировать функцию
bind
из wsock32.dll с нужным прототипом. При этом придется импортировать и некоторые другие функции, работающие с адресами. Впрочем мы здесь ограничиваемся только протоколами TCP и UDP, поэтому больше останавливаться на этом вопросе не будем.

Примечание

На самом деле существует способ передать в функцию

bind
с таким прототипом параметр
addr
любого типа, совместимого с этой функцией. Если
A
— некая переменная типа, отличающегося от
TSockAddr
, то передать в качестве параметра-переменной ее можно так:
PSockAddr(@А)^
. Однако подобные низкоуровневые операции программу не украшают.

В стандартной библиотеке сокетов (т.е. в заголовочных файлах для этой библиотеки) полагается, что адрес кодируется структурой

sockaddr
длиной 16 байтов, причем первые два байта этой структуры кодируют семейство протоколов, а смысл остальных зависит от этого семейства. В частности, для стека TCP/IP семейство протоколов задается константой
PF_INET
. (Ранее мы уже встречались с термином "семейство
адресов" и константой
AF_INET
. В ранних версиях библиотеки сокетов семейства протоколов и семейства адресов были разными понятиями, но затем эти понятия слились в одно, и константы
AF_XXX
и
PF_XXX
стали взаимозаменяемыми). Остальные 14 байтов структуры
sockaddr
занимает массив типа
char
(напомним, что тип
char
в C/C++ соответствует одновременно двум типам Delphi:
Char
и
ShortInt
). В принципе, в стандартной библиотеке сокетов предполагается, что структура, задающая адрес, всегда имеет длину 16 байтов, но на всякий случай предусмотрен третий параметр функции
bind
, который хранит длину структуры. В сокетах Windows длина структуры может быть любой (это зависит от протокола), так что этот параметр, в принципе, может пригодиться.

Ранее уже упоминалось, что неструктурированное представление адреса в виде массива из 14 байтов бывает неудобно, и поэтому для каждого семейства протоколов предусмотрена своя структура, учитывающая особенности адреса. В частности, для протоколов стека TCP/IP используется структура

sockaddr_in
, размер которой также составляет 16 байтов. Из них задействовано только восемь: два для кодирования семейства протоколов, четыре для IP-адреса и два — для порта. Оставшиеся 8 байтов должны содержать нули.

Можно было бы предположить, что типы

TSockAddr
и
TSockAddrIn
, описанные в модуле WinSock, соответствуют структурам
sockaddr
и
sockaddr_in
, однако это не так. На самом деле эти типы описаны следующим образом (листинг 2.1).

Листинг 2.1. Типы
TSockAddr
и
TSockAddrIn

SunB = packed record

 s_b1, s_b2, s_b3, s_b4: u_char;

end;

SunW = packed record

 s_w1, s_w2: u_short;

end;

in_addr = record

 case Integer of

 0: (S_un_b: SunB);

 1: (S_un_w: SunW);

 2: (S_addr: u_long);

end;

TInAddr = in_addr;

sockaddr_in = record

 case Integer of

 0: (

sin_family: u_short;

sin_port: u_short;

sin_addr: TInAddr;

sin_zero: array[0..7] of Char);

 1: (

sa_family: u_short;

sa_data: array[0..13] of Char);

end;

TSockAddrIn = sockaddr_in;

TSockAddr = sockaddr_in;

Таким образом, типы

TSockAddr
и
TSockAddrIn
— это синонимы типа
sockaddr_in
(но не того
sockaddr_in
, который имеется в стандартной библиотеке сокетов, а типа
sockaddr_in
, описанного в модуле
WinSock
). А тип
sockaddr_in
из
WinSock
является вариантной записью, и в случае 0 соответствует типу
sockaddr_in
из стандартной библиотеки сокетов, а в случае 1 —
sockaddr
из этой же библиотеки. Вот такая несколько запутанная ситуация, хотя на практике все выглядит не так страшно.

Примечание

Из названия типов можно сделать вывод, что тип

u_short
— это
Word
, а
u_long
Cardinal
. На самом деле
u_short
— это действительно
Word
, а вот
u_long
— это
LongInt
. Сложно сказать почему выбран знаковый тип там, где предполагается беззнаковый. Видимо, это осталось в наследство от старых версий Delphi, которые не поддерживали тип
Cardinal
в полном объеме. Кстати, тип
u_char
— это
Char
, а не
Byte
.

Поделиться с друзьями: