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

ЖАНРЫ

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

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

Шрифт:

Таким образом, функция

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-чата

Листинг 2.6. Функция
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.

Листинг 2.7. Код "читающей" нити

unit ReceiveThread;

{

 В этом модуле реализуется дополнительная нить UDP-чата, отвечающая за прием сообщений.

}

interface

uses

 SysUtils, Classes, WinSock;

type

 TReceiveThread = class(TThread)

 private

// Сообщение, которое нужно добавить в лог,

// хранится в отдельном поле, т.к. метод, вызывающийся через

// Synchronize, не может иметь параметров.

FMessage: string;

// Сокет, получающий сообщения

FSocket: TSocket;

// Вспомогательный метод для вызова через Synchronize

procedure DoLogMessage;

 protected

procedure Execute; override;

// Вывод сообщения в лог главной формы

procedure LogMessage(const Msg: string);

 public

constructor Create(ServerSocket: TSocket);

 end;

implementation

uses ChatMainUnit;

{TReceiveThread}

// Сокет, получающий сообщения, создается в главной нити,

// а сюда передаётся через параметр конструктора

constructor TReceiveThread.Create(ServerSocket: TSocket);

begin

 FSocket := ServerSocket;

 inherited Create(False);

end;

procedure TReceiveThread.Execute;

var

 // Буфер для получения сообщения.

 // Размер равен максимальному размеру UDP-дейтаграммы

 Buffer: array[0..65506] of Byte;

 // Адрес, с которого пришло сообщение

 RecvAddr: TSockAddr;

 RecvLen, AddrLen: Integer;

 Msg: string;

begin

 // Начинаем бесконечный цикл, на каждой итерации которого

 // читается одна дейтаграмма

 repeat

AddrLen := SizeOf(RecvAddr);

// Получаем дейтаграмму

RecvLen :=

recvfrom(FSocket, Buffer, SizeOf(Buffer), 0, RecvAddr, AddrLen);

// Так как UDP не поддерживает соединение, ошибку при вызове recvfrom

// мы можем получить, только если случилось что-то совсем

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