А. Григорьев - О чём не пишут в книгах по Delphi
- Название:О чём не пишут в книгах по Delphi
- Автор:
- Жанр:
- Издательство:БХВ-Петербург
- Год:2008
- Город:СПб
- ISBN:978-5-9775-019003
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
А. Григорьев - О чём не пишут в книгах по Delphi краткое содержание
Рассмотрены малоосвещённые вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные механизмы их работы, особенности для протоколов TCP и UDP и др. Большое внимание уделено разбору ситуаций возникновения ошибок и получения неверных результатов в "простом и правильном" коде. Отдельно рассмотрены особенности работы с целыми, вещественными и строковыми типами данных, а также приведены примеры неверных результатов, связанных с ошибками компилятора, VCL и др. Для каждой из таких ситуаций предложены методы решения проблемы. Подробно рассмотрен синтаксический анализ в Delphi на примере арифметических выражений. Многочисленные примеры составлены с учётом различных версий: от Delphi 3 до Delphi 2007. Прилагаемый компакт-диск содержит примеры из книги.
Для программистов
О чём не пишут в книгах по Delphi - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, WinSock;
const
WM_SOCKETEVENT = WM_USER + 1;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObjеct);
private
ServSock: TSocket;
procedure WMSocketEvent(var Msg: TMessage); message WM_SOCKETEVENT;
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
var
Data: TWSAData;
Addr: TSockAddr;
begin
WSAStartup($101, Data);
// Обычная последовательность действий по созданию сокета,
// привязке его к адресу и установлению на прослушивание
ServSock := socket(AF_INET, SOCK_STREAM, 0);
Addr.sin_family := AF_INET;
Addr.sin_addr.S_addr := INADDR_ANY;
Addr.sin_port := htons(3320);
FillChar(Addr.sin_zero, SizeOf(Addr.sin_zero), 0);
bind(ServSock, Addr, SizeOf(Addr));
listen(ServSock, SOMAXCONN);
// Перевод сокета в асинхронный режим. Кроме события FD_ACCEPT
// указаны также события FD_READ и FD_CLOSE, которые никогда не
// возникают на сокете, установленном в режим прослушивания.
// Это сделано потому, что сокеты, созданные с помощью функции
// accept, наследуют асинхронный режим, установленный для
// слушающего сокета. Таким образом, не придется вызывать
// функцию WSAAsyncSelect для этих сокетов - для них сразу
// будет назначен обработчик событий FD_READ и FD_CLOSE.
WSAAsyncSelect(ServSock, Handle, WM_SOCKETEVENT, FD_READ or FD_ACCEPT or FD_CLOSE);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
closesocket(ServSock);
WSACleanup;
end;
procedure TForm1.WMSocketEvent(var Msg: TMessage);
var
Sock: TSocket;
SockError: Integer;
begin
Sock := TSocket(Msg.WParam);
SockError := WSAGetSelectError(Msg.lParam);
if SockError <> 0 then
begin
// Здесь должен быть анализ ошибки
closesocket(Sock);
Exit;
end;
case WSAGetSelectEvent(Msg.lParam) of
FD_READ: begin
// Пришел запрос от клиента. Необходимо прочитать данные,
// сформировать ответ и отправить его.
end;
FD_АССЕРТ: begin
// Просто вызываем функция accept. Ее результат нигде не
// сохраняется, потому что вновь созданный сокет автоматически
// начинает работать в асинхронном режиме, и его дескриптор
// при необходимости будет передан через Msg.wParam при
// возникновение события
accept(Sock, nil, nil);
end;
FD_CLOSE:
begin
// Получив от клиента сигнал завершения, сервер, в принципе,
// может попытаться отправить ему данные. После этого сервер
// также должен закрыть соединение со своей стороны
shutdown(Sock, SD_SEND);
closesocket(Sock);
end;
end;
end;
end.
Преимущество такого сервера по сравнению с сервером, основанным на функции select
, заключается в том, что он не должен постоянно проверять наличие полученных данных — когда данные поступят, он без дополнительных усилий получит уведомление об этом. Кроме того, этот сервер не имеет проблем, связанных с количеством сокетов в множестве типа TFDSet
. Впрочем, последнее несущественно, т.к. при таком количестве клиентов сервер обычно реализует другие, более производительные способы взаимодействия с клиентами.
2.2.6. Пример сервера, основанного на сообщениях
В этом разделе мы напишем сервер, использующий асинхронные сокеты и их сообщения (пример AsyncSelectServer на компакт-диске). Этот сервер будет во многом похож на сервер на основе неблокирующих сокетов (см. разд. 2.1.16) , только он не станет проверять по таймеру наличие данных в буфере и возможность отправки данных, а будет выполнять это тогда, когда поступят соответствующие сообщения.
Такая схема работы требует более осторожного подхода. По сигналу от таймера мы сами проверяем, на каком этапе в данный момент находится обмен данными с клиентом. Если, например, идет этап отправки данных, то проверять входной буфер сокета не нужно, можно оставить это до тех пор, пока не наступит этап чтения данных. При использовании сообщений приходится учитывать, что сообщение о поступлении данных в буфер сокета может прийти в любой момент, в том числе и тогда, когда обмен с клиентом находится на этапе отправки строки. По протоколу сервер не должен читать сообщение в этот момент, необходимо сначала закончить отправку, поэтому приходится данное уведомление игнорировать. Но второго уведомления система не пришлет, соответственно, после окончания отправки данных сервер должен сам вспомнить, что было уведомление, и перейти к операции чтения.
Вообще говоря, ситуация, когда сервер не отправит данные за один раз, и их отправка растянется на несколько итераций петли сообщений, настолько редка, что при разработке сервера, к которому не предъявляются повышенные требования по надежности, ее можно было бы вообще не учитывать. Соответственно, возможность получения сервером нового уведомления о поступлении данных до того, как на старое сообщение будет дан ответ, возможна только тогда, когда клиент не соблюдает принятый протокол и посылает несколько сообщений подряд, не дожидаясь ответа. Наш пример призван продемонстрировать наиболее надежный к подобным действиям клиента сервер, поэтому мы его напишем "по всем правилам".
Как обычно, работа сервера начинается с инициализации слушающего сокета, выполняющейся при нажатии кнопки Запустить(листинг 2.50).
procedure TServerForm.BtnStartServerClick(Sender: TObject);
var
// Адрес, к которому привязывается слушающий сокет
ServerAddr: TSockAddr;
begin
// Формируем адрес для привязки.
FillChar(ServerAddr.sin_zero, SizeOf(ServerAddr.sin_zero), 0);
ServerAddr.sin_family := AF_INET;
ServerAddr.sin_addr.S_addr := INADDR_ANY;
try
ServerAddr.sin_port := htons(StrToInt(EditPortNumber.Text));
if ServerAddr.sin_port = 0 then
begin
MessageDlg('Номер порта должен находиться в диапазоне 1-65535',
mtError, [mbOK], 0);
Exit;
end;
// Создание сокета
FServerSocket := socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if FServerSocket = INVALID_SOCKET then
begin
MessageDlg('Ошибка при создании сокета:'#13#10 +
GetErrorString, mtError, [mbOK], 0);
Exit;
end;
// Привязка сокета к адресу
if bind(FServerSocket, ServerAddr, SizeOf(ServerAddr)) = SOCKET_ERROR then
begin
MessageDlg('Ошибка при привязке сокета к адресу:'#13#10 +
GetErrorString, mtError, [mbOK], 0);
closesocket(FServerSocket);
Exit;
end;
// Перевод сокета в режим прослушивания
Интервал:
Закладка: