О чём не пишут в книгах по Delphi
Шрифт:
Событие
FD_CLOSE
возникает только для сокетов, поддерживающих соединение, при разрыве такого соединения нормальным образом или в результате ошибки связи. Если удаленная сторона дня завершения соединения использует функцию shutdown
, то FD_CLOSE
возникает после вызова этой функции с параметром SD_SEND
. При этом соединение закрыто еще не полностью, удаленная сторона еще может получать данные, поэтому при обработке FD_CLOSE
можно попытаться отправить те данные, которые в этом нуждаются. Однако гарантии, что вызов функции отправки не завершится неудачей, нет, т.к. удаленная сторона может закрывать сокет сразу, не прибегая к shutdown
. Рекомендуемая
shutdown
с параметром SD_SEND
. Сервер при этом получает событие FD_CLOSE
. Сервер отсылает данные клиенту (при этом клиент получает одно или несколько событий FD_READ
), а затем также завершает отправку данных с помощью shutdown
с параметром SD_SEND
. Клиент при этом получает событие FD_CLOSE
, в ответ на которое закрывает сокет с помощью closesocket
. Сервер, в свою очередь, сразу после вызова shutdown
также вызывает closesocket
. В листинге 2.49 приведен пример кода сервера, использующего асинхронные сокеты. Сервер работает в режиме запрос-ответ, т.е. посылает какие-то данные клиенту только в ответ на его запросы. Константа WM_SOCKETEVENT
, определенная в коде для сообщений, связанных с сокетом, может, в принципе, иметь и другие значения. Листинг 2.49. Пример простого сервера на асинхронных сокетах
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
. Впрочем, последнее несущественно, т.к. при таком количестве клиентов сервер обычно реализует другие, более производительные способы взаимодействия с клиентами.
Поделиться с друзьями: