Получение данных через "соединенные" сокеты может также осуществляться с помощью функции reсv
, имеющей следующий прототип:
function recv(s: TSocket; var Buf; len, flags: Integer): Integer;
От своего аналога recvfrom
она отличается только отсутствием параметров from
и fromlen
, через которые передается адрес отправителя дейтаграммы.
Рис. 2.1. Последовательность действий программы при обмене данными с помощью UDP
Строго говоря, функцию recv
можно использовать и для несоединенных сокетов, но при этом программе остается неизвестным адрес отправителя. В случае же "соединенных" сокетов адрес отправителя заранее известен — это адрес, заданный в функции connect
, а дейтаграммы всех других отправителей будут отбрасываться. Функция recvfrom
также пригодна для "соединенных" сокетов, но адрес отправителя, который она возвращает, в данном случае может быть только тот, который определен в функции connect
.
Таким образом, функция connect
в случае протокола UDP позволяет, во-первых, выполнить фильтрацию входящих дейтаграмм по адресу средствами самой библиотеки сокетов, а во-вторых, использовать более лаконичные альтернативы recvfrom
и sendto
— recv
и send
.
Возможные последовательности действий программы для протокола UDP показаны на рис. 2.1.
2.1.10. Пример программы: простейший чат на UDP
Попробуем применить свои знания на практике и напишем простейший чат на основе протокола UDP. Пример этой программы находится на прилагаемом к книге компакт-диске и называется UDPChat, окно приложения показано на рис. 2.2.
Прежде чем писать программу, необходимо определиться с форматом передаваемых данных (т.е. договориться о протоколе уровня представлений). Так как мы пишем простейший пример, то и протокол у нас будет простейшим: дейтаграмма содержит текстовое сообщение, введенное пользователем, без завершающего нуля (он не нужен, т.к. размер строки определяется размером дейтаграммы) и без дополнительной служебной информации.
Для начала нам потребуется научиться сообщать пользователю об ошибках. Номер ошибки мало что дает даже опытному пользователю, поэтому сообщения должны быть дружественными, с внятным объяснением того, какая именно ошибка произошла. К счастью, мы избавлены от необходимости вручную писать текстовое сообщение для каждой из возможных ошибок, т.к. в системе уже есть функция FormatMessage
, которая возвращает текстовое сообщение по коду ошибки (эта функция работает со всеми ошибками, а не только с ошибками сокетов). На основе FormatMessage
мы создадим функцию GetErrorString
(листинг 2.6), которая возвращает сообщение, соответствующее коду ошибки, возвращаемому функцией WSAGetLastError
. Эта функция будет встречаться во всех наших примерах.
Рис. 2.2. Главное окно UDP-чата
GetErrorString
, возвращающая описание ошибки// функция GetErrorString возвращает сообщение об ошибке,
// сформированное системой из основе значения, которое
// вернула функция WSAGetLastError. Для получения сообщения
// используется системная функция FormatMessage.
function GetErrorString: string;
var
Buffer: array [0..2047] of Char;
begin
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, nil, WSAGetLastError, $400,
@Buffer, SizeOf(Buffer), nil);
Result := Buffer;
end;
Нам понадобится и принимать, и передавать данные. Как мы помним, функция recvfrom
не возвращает управление вызвавшей ее нити до тех пор, пока не будут получены данные. Таким образом, если мы будем вызывать recvfrom
в главной нити, то при отсутствии входящих дейтаграмм программа просто повиснет, т.к. не сможет обрабатывать оконные сообщения. Поэтому все действия по приему сообщений мы должны вынести в отдельную нить. Задача этой нити очень проста: она в бесконечном цикле вызывает recvfrom и все полученные дейтаграммы передает в главное окно для отображения на экране.
Нить, читающая данные, создается обычным образом — порождением наследника от класса TThread
. Мы не будем возлагать на эту нить задачу создания сокета, — пусть он создается в главной нити, а затем его дескриптор передаётся в дополнительную, которая сохраняет его в своем внутреннем поле FSocket
. Код нити, читающей сообщения, показан в листинге 2.7.
unit ReceiveThread;
{