Глава 5. Компонент Winsock в DelphiОбъекты хороши… но компоненты лучше. Чтобы наши программы могли мгновенно обращаться к Internet, мы упакуем весь багаж Winsock в один VCL-компонент.Internet (и распределенные среды вообще) с каждым днем становится все популярнее, поэтому сетевая поддержка в приложениях выглядит вполне естественно. Lingua francaдля работы с Internet в Microsoft Windows является Winsock API. Описанный в этой главе компонент Winsock1 станет отправной точкой, позволяющей вам самостоятельно написать многие знакомые программы на базе TCP/IP — такие, как FINGER, FTP, SMTP, POP3 и ECHO. Что такое Winsock?Winsock — сокращение от «Windows Sockets», это интерфейсная прослойка между Windows-приложением и базовой сетью TCP/IP. Интерфейс сокетов впервые появился в Berkeley Unix как API для работы с сетями TCP/IP. Winsock базируется на Berkeley Sockets API и включает большую часть стандартных функций BSD API, а также некоторые расширения, специфические для Windows. Поддержка сетевого взаимодействия через TCP/IP в Windows-программе сводится к вызову функций Winsock API и использованию библиоте ки WINSOCK.DLL, реализующей интерфейс Winsock.Программисту на Delphi проще всего работать
с Winsock API с помощью компонентов. В этой главе мы создадим компонент
CsSocket, инкапсулирую щий Winsock API. Он обладает несколькими несомненными
достоинствами:
Несомненно, компонент CsSocket удобен для программирования на Delphi, но он не претендует на полноту. На фундаменте CsSocket вы сможете построить дочерние компоненты, предназначенные для работы с любым специали зированным Internet-протоколом. Компонент Winsock, поддерживающий все известные Internet-протоколы, получился бы слишком сложным и громоздким. Вместо этого мы воспользуемся CsSocket как основой для создания новых компонентов, работающих с конкретными протоколами. Например, компонент для работы с гипертекстовым протоколом (HTTP) создается так:
Изучаем CsSocketКомпонент CsSocket построен на основе невизуального класса TCsSocket, который в свою очередь является потомком TComponent. Невизуальный класс TCsSocket похож на фундамент дома — обычно его никто не видит. Класс TComponent предоставляет самые необходимые методы и свойства, необходимые для работы CsSocket — но не более того. Если бы мы выбрали в качестве предка TGraphicControl, то класс TCsSocket обладал бы большими возможностями, но за счет соответствующего возрастания сложности и накладных расходов. CsSocket создает основу для настройки и поддержания TCP/IP-соединения, а также поддерживает сокеты как потоковые (TCP), так и датаграммные (UDP).Чтобы упростить задачу построения сетевых
компонентов TCP/IP для Internet-приложений, наш идеальный компонент Winsock
должен выполнять четыре основные функции. К ним относятся:
Наш компонент Winsock, как и все сетевые формы жизни, должен выполнять инициализацию, корректно завершать работу и сообщать о возникающих ошибках. В листинге 5.1 приведен исходный код для класса TCsSocket, выполняющего эти и многие другие функции. Большинство методов находит ся в protected-секции TCsSocket, чтобы они были доступны компонентам -потомкам. Эти методы остаются невидимыми для клиентских приложений. Листинг 5.1. Определение TCsSocket (* CsSocket Unit
Простейший интерфейсный модуль Winsock
Написан для книги
High Performance Delphi Programming
Джон К.Пенман 1997
*)
{$H+}
unit CsSocket;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics,
Controls, Forms, Dialogs;
{$INCLUDE CsSOCKINT.PAS}
const
winsocket = 'wsock32.dll';
WSockVersionNo : String = '2.0';
WSockBuildDate : String = '7 May 97';
SOCK_EVENT = WM_USER + 1;
ASYNC_EVENT = SOCK_EVENT + 1;
type
TConditions = (Success, Failure, None);
THostAddr = (HostAddr, IPAddr);
TOperations = (SendOp, RecvOp, NoOp);
TAccess = (Blocking, NonBlocking);
TSockTypes = (SockStrm, SockDgram, SockRaw);
TServices = (NoService, Echo, Discard,
Systat, Daytime, Netstat,
Qotd, Chargen, ftp, telnet,
smtp, time, rlp, nameserver,
whois, domain, mtp, tftp, rje,
finger, http, link, supdup,
hostnames, ns, pop2,pop3,
sunrpc, auth, sftp,
uucp_path, nntp);
TProtoTypes = (IP, ICMP, GGP, TCP, PUP, UDP);
TAsyncTypes = (AsyncName, AsyncAddr, AsyncServ,
AsyncPort, AsyncProtoName, AsyncProtoNumber);
const
NULL : Char = #0;
CRLF : array[0..2] of char = #13#10#0;
MaxBufferSize = MAXGETHOSTSTRUCT;
{ Строки для расшифровки значения
свойства Service }
ServiceStrings : array[TServices] of String[10]
= ('No Service ',
'echo ',
'discard ',
'systat ',
'daytime ',
'netstat ',
'qotd ',
'chargen ',
'ftp ',
'telnet ',
'smtp ',
'time ',
'rlp ',
'nameserver ',
'whois ',
'domain ',
'mtp ',
'tftp ',
'rje ',
'finger ',
'http ',
'link ',
'supdup ',
'hostnames ',
'ns ',
'pop2 ',
'pop3 ',
'sunrpc ',
'auth ',
'sftp ',
'uucp-path ',
'nntp ');
{ Строки для расшифровки значения
свойства Protocol }
ProtoStrings : array[TProtoTypes] of String[4] =
('ip ',
'icmp ',
'gcmp ',
'tcp ',
'pup ',
'udp ');
type
CharArray = array[0..MaxBufferSize] of char;
TAddrTypes = (AFUnspec, { не указан }
AFUnix, { локальный для хоста
(конвейеры, порталы) }
AFInet, { межсетевой: UDP,
TCP и т. д. }
AFImpLink,
{ адреса arpanet imp}
AFPup,
{ протоколы pup: например, BSP }
AFChaos,
{ протокол mit CHAOS }
AFNs,
{ протоколы XEROX NS }
AFIso, { протоколы ISO }
AFOsi, { OSI - ISO }
AFEcma,
{ European computer manufacturers }
AFDatakit,
{ протоколы data kit }
AFCcitt,
{ протоколы CCITT,
X.25 и т. д.}
AFSna, { IBM SNA }
AFDecNet, { DECnet }
AFDli,
{ интерфейс непосредственной
передачи данных (data link) }
AFLat, { LAT }
AFHyLink, { гиперканал NSC }
AFAppleTalk,{ AppleTalk }
AFNetBios, { адреса NetBios }
AFMax);
const
ServDefault = NoService;
ProtoDefault = TCP;
SockDefault = SockStrm;
AddrDefault = AFINET;
PortNoDefault = 0;
type
{$LONGSTRINGS ON}
ECsSocketError = class(Exception);
TLookUpOp = (resHostName, resIpAddress,
resService, resPort,
resProto, resProtoNo);
TAsyncOpEvent = procedure(Sender : TObject;
sSocket : TSocket)
of object;
TCleanUpEvent = procedure(Sender : TObject;
CleanUp : Boolean)
of object;
TConnEvent = procedure(Sender : TObject;
sSocket : TSocket) of object;
TDisConnEvent = procedure(Sender : TObject;
sSocket : TSocket) of object;
TInfoEvent = procedure(Sender : TObject;
Msg : String) of object;
TErrorEvent = procedure(Sender : TObject;
Status : TConditions; Msg
: String) of object;
TAbortEvent = procedure(Sender : TObject)
of object;
TBusyEvent = procedure(Sender : TObject;
BusyFlag : Boolean) of object;
TStatusEvent = procedure(Sender : TObject;
Mode, Status : String)
of object;
TLookUpEvent = procedure(Sender : TObject;
LookUpOp : TLookUpOp; Value
: String; Result : Boolean) of object;
TSendDataEvent = procedure(Sender : TObject;
sSocket : TSocket) of object;
TRecvDataEvent = procedure(Sender : TObject;
sSocket : TSocket) of object;
TTimeOutEvent = procedure(Sender : TObject;
sSocket : TSocket; TimeOut
: LongInt) of object;
TCsSocket = class(TComponent)
private
{ Private declarations }
FOnCleanUpEvent : TCleanUpEvent;
FOnConnEvent : TConnEvent;
FOnDisConnEvent : TDisConnEvent;
FOnInfoEvent : TInfoEvent;
FOnErrorEvent : TErrorEvent;
FOnAbortEvent : TAbortEvent;
FOnBusyEvent : TBusyEvent;
FOnStatusEvent : TStatusEvent;
FOnLookUpEvent : TLookUpEvent;
FOnSendDataEvent : TSendDataEvent;
FOnRecvDataEvent : TRecvDataEvent;
FOnTimeOutEvent : TTimeOutEvent;
FOnAsyncOpEvent : TAsyncOpEvent;
FValidSocket : u_int;
FParent : TComponent;
FSockType : TSockTypes;
FService : TServices;
FProtocol : TProtoTypes;
FAddrType : TAddrTypes;
FAsyncType : TAsyncTypes;
FLookUpOp : TLookUpOp;
FCleanUp : Boolean;
FData,
FRemoteName,
FAsyncRemoteName,
FAsyncService,
FAsyncPort,
FAsyncProtocol,
FAsyncProtoNo,
FLocalName,
FInfo : String;
FBusy,
FCancelAsyncOp,
FOKToDisplayErrors : Boolean;
FStatus : TConditions;
FConnected : Boolean;
FTaskHandle : THandle;
FHomeHostName : String;
FWSALastError,
FTimeOut : Integer;
FRC : Integer;
FVendor,
FWSVersion,
FMaxNoSockets,
FMaxUDPPSize,
FWSStatus,
FServiceName,
FPortName,
FProtocolName,
FProtocolNo : String;
FAsyncBuff
: array[0..MAXGETHOSTSTRUCT-1] of char;
FNoOfBlockingTasks : Integer;
protected
{ Protected declarations }
FAccess : TAccess;
FPortNo : Integer;
FHost : pHostent;
FServ : pServent;
FProto : pProtoEnt;
FHostEntryBuff,
FProtoName,
FServName : CharArray;
Fh_addr : pChar;
FpHostBuffer,
FpHostName
: array[0..MAXGETHOSTSTRUCT-1] of char;
FAddress : THostAddr;
FMsgBuff : CharArray;
FSocket : TSocket;
FSockAddress : TSockAddrIn;
FHandle : THandle;
FStarted : Boolean;
FHwnd,
FAsyncHWND : HWND;
// Методы
procedure ConnEvent;
procedure CleanUpEvent; dynamic;
procedure DisConnEvent; dynamic;
procedure InfoEvent(Msg : String); dynamic;
procedure ErrorEvent(Status : TConditions;
Msg : String); dynamic;
procedure StatusEvent; dynamic;
procedure BusyEvent; dynamic;
procedure LookUpEvent(Value : TLookUpOp;
Msg : String; Result :
Boolean); dynamic;
procedure SendDataEvent; dynamic;
procedure RecvDataEvent; dynamic;
procedure TimeOutEvent; dynamic;
procedure AbortEvent; dynamic;
procedure AsyncOpEvent; dynamic;
function GetLocalName : String;
procedure SetRemoteHostName(NameReqd
: String);
function GetDataBuff : String;
procedure SetDataBuff(DataReqd : String);
function GetDatagram : String;
procedure SetDatagram(DataReqd : String);
procedure SetUpPort;
procedure SetPortName(ReqdPortName : String);
procedure SetServiceName(ReqdServiceName
: String);
{ Вызовы Winsock }
procedure GetProt(Protocol : PChar);
procedure ConnectToHost;
function GetOOBData : String;
procedure SetOOBData(ReqdOOBData : String);
function StartUp : Boolean;
procedure CleanUp;
procedure SetUpAddr; virtual;
procedure SetUpAddress; virtual;
procedure GetHost; virtual;
procedure GetServ;
function CreateSocket : TSocket;
function WSAErrorMsg : String;
function GetInfo : String; virtual;
procedure SetInfo(InfoReqd : String);
virtual;
procedure SetProtocolName(ReqdProtoName
: String);
procedure SetProtoNo(ReqdProtoNo : String);
procedure WMTimer(var Message : TMessage);
message wm_Timer;
procedure StartAsyncSelect; virtual;
procedure AsyncOperation(var Mess
: TMessage);
function GetAsyncHostName : String;
procedure SetAsyncHostName(ReqdHostName
: String);
function GetAsyncService : String;
procedure SetAsyncService(ReqdService
: String);
function GetAsyncPort : String;
procedure SetAsyncPort(ReqdPort : String);
function GetAsyncProtoName : String;
procedure SetAsyncProtoName(ReqdProtoName
: String);
function GetAsyncProtoNo : String;
procedure SetAsyncProtoNo(ReqdProtoNo
: String);
procedure CancelAsyncOperation(CancelOp
: Boolean);
function CheckConnection : Boolean;
public
{ Public declarations }
procedure GetServer;
procedure QuitSession;
procedure Cancel;
constructor Create(AOwner : TComponent);
override;
destructor Destroy; override;
{ Public properties }
property WSVendor : String
read FVendor;
property WSVersion : String
read FWSVersion;
property WSMaxNoSockets: String
read FMaxNoSockets;
property WSMaxUDPPSize : String
read FMaxUDPPSize;
property WSStatus : String
read FWSStatus;
property Info : String
read FInfo
write FInfo;
property WSErrNo : Integer
read FWSALastError
default 0;
property Connected : Boolean
read FConnected
write FConnected default FALSE;
property LocalName : String
read GetLocalName
write FLocalName;
property Status : TConditions
read FStatus
write FStatus default None;
property HostName : String
read FRemoteName
write SetRemoteHostName;
property WSService : String
read FServiceName
write SetServiceName;
property WSPort : String
read FPortName
write SetPortName;
property WSProtoName : String
read FProtocolName
write SetProtocolName;
property WSProtoNo : String
read FProtocolNo
write SetProtoNo;
property Data : String
read GetDataBuff
write SetDataBuff;
property Datagram : String
read GetDatagram
write SetDatagram;
property OOBData : String
read GetOOBData
write SetOOBData;
property CancelAsyncOP : Boolean
read FCancelAsyncOp
write CancelAsyncOperation;
published
{ Published declarations }
property OkToDisplayErrors : Boolean
read FOKToDisplayErrors
write FOKToDisplayErrors
default TRUE;
property HomeServer : String
read FHomeHostName
write FHomeHostName;
property SockType : TSockTypes
read FSockType
write FSockType
default SOCKSTRM;
property Service : TServices
read FService
write FService
default NoService;
property Protocol : TProtoTypes
read FProtocol
write FProtocol
default TCP;
property AddrType : TAddrTypes
read FAddrType
write FAddrType
default AFInet;
property Access : TAccess
read FAccess
write FAccess
default blocking;
property OnConnect : TConnEvent
read FOnConnEvent
write FOnConnEvent;
property OnClose : TDisConnEvent
read FOnDisConnEvent
write FOnDisConnEvent;
property OnCleanUp : TCleanUpEvent
read FOnCleanUpEvent
write FOnCleanUpEvent;
property OnInfo : TInfoEvent
read FOnInfoEvent
write FOnInfoEvent;
property OnError : TErrorEvent
read FOnErrorEvent
write FOnErrorEvent;
property OnLookup : TLookUpEvent
read FOnLookUpEvent
write FOnLookUpEvent;
property OnStatus : TStatusEvent
read FOnStatusEvent
write FOnStatusEvent;
property OnSendData : TSendDataEvent
read FOnSendDataEvent
write FOnSendDataEvent;
property OnRecvData : TRecvDataEvent
read FOnRecvDataEvent
write FOnRecvDataEvent;
property OnTimeOut : TTimeOutEvent
read FOnTimeOutEvent
write FOnTimeOutEvent;
property OnAbort : TAbortEvent
read FOnAbortEvent
write FOnAbortEvent;
property OnAsyncOp : TAsyncOpEvent
read FOnAsyncOpEvent
write FOnAsyncOpEvent;
end;
procedure Register;
implementation
var
myWsaData : TWSADATA;
function TCsSocket.StartUp : Boolean;
var
VersionReqd : WordRec;
begin
with VersionReqD do
begin
Hi := 1;
Lo := 1;
end;
Result :=
WSAStartUp(Word(VersionReqD),
myWsaData) = 0;
if not Result then
begin
FStatus := Failure;
raise ECsSocketError.create
('Cannot start Winsock!');
Exit;
end
else
begin
with myWsaData do
begin
FVendor := StrPas(szDescription);
FWSVersion :=
Concat(IntToStr(Hi(wVersion)),'.',
(intToStr(Lo(wVersion))));
FWSStatus := StrPas(szSystemStatus);
FMaxNoSockets := IntToStr(iMaxSockets);
FMaxUDPPSize := IntToStr(iMaxUDPDg);
end;
InfoEvent('Started WinSock');
end;
end;
procedure TCsSocket.CleanUp;
begin
if FStarted then
begin
FStarted := False;
if WSACleanUp = SOCKET_ERROR then
raise ECsSocketError.create('Cannot
close Winsock!');
end;
end;
constructor TCsSocket.Create(AOwner :
TComponent);
begin
inherited Create(AOwner);
FParent := AOwner;
FValidSocket := INVALID_SOCKET;
FSockType := SockDefault;
FAddrType := AddrDefault;
FService := ServDefault;
FProtocol := ProtoDefault;
with FSockAddress do
begin
sin_family := PF_INET;
sin_addr.s_addr := INADDR_ANY;
sin_port := 0;
end;
FSocket := INVALID_SOCKET;
FLocalName := '';
FInfo := '';
FAccess := Blocking;
FStarted := StartUp;
if not FStarted then
begin
inherited Destroy;
Exit;
end;
FHomeHostName := 'local';
Foktodisplayerrors := TRUE;
FConnected := FALSE;
FWSALastError := 0;
FTimeOut := 0;
FNoOfBlockingTasks := 0;
InfoEvent(Concat('Version ',WSockVersionNo));
FAsyncHWND :=
AllocateHWND(AsyncOperation);
end;
destructor TCsSocket.Destroy;
begin
DeallocateHWND(FAsyncHWND);
CleanUp;
inherited Destroy;
end;
procedure TCsSocket.SetUpPort;
begin
{ Теперь необходимо определить номер порта
по типу сервиса }
case FService of
NoService : FPortNo := 0;
echo : FPortNo := 7;
discard : FPortNo := 9;
systat : FPortNo := 11;
daytime : FPortNo := 13;
netstat : FPortNo := 15;
qotd : FPortNo := 17;
chargen : FPortNo := 19;
ftp : FPortNo := 21;
telnet : FPortNo := 23;
smtp : FPortNo := 25;
time : FPortNo := 37;
rlp : FPortNo := 39;
nameserver : FPortNo := 42;
whois : FPortNo := 43;
domain : FPortNo := 53;
mtp : FPortNo := 57;
tftp : FPortNo := 69;
rje : FPortNo := 77;
finger : FPortNo := 79;
http : FPortNo := 80;
link : FPortNo := 87;
supdup : FPortNo := 95;
hostnames : FPortNo := 101;
ns : FPortNo := 105;
pop2 : FPortNo := 109;
pop3 : FPortNo := 110;
sunrpc : FPortNo := 111;
auth : FPortNo := 113;
sftp : FPortNo := 115;
uucp_path : FPortNo := 117;
nntp : FPortNo := 119;
end;{case}
end;
function TCsSocket.GetLocalName : String;
var
LocalName : array[0..MaxBufferSize] of Char;
begin
if gethostname(LocalName,
SizeOf(LocalName)) = 0 then
Result := StrPas(LocalName)
else
Result := '';
end;
function TCsSocket.GetInfo : String;
begin
GetInfo := FInfo;
end;
procedure TCsSocket.SetInfo(InfoReqd : String);
begin
FInfo := InfoReqd;
end;
function TCsSocket.CreateSocket: TSocket;
begin
case FSockType of
SOCKSTRM : FSocket := socket(PF_INET,
SOCK_STREAM, IPPROTO_IP);
SOCKDGRAM : FSocket := socket(PF_INET,
SOCK_DGRAM, IPPROTO_IP);
SOCKRAW : FSocket := socket(PF_INET,
SOCK_RAW, IPPROTO_IP);
end;
if FSocket = INVALID_SOCKET then
begin { Попытка создать сокет
закончилась неудачно }
FStatus := Failure;
ErrorEvent(FStatus, WSAErrorMsg);
Result := INVALID_SOCKET;
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end;
FStatus := Success;
Result := FSocket;
InfoEvent('Socket ' + IntToStr(Result) +
' created...');
end;
procedure TCsSocket.SetUpAddress;
begin
with FSockAddress.sin_addr do
begin
S_un_b.s_b1 := Fh_addr[0];
S_un_b.s_b2 := Fh_addr[1];
S_un_b.s_b3 := Fh_addr[2];
S_un_b.s_b4 := Fh_addr[3];
end;
end;
procedure TCsSocket.SetUpAddr;
begin
with FSockAddress do
begin
sin_family := AF_INET;
sin_port := FServ^.s_port;
end;
end;
procedure TCsSocket.GetServ;
var
ProtoStr,
ServStr : String;
begin
ProtoStr := Copy(ProtoStrings[TProtoTypes
(FProtocol)],1,Pos(' ',
ProtoStrings[TProtoTypes
(FProtocol)])-1);
StrPCopy(FProtoName, ProtoStr);
GetProt(FProtoName);
if FProto = NIL then
begin { Сервис недоступен }
FStatus := Failure;
ErrorEvent(FStatus, WSAErrorMsg);
InfoEvent(ProtoStr + ' not available!');
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end;
if FService = NoService then
Exit;
ServStr := Copy(ServiceStrings[TServices
(FService)],1,Pos(' ',
ServiceStrings[TServices
(FService)])-1);
StrPCopy(FServName, ServStr);
FServ := getservbyname(FServName,FProtoName);
if FServ = NIL then
begin { Сервис недоступен }
FStatus := Failure;
ErrorEvent(FStatus, WSAErrorMsg);
InfoEvent(ServStr + ' not available!');
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end;
FStatus := Success;
end;
procedure TCsSocket.GetProt(Protocol : PChar);
begin
FProto := getprotobyname(Protocol);
if FProto = NIL then
begin
FStatus := Failure;
ErrorEvent(FStatus, WSAErrorMsg);
LookUpEvent(resProto, StrPas(Protocol) +
' not available!', FALSE);
if FOKToDisplayErrors then
raise ECsSocketError.create(StrPas
(Protocol) + 'not available!');
Exit;
end;
FStatus := Success;
LookUpEvent(resProto, StrPas(FProto.p_name),
TRUE);
end;
procedure TCsSocket.WMTimer(var Message
: TMessage);
begin
KillTimer(FHandle,10);
if WSAIsBlocking then
begin
if WSACancelBlockingCall <>
SOCKET_ERROR then
InfoEvent('Timed out. Call cancelled')
else
begin
ErrorEvent(Failure, WSAErrorMsg);
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
end;
end;
end;
procedure TCsSocket.ConnectToHost;
begin
InfoEvent('Connecting to ' + FRemoteName);
case SockType of
SOCKSTRM : begin
if connect(FSocket,
FSockAddress,
SizeOf(TSockAddrIn)) =
SOCKET_ERROR then
begin
if WSAGetLastError <>
WSAEWOULDBLOCK then
begin
ErrorEvent(Failure, WSAErrorMsg);
FConnected := FALSE;
closesocket(FSocket);
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end;
end;
FStatus := Success;
FConnected := TRUE;
end;
SOCKDGRAM : begin
end;
end;{case}
end;
procedure TCsSocket.GetHost;
begin
if Length(HostName) = 0 then
begin
MessageDlg('No host name given!',
mtError,[mbOk],0);
FStatus := Failure;
Exit;
end;
CreateSocket;
if FStatus = Failure then
Exit;
GetServ;
if FStatus = Failure then
begin
raise ECsSocketError.create('Failed to
resolve host : ' + HostName);
Exit;
end;
SetUpAddress;
if FService = NoService then
FSockAddress.sin_family := AF_INET (* для
приложений, не требующих порта *)
else
SetUpAddr;
if FStatus = Failure then
Exit;
FRemoteName :=
StrPas(inet_ntoa(FSockAddress.sin_addr));
if SockType = SockStrm then
ConnectToHost
else
begin
{ Поскольку мы работаем с пакетами,
предполагается,
что соединение уже имеется }
FConnected := TRUE;
end;
end;
procedure TCsSocket.GetServer;
begin
GetServ;
if Status = Failure then Exit;
FSockAddress.sin_family
:= PF_INET;
FSockAddress.sin_port
:= FServ^.s_port;
FSockAddress.sin_addr.s_addr
:= htonl(INADDR_ANY);
FRemoteName
:= LocalName;
FSocket
:= CreateSocket;
end;
procedure TCsSocket.QuitSession;
begin
if FConnected then
begin
if WSAIsBlocking then
WSACancelBlockingCall;
closesocket(FSocket);
FConnected := FALSE;
end;
end;
function TCsSocket.WSAErrorMsg : String;
begin
FWSALastError := WSAGetLastError;
Result := LoadStr(SWSABASE + FWSALastError);
FStatus := Failure;
end;
procedure TCsSocket.SetRemoteHostName(NameReqd
: String);
var
P : Pointer;
IPAddress : LongInt;
begin
FRemoteName := NameReqd;
if Length(NameReqd) = 0 then
begin
FStatus := Failure;
ErrorEvent(FStatus, 'No host name given!');
case FLookUpOp of
resHostName : LookUpEvent(resHostName,
FRemoteName, FALSE);
resIPAddress : LookUpEvent(resIPAddress,
FRemoteName, FALSE);
end;// case
raise ECsSocketError.create('No host
name given!');
Exit;
end;
if FAccess = NonBlocking then
SetAsyncHostName(FRemoteName)
else
begin
InfoEvent('Resolving host');
StrPCopy(FpHostName, FRemoteName);
{ Определяем тип введенного адреса }
IPAddress := inet_addr(FpHostName);
if IPAddress <>INADDR_NONE then
{ Это IP-адрес }
begin
FLookUpOp := resHostName;
FAddress := IPAddr;
P := addr(IPAddress);
case AddrType of
AFINET : FHost := gethostbyaddr(P, 4,
AF_INET);
end;
end
else { Нет, это больше похоже на символьное
имя хоста }
begin
FLookUpOp := resIPAddress;
FAddress := HostAddr;
FHost := gethostbyname(FpHostName);
end;
if FHost = NIL then
begin{ Неизвестный хост, отменяем попытку... }
LookUpEvent(FLookUpOp, '', FALSE);
FStatus := Failure;
if FOKToDisplayErrors then
raise ECsSocketError.create('Unable to
resolve ' + FpHostName);
Exit;
end;
InfoEvent('Host found');
FStatus := Success;
Move(FHost^.h_addr_list^, Fh_addr,
SizeOf(FHost^.h_addr_list^));
if FAddress = HostAddr then
begin
SetUpAddress;
FRemoteName
:= StrPas(inet_ntoa(FSockAddress.sin_addr));
end
else
if FAddress = IPAddr then
begin
FRemoteName := StrPas(FHost^.h_name);
InfoEvent('Host found...');
end;
case FLookUpOp of
resHostName : LookUpEvent(resHostName,
FRemoteName, TRUE);
resIPAddress : LookUpEvent(resIPAddress,
FRemoteName, TRUE);
end;// case
end;
end;
function TCsSocket.GetDataBuff : String;
var
Response : Integer;
Buffer : CharArray;
begin
Response := recv(FSocket, Buffer,
MaxBufferSize, 0);
if Response = SOCKET_ERROR then
begin
if WSAGetLastError <> WSAEWOULDBLOCK then
{ Это действительно ошибка! }
begin
FStatus := Failure;
ErrorEvent(FStatus, WSAErrorMsg);
Result := '';
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end else Exit;
end
else
if Response = 0 then { Больше нет данных
от хоста}
begin
Result := '';
Exit;
end;
Buffer[Response] := NULL;
FData := StrPas(Buffer);
Result := FData;
end;
procedure TCsSocket.SetDataBuff(DataReqd :
String);
var
Data : CharArray;
Response : Integer;
begin
FData := DataReqd;
StrPCopy(Data, FData);
StrCat(Data, CRLF);
Response := send(FSocket, Data, StrLen(Data), 0);
if Response = SOCKET_ERROR then
begin { Ошибка при посылке данных
удаленному хосту }
if WSAGetLastError <>
WSAEWOULDBLOCK then{ Это
действительно ошибка! }
begin
FStatus := Failure;
ErrorEvent(FStatus, WSAErrorMsg);
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end
end;
end;
function TCsSocket.GetDatagram : String;
var
Size : Integer;
Response : Integer;
MsgBuff : CharArray;
begin
Size := SizeOf(TSockAddrIn);
Response := recvfrom(FSocket, MsgBuff,
SizeOf(MsgBuff), 0,
FSockAddress, Size);
if Response = SOCKET_ERROR then
begin { Ошибка при посылке данных
удаленному хосту }
if WSAGetLastError <> WSAEWOULDBLOCK
then{ Это действительно ошибка! }
begin
FStatus := Failure;
ErrorEvent(FStatus, WSAErrorMsg);
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end
end;
Result := StrPas(MsgBuff);
end;
procedure TCsSocket.SetDatagram(DataReqd
: String);
var
Response : Integer;
MsgBuff : CharArray;
begin
StrpCopy(MsgBuff,DataReqd);
StrCat(MsgBuff,@NULL);
Response := sendto(FSocket, MsgBuff,
SizeOf(MsgBuff), MSG_DONTROUTE,
FSockAddress,
SizeOf(TSockAddrIn));
if Response = SOCKET_ERROR then
begin { Ошибка при посылке данных удаленному
хосту }
if WSAGetLastError <> WSAEWOULDBLOCK
then { Это действительно ошибка! }
begin
FStatus := Failure;
ErrorEvent(FStatus, WSAErrorMsg);
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end
end else InfoEvent('Data sent...');
end;
function TCsSocket.GetOOBData : String;
var
Response: integer;
Data : CharArray;
begin
if FSocket <> INVALID_SOCKET then
begin
Response := recv(FSocket,Data,255,MSG_OOB);
if Response < 0 then
begin
ErrorEvent(Failure, WSAErrorMsg);
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
FStatus := Failure;
Exit;
end;
Data[Response] := NULL;
Result := StrPas(Data);
end
else Result := '';
end;
procedure TCsSocket.SetOOBData(ReqdOOBData
: String);
var
Data : CharArray;
Response : Integer;
begin
if WSAIsBlocking then
if WSACancelBlockingCall <>
SOCKET_ERROR then
begin
StrPCopy(Data, ReqdOOBData);
StrCat(Data, CRLF);
Response := send(FSocket, Data,
StrLen(Data), MSG_OOB);
if Response = SOCKET_ERROR then
begin { Ошибка при посылке данных
удаленному хосту }
FStatus := Failure;
ErrorEvent(Failure,WSAErrorMsg);
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end;
end;
end;
procedure TCsSocket.Cancel;
begin
if WSAIsBlocking then
if WSACancelBlockingCall = SOCKET_ERROR then
begin
FStatus := Failure;
ErrorEvent(FStatus,WSAErrorMsg);
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
end;
end;
{ Начало асинхронного кода }
procedure TCsSocket.StartAsyncSelect;
begin
FRC := WSAAsyncSelect(FSocket, FHwnd,
SOCK_EVENT, FD_READ
or FD_CONNECT or FD_WRITE or FD_CLOSE);
if FRC = SOCKET_ERROR then
begin
FStatus := Failure;
ErrorEvent(FStatus,WSAErrorMsg);
InfoEvent('Cannot get WSAAsyncSelect');
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end;
end;
procedure TCsSocket.SetPortName(ReqdPortName
: String);
var
ProtocolName : String;
ProtoName : CharArray;
begin
if Length(ReqdPortName) = 0 then
begin
FStatus := Failure;
LookUpEvent(resPort,'',FALSE);
raise ECsSocketError.create('No port
number given!');
Exit;
end;
if ReqdPortName[1] in ['a'..'z', 'A'..'Z'] then
begin
FStatus := Failure;
LookUpEvent(resPort,'',FALSE);
raise ECsSocketError.create('You must enter a
number for a port!');
Exit;
end;
if FAccess = NonBlocking then
SetAsyncPort(ReqdPortName)
else
begin
FPortName := ReqdPortName;
ProtocolName := ProtoStrings[FProtocol];
ProtocolName := Copy(ProtocolName,1, Pos(' ',
ProtocolName)-1);
StrPCopy(ProtoName, ProtocolName);
FServ := getservbyport(htons(StrToInt
(FPortName)),ProtoName);
if FServ = NIL then
begin
FStatus := Failure;
FPortName := 'no service';
LookUpEvent(resPort, '', FALSE);
if FOKToDisplayErrors then
raise ECsSocketError.create('Cannot get
service');
end else
begin
FStatus := Success;
FPortName := StrPas(Fserv^.s_name);
LookUpEvent(resPort, FPortName, TRUE);
end;
end;
end;
procedure TCsSocket.SetServiceName(ReqdServiceName
: String);
var
ProtoName, ServName : CharArray;
ProtocolName : String;
begin
if Length(ReqdServiceName) = 0 then
begin
FStatus := Failure;
LookUpEvent(resService, '', FALSE);
raise ECsSocketError.create('No service
name given!');
Exit;
end;
if FAccess = NonBlocking then
SetAsyncService(ReqdServiceName) else
begin
FServiceName := ReqdServiceName;
StrPCopy(ServName, FServiceName);
ProtocolName := ProtoStrings[FProtocol];
ProtocolName := Copy(ProtocolName,1, Pos(' ',
ProtocolName)-1);
StrPCopy(ProtoName, ProtocolName);
FServ := getservbyname(ServName,ProtoName);
if FServ = NIL then
begin
FStatus := Failure;
LookUpEvent(resService, '', FALSE);
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
end else
begin
FStatus := Success;
FPortName
:= IntToStr(LongInt(abs(ntohs(FServ^.s_port))));
LookUpEvent(resService, FPortName, TRUE);
end;
end;
end;
procedure TCsSocket.SetProtocolName
(ReqdProtoName : String);
var
ProtoName : CharArray;
begin
if Length(ReqdProtoName) = 0 then
begin
FStatus := Failure;
LookUpEvent(resProto,'No protocol
number given!',FALSE);
raise ECsSocketError.create('No
protocol number given!');
Exit;
end;
if FAccess = NonBlocking then
SetAsyncProtoName(ReqdProtoName)
else
begin
StrPCopy(ProtoName, ReqdProtoName);
FProto := getprotobyname(ProtoName);
if FProto = NIL then
begin
InfoEvent(StrPas(ProtoName) +
' not available!');
LookUpEvent(resProto, '', FALSE);
FStatus := Failure;
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end;
FStatus := Success;
FProtocolNo := IntToStr(FProto^.p_proto);
LookUpEvent(resProto, FProtocolNo, TRUE)
end;
end;
procedure TCsSocket.SetProtoNo(ReqdProtoNo
: String);
var
ProtoNo : Integer;
begin
if Length(ReqdProtoNo) = 0 then
begin
FStatus := Failure;
raise ECsSocketError.create('No protocol
number given!');
Exit;
end;
if FAccess = NonBlocking then
SetAsyncProtoNo(ReqdProtoNo)
else
begin
ProtoNo := StrToInt(ReqdProtoNo);
FProto := getprotobynumber(ProtoNo);
if FProto = NIL then
begin
InfoEvent(IntToStr(ProtoNo) +
' not available!');
LookUpEvent(resProtoNo, '', FALSE);
FStatus := Failure;
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end;
FStatus := Success;
FProtocolName := StrPas(FProto^.p_name);
LookUpEvent(resProtoNo,FProtocolName, TRUE);
end;
end;
procedure TCsSocket.CancelAsyncOperation(CancelOP
: Boolean);
begin
if WSACancelAsyncRequest(THandle(FTaskHandle))
= SOCKET_ERROR then
begin
FStatus := Failure;
ErrorEvent(FStatus,WSAErrorMsg);
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
end
else
begin
FStatus := Success;
InfoEvent('WSAAsync lookup cancelled!');
end;
end;
procedure TCsSocket.AsyncOperation(var Mess
: TMessage);
var
MsgErr : Word;
begin
if Mess.Msg = ASYNC_EVENT then
begin
MsgErr := WSAGetAsyncError(Mess.lparam);
if MsgErr <> 0 then
begin
FStatus := Failure;
ErrorEvent(FStatus,WSAErrorMsg);
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end
else
begin
FStatus := Success;
InfoEvent('WSAAsync operation succeeded!');
case FAsyncType of
AsyncName,
AsyncAddr : begin
FHost := pHostent(@FAsyncBuff);
if (FHost^.h_name = NIL) then
begin { Неизвестный хост, отменяем попытку...}
FStatus := Failure;
if FAsyncType = AsyncName then
LookUpEvent(resIPAddress,'',FALSE)
else
LookUpEvent(resHostName,'',FALSE);
if FOKToDisplayErrors then
raise ECsSocketError.create('Unable
to resolve host');
Exit;
end;
if length(StrPas(FHost^.h_name)) = 0 then
begin
InfoEvent('Host lookup failed!');
FStatus := Failure;
if FAsyncType = AsyncName then
LookUpEvent(resIPAddress,'',FALSE)
else
LookUpEvent(resHostName,'',FALSE);
if FOKToDisplayErrors then
raise ECsSocketError.create('Unknown host');
Exit;
end;
case FAddress of
IPAddr : begin
Move(FHost^.h_addr_list^, Fh_addr,
SizeOf(FHost^.h_addr_list^));
FAsyncRemoteName :=
StrPas(FHost^.h_name);
LookUpEvent(resHostName,
FAsyncRemoteName, TRUE);
end;
HostAddr : begin
Move(FHost^.h_addr_list^, Fh_addr,
SizeOf(FHost^.h_addr_list^));
SetUpAddress;
FAsyncRemoteName:=
StrPas(inet_ntoa(FSockAddress.
sin_addr));
LookUpEvent(resIPAddress,FAsyncRemoteName,
TRUE);
end;
end;{case}
end;
AsyncServ : begin
FServ := pServent(@FAsyncBuff);
if FServ^.s_name = NIL then
begin { Сервис недоступен }
FStatus := Failure;
LookUpEvent(resService,'',FALSE);
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end;
FAsyncPort := IntToStr(ntohs(FServ^.s_port));
LookUpEvent(resService, FAsyncPort, TRUE);
end;
AsyncPort : begin
FServ := pServent(@FAsyncBuff);
if FServ^.s_name = NIL then
begin { Сервис недоступен }
FStatus := Failure;
LookUpEvent(resPort,'',FALSE);
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end;
FAsyncService := StrPas(FServ^.s_name);
LookUpEvent(resPort, FAsyncService, TRUE);
end;
AsyncProtoName : begin
FProto := pProtoEnt(@FAsyncBuff);
if FProto^.p_name = NIL then
begin
FStatus := Failure;
LookUpEvent(resProto,'',FALSE);
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end;
FAsyncProtoNo := IntToStr(FProto^.p_proto);
LookUpEvent(resProto, FAsyncProtoNo, TRUE);
end;
AsyncProtoNumber : begin
FProto := pProtoEnt(@FAsyncBuff);
if FProto^.p_name = NIL then
begin
FStatus := Failure;
LookUpEvent(resProtoNo,'',FALSE);
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end;
FAsyncProtocol := StrPas(FProto^.p_name);
LookUpEvent(resProtoNo, FAsyncProtocol, TRUE);
end;
end;
if FNoOfBlockingTasks > 0 then
dec(FNoOfBlockingTasks);
end;
end;
end;
function TCsSocket.GetAsyncHostName : String;
begin
InfoEvent('Host resolved');
Result := FAsyncRemoteName;
end;
procedure TCsSocket.SetAsyncHostName(ReqdHostName
: String);
var
IPAddress : TInaddr;
SAddress: array[0..31] of char;
begin
FillChar(FAsyncBuff, SizeOf(FAsyncBuff), #0);
FAsyncRemoteName := ReqdHostName;
StrPcopy(SAddress, FAsyncRemoteName);
IPAddress.s_addr := inet_addr(SAddress);
if IPAddress.s_addr <> INADDR_NONE then
{ Это IP-адрес }
begin
FAddress := IPAddr;
FAsyncType := AsyncAddr;
if IPAddress.s_addr <> 0 then
FTaskHandle
:= WSAAsyncGetHostByAddr(FAsyncHWND, ASYNC_EVENT,
pChar(@IPAddress), 4, PF_INET,
@FAsyncBuff[0], SizeOf(FAsyncBuff));
if FTaskHandle = 0 then
begin
if FNoOfBlockingTasks > 0 then
dec(FNoOfBlockingTasks);
FStatus := Failure;
ErrorEvent(FStatus,WSAErrorMsg);
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end else FStatus := Success;
end
else { Нет, это больше похоже на символьное
имя хоста }
begin
FAddress := HostAddr;
FAsyncType := AsyncName;
Inc(FNoOfBlockingTasks);
FTaskHandle
:= WSAAsyncGetHostByName(FAsyncHWND, ASYNC_EVENT,
@FpHostName[0],
@FAsyncBuff[0],
MAXGETHOSTSTRUCT);
if FTaskHandle = 0 then
begin
FStatus := Failure;
if FNoOfBlockingTasks > 0 then
dec(FNoOfBlockingTasks);
ErrorEvent(FStatus,WSAErrorMsg);
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end else FStatus := Success;
end;
end;
function TCsSocket.GetAsyncService : String;
begin
InfoEvent('Service resolved');
Result := FAsyncService;
end;
procedure TCsSocket.SetAsyncService(ReqdService
: String);
var
ProtoStr,
ServStr : String;
begin
ProtoStr
:= Copy(ProtoStrings[TProtoTypes
(FProtocol)],1,Pos(' ',
ProtoStrings[TProtoTypes(FProtocol)])-1);
StrPCopy(FProtoName, ProtoStr);
FProto := getprotobyname(FProtoName);
if FProto = NIL then
begin { Сервис недоступен }
FStatus := Failure;
InfoEvent(ProtoStr + ' not available!');
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end;
ServStr := ReqdService;
if Length(ServStr) = 0 then
begin
FStatus := Failure;
ErrorEvent(FStatus,WSAErrorMsg);
raise ECsSocketError.create('No service name!');
Exit;
end;
FillChar(FAsyncBuff, SizeOf(FAsyncBuff), #0);
StrPCopy(FServName, ServStr);
Inc(FNoOfBlockingTasks);
FAsyncType := AsyncServ;
FTaskHandle := WSAAsyncGetServByName
(FAsyncHWND, ASYNC_EVENT,
FServName, FProtoName,
@FAsyncBuff[0],
MAXGETHOSTSTRUCT);
if FTaskHandle = 0 then
begin
FStatus := Failure;
if FNoOfBlockingTasks > 0 then
dec(FNoOfBlockingTasks);
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end else FStatus := Success;
end;
function TCsSocket.GetAsyncPort : String;
begin
InfoEvent('Port resolved');
Result := FAsyncPort;
end;
procedure TCsSocket.SetAsyncPort(ReqdPort
: String);
var
ProtoStr,
PortStr : String;
begin
ProtoStr
:= Copy(ProtoStrings
[TProtoTypes(FProtocol)],1,Pos(' ',
ProtoStrings[TProtoTypes(FProtocol)])-1);
StrPCopy(FProtoName, ProtoStr);
FProto := getprotobyname(FProtoName);
if FProto = NIL then
begin { Сервис недоступен }
FStatus := Failure;
InfoEvent(ProtoStr + ' not available!');
ErrorEvent(Failure, ProtoStr +
' not available');
raise ECsSocketError.create(ProtoStr +
' not available');
Exit;
end;
PortStr := ReqdPort;
if Length(PortStr) = 0 then
begin
FStatus := Failure;
raise ECsSocketError.create('No port number!');
Exit;
end;
FillChar(FAsyncBuff, SizeOf(FAsyncBuff), #0);
FAsyncType := AsyncPort;
FTaskHandle := WSAAsyncGetServByPort
(FAsyncHWND, ASYNC_EVENT,
htons(StrToInt(PortStr)),
FProtoName,
@FAsyncBuff[0],
MAXGETHOSTSTRUCT);
if FTaskHandle = 0 then
begin
FStatus := Failure;
if FNoOfBlockingTasks > 0 then
dec(FNoOfBlockingTasks);
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end else FStatus := Success;
end;
function TCsSocket.GetAsyncProtoName : String;
begin
InfoEvent('Protocol resolved');
Result := FAsyncProtocol;
end;
procedure TCsSocket.SetAsyncProtoName
(ReqdProtoName : String);
begin
if Length(ReqdProtoName) = 0 then
begin
FStatus := Failure;
ErrorEvent(FStatus, 'No protocol name!');
raise ECsSocketError.create('No
protocol name!');
Exit;
end;
FillChar(FAsyncBuff, SizeOf(FAsyncBuff), #0);
StrPCopy(FProtoName, ReqdProtoName);
FAsyncType := AsyncProtoName;
FTaskHandle
:= WSAAsyncGetProtoByName(FAsyncHWND, ASYNC_EVENT,
@FProtoName[0],
@FAsyncBuff[0],
MAXGETHOSTSTRUCT);
if FTaskHandle = 0 then
begin
FStatus := Failure;
ErrorEvent(FStatus, WSAErrorMsg);
if FNoOfBlockingTasks > 0 then
dec(FNoOfBlockingTasks);
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end else FStatus := Success;
end;
function TCsSocket.GetAsyncProtoNo : String;
begin
InfoEvent('Proto Number resolved');
Result := FAsyncProtoNo;
end;
procedure TCsSocket.SetAsyncProtoNo(ReqdProtoNo
: String);
var
ProtocolNo : Integer;
begin
if Length(ReqdProtoNo) = 0 then
begin
FStatus := Failure;
ErrorEvent(FStatus,'No protocol number!');
raise ECsSocketError.create('No protocol
number!');
Exit;
end;
FillChar(FAsyncBuff, SizeOf(FAsyncBuff), #0);
ProtocolNo := StrToInt(ReqdProtoNo);
FAsyncType := AsyncProtoNumber;
FTaskHandle
:= WSAAsyncGetProtoByNumber(FAsyncHWND,ASYNC_EVENT,
ProtocolNo, @FAsyncBuff[0],
MAXGETHOSTSTRUCT);
if FTaskHandle = 0 then
begin
FStatus := Failure;
ErrorEvent(FStatus,WSAErrorMsg);
if FNoOfBlockingTasks > 0 then
dec(FNoOfBlockingTasks);
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end else FStatus := Success;
end;
function TCsSocket.CheckConnection : Boolean;
var
peeraddr : tsockaddr;
namelen : integer;
begin
namelen := SizeOf(tsockaddr);
Result := getpeername(FSocket,
peeraddr, namelen) = 0;
end;
procedure TCsSocket.ConnEvent;
begin
if Assigned(FOnConnEvent) then
FOnConnEvent(Self, FSocket);
end;
procedure TCsSocket.CleanUpEvent;
begin
if Assigned(FOnCleanUpEvent) then
FOnCleanUpEvent(Self, FCleanUp);
end;
procedure TCsSocket.DisConnEvent;
begin
if Assigned(FOnDisConnEvent) then
FOnDisConnEvent(Self, FSocket);
end;
procedure TCsSocket.InfoEvent(Msg : String);
begin
if Assigned(FOnInfoEvent) then
FOnInfoEvent(Self, Msg);
end;
procedure TCsSocket.ErrorEvent(Status
: TConditions; Msg : String);
begin
if Assigned(FOnErrorEvent) then
FOnErrorEvent(Self, Status, Msg);
end;
procedure TCsSocket.StatusEvent;
begin
if Assigned(FOnStatusEvent) then
FOnStatusEvent(Self, '','');
end;
procedure TCsSocket.BusyEvent;
begin
if Assigned(FOnBusyEvent) then
FOnBusyEvent(Self, FBusy);
end;
procedure TCsSocket.LookUpEvent(Value
: TLookUpOp; Msg : String; Result
: Boolean);
begin
if Assigned(FOnLookUpEvent) then
FOnLookUpEvent(Self, Value, Msg, Result);
end;
procedure TCsSocket.SendDataEvent;
begin
if Assigned(FOnSendDataEvent) then
FOnSendDataEvent(Self, FSocket);
end;
procedure TCsSocket.RecvDataEvent;
begin
if Assigned(FOnRecvDataEvent) then
FOnRecvDataEvent(Self, FSocket);
end;
procedure TCsSocket.TimeOutEvent;
begin
if Assigned(FOnTimeOutEvent) then
FOnTimeOutEvent(Self, FSocket, FTimeOut);
end;
procedure TCsSocket.AbortEvent;
begin
if Assigned(FOnAbortEvent) then
FOnAbortEvent(Self);
end;
procedure TCsSocket.AsyncOpEvent;
begin
if Assigned(FOnAsyncOpEvent) then
FOnAsyncOpEvent(Self, FSocket);
end;
// Начало кода WinSock - реализация
{$INCLUDE CsSOCKIMP.PAS}
procedure Register;
begin
RegisterComponents('CSWinsock', [TCsSocket]);
end;
end.
В Unix сетевые протоколы обычно компилируются прямо в ядро операционной системы. Как следствие, они всегда инициализированы и доступны для приложений. Однако в Windows ситуация выглядит иначе. Перед тем как приложение сможет воспользоваться услугами сетевого протокола, оно сначала должно обратиться с запросом на инициализацию к Winsock DLL. Компонент CsSocket решает эту задачу с помощью своего private-метода StartUp . Конструктор TCsSocket.Create задает значения свойств по умолчанию и затем вызывает StartUp (см. листинг 5.2). Листинг 5.2. Конструктор TCsSocket.Create constructor TCsSocket.Create(AOwner : TComponent);
begin
inherited Create(AOwner);
FParent := AOwner;
FValidSocket := INVALID_SOCKET;
FSockType := SockDefault;
FAddrType := AddrDefault;
FService := ServDefault;
FProtocol := ProtoDefault;
with FSockAddress do
begin
sin_family := PF_INET;
sin_addr.s_addr := INADDR_ANY;
sin_port := 0;
end;
FSocket := INVALID_SOCKET;
FLocalName := '';
FInfo := '';
FAccess := Blocking;
FStarted := StartUp;
if not FStarted then
begin
inherited Destroy;
Exit;
end;
FHomeHostName := 'local';
Foktodisplayerrors := TRUE;
FConnected := FALSE;
FWSALastError := 0;
FTimeOut := 0;
FNoOfBlockingTasks := 0;
InfoEvent(Concat('Version ',WSockVersionNo));
FAsyncHWND
:= AllocateHWND(AsyncOperation);
end;
Метод StartUp проверяет доступность Winsock DLL и ее статус. В нем задаются значения следующих свойств: FVendor, FWSVersion, FMaxNoSocks и FMaxUDPPSize (см. листинг 5.3). Это чисто информационные свойства, которые никак не влияют на работу главного приложения. При желании вы можете вывести данные, возвращаемые методом StartUp. Если методу StartUp не удается инициализировать Winsock DLL, он присваивает полю FStatus код «неудача», отображает сообщение об ошибке и завершает работу. Приложение, вызывающее этот метод, всегда должно проверять значение свойства Status во время инициализации программы, обычно в обработчике OnCreate приложения. Листинг 5.3. Функция TCsSocket.StartUp function TCsSocket.StartUp : Boolean;
var
VersionReqd : WordRec;
begin
with VersionReqD do
begin
Hi := 1;
Lo := 1;
end;
Result := WSAStartUp(Word(VersionReqD),
myWsaData) = 0;
if not Result then
begin
FStatus := Failure;
raise ECsSocketError.create('Cannot
start Winsock!');
Exit;
end
else
begin
with myWsaData do
begin
FVendor := StrPas(szDescription);
FWSVersion :=
Concat(IntToStr(Hi(wVersion)),'.',
(intToStr(Lo(wVersion))));
FWSStatus := StrPas(szSystemStatus);
FMaxNoSockets := IntToStr(iMaxSockets);
FMaxUDPPSize := IntToStr(iMaxUDPDg);
end;
InfoEvent('Started WinSock');
end;
end;
«Уборка мусора» не менее важна, чем инициализация. Когда клиентское приложение завершает свою работу (и не нуждается более в услугах Winsock), оно должно приказать Winsock DLL освободить используемую память. Процедура CleanUp (см. листинг 5.4) автоматически выполняет эту работу при закрытии Winsock DLL. Листинг 5.4. Процедура TCsSocket.CleanUp procedure TCsSocket.CleanUp;
begin
if FStarted then
begin
FStarted := False;
if WSACleanUp = SOCKET_ERROR then
raise ECsSocketError.create('Cannot
close Winsock!');
end;
end;
Наконец, обращение к Winsock DLL может закончиться неудачей по целому ряду причин, обусловленных спецификой сети. Если это происходит, CsSocket сообщает об ошибке, вызывая функцию Winsock WSAGetLastError через WSA ErrorMsg. Приложение RESOLVER32Программа RESOLVER32 использует ряд интересных методов и свойств объекта TCsSocket. RESOLVER32 может преобразовывать символьное имя хоста в его IP-адрес (то есть адрес в Internet), и наоборот. Кроме того, программа определяет взаимное соответствие между номером порта и типом сервиса, а также между номером протокола и его именем. Все эти примеры взяты из практики, поскольку преобразование имен хостов и имен сервисов — самые распространенные операции, выполняемые приложениями Winsock.На рис. 5.1 показано, как выглядит приложение в Delphi IDE. Щелкните на компоненте CsSocket1, и в окне инспектора объектов появится перечень его свойств (см. рис. 5.2). Содержащиеся в нем стандартные значения хорошо подходят для выполнения преобразований с помощью блокирующих функций. Свойство Service по умолчанию имеет значение NoService, поскольку в нашем приложении не предусмотрено конкретного сервиса для выполнения преобразований. На рис. 5.3 изображена вкладка Events с несколькими обработчиками событий. При любом изменении статуса Winsock DLL обработчик CsSocket1OnInfo передает информацию от CsSocket к приложению. Аналогично, процедура CsSocket1LookUp передает информацию при завершении работы функции просмотра. Также заслуживает внимания процедура CsSocket1Error, которая сообщает приложению об ошибках, случившихся во время работы CsSocket.
Рис. 5.1. Приложение RESOLVER32
Рис. 5.2. Свойства CsSocket
Рис. 5.3. События CsSocket При запуске приложения RESOLVER32 процедура Application.CreateForm из файла RESOLVER32.DPR вызывает конструктор TCsSocket.Create, чтобы задать свойствам CsSocket значения по умолчанию. После того как конструктор инициализирует компоненты и успешно обратится к Winsock DLL, процедура TFrmMain.FormCreate (см. листинг 5.5) выполняет ряд других задач. В частности, метод TMainForm.FormCreate должен проверить свойство Status, обновляемое в CsSocket. Если свойство Status сообщает о наличии сбоев, RESOLVER32 блокирует кнопку Resolve и текстовые поля, устанавливает цвет компонента pnStatus (элемента типа TPanel) в значение clRed и выводит в панели pnStatus сообщение об ошибке. Если же все прошло гладко, RESOLVER32 обновляет элементы в групповом поле gbWSInfo в соответствии со значениями, полученными от Winsock. Листинг 5.5. Процедура FormCreate главной формы procedure TfrmMain.FormCreate(Sender: TObject); begin tag := 1; memErrorLog.Clear; memErrorLog.Visible := FALSE; if CsSocket1.Status = Failure then begin pnStatus.Color := clRed; pnStatus.Caption := 'Winsock not available!'; btnResolve.Enabled := FALSE; gbNameRes.Enabled := FALSE; gbServiceRes.Enabled := FALSE; gbProtoRes.Enabled := FALSE; gbTypeOfLookUp.Enabled := FALSE; edMachineName.Text := ''; edVendorName.Text := ''; edVersionNo.Text := ''; edMaxNoSockets.Text := ''; edMaxUDPacketSize.Text := ''; edWSStatusInfo.Text := ''; end else begin with CsSocket1 do begin edMachineName.Text := LocalName; edVendorName.Text := WSVendor; edVersionNo.Text := WSVersion; edMaxNoSockets.Text := WSMaxNoSockets; edMaxUDPacketSize.Text := WSMaxUDPPSize; edWSStatusInfo.Text := WSStatus; Access := Blocking; rgProtocol.ItemIndex := 0; // По умолчанию выбирается TCP end; if CsSocket1.Access = Blocking then begin btnAbortRes.Enabled := FALSE; rbBlocking.Checked := TRUE; end; cbHint.Checked := TRUE; frmMain.ShowHint := TRUE; end; end; Как меня зовут?Программа RESOLVER32 отображает имя, под которым ваш компьютер числится в сети. Это достигается путем присваивания тексту в поле ввода edMachineName значения свойства CsSocket1.LocalName. Метод TCsSocket.GetLocalName является оболочкой для функции gethostname Winsock API. Он извлекает имя вашего компьютера из локального файла хостов (который обычно хранится в каталоге Windows) и возвращает его в свойстве LocalName.В листинге 5.6 приведен метод TCsSocket.GetLocalName из файла CSSOCKET.PAS. Обратите внимание — gethostname, как и все функции Winsock, работает только со строками, завершающимися нулевым символом . Метод Get LocalName использует функцию StrPas, чтобы преобразовать возвращаемый результат в строку Object Pascal. Затем имя компьютера выводится в текстовом поле edMachineName. Если компьютер не имеет имени, GetLocalName просто возвращает пустую строку. Разнообразная информация, собранная методом TCsSocket.StartUp об используемом Winsock DLL, передается RESOLVER32 через свойства WSVendor, WSVersion, WSStatus, WSMaxNoSockets и WSMaxUDPPSize и отобража ется в групповом поле gbWSInfo. Листинг 5.6. Функция GetLocalName function TCsSocket.GetLocalName : String; var LocalName : array[0..MaxBufferSize] of Char; begin if gethostname(LocalName, SizeOf(LocalName)) = 0 then Result := StrPas(LocalName) else Result := ''; end; Какой у тебя адрес?Преобразование имени хоста является самой распространенной операцией, выполняемой Winsock-приложениями в режиме блокировки. В данном случае «режим блокировки» означает, что приложение ожидает ответа от удаленного компьютера — ответа, который может никогда не прийти. До получения ответа заблокированное приложение не может продолжать работу или реагировать на ввод информации пользователем и часто кажется «мертвым».В таких операционных системах, как Unix, Windows 95 и Windows NT, такое поведение не представляет особых проблем. Даже если приложение заблокировано, использованный в них принцип вытеснения задач позволяет другим приложениям нормально работать. Чтобы пользователь не терял возможности взаимодействовать с любым приложением Winsock во время блокировки, Winsock заменяет блокирующие функции псевдоблокирующими асинхронными эквивалентами. Вместо того чтобы осуществлять полноценную блокировку, эти функции при ожидании завершения сетевого события переходят в цикл опроса. Псевдоблокирующие функции можно узнать по префиксу WSAAsync. Например, функция WSAAsyncGet HostByName является асинхронной версией gethostbyname. Используя WSAAsyncGet HostByName, пользователь может в любой момент прервать операцию просмотра. В блокирующих функциях такая возможность отсутствует. Чтобы изменить поведение RESOLVER32, достаточно сменить значение свойства Access c Blocking на NonBlocking, или наоборот. Значение NonBlocking сообщает CsSocket о том, что для просмотра должны использоваться асинхронные функции. Обычно хост Internet идентифицируется в
сети по уникальному адресу
Чтобы преобразовать имя хоста, введите его в текстовом поле edHostName программы RESOLVER32. После нажатия кнопки Resolve RESOLVER32 присваивает имя, введенное в edHostName, свойству Hostname. При этом свойство вызывает метод TCsSocket.SetRemoteHostName. Если строка NameReqd пуста, SetRemote HostName сообщает об ошибке и завершается. В противном случае CsSocket проверяет значение поля FAccess (которое может быть равно Blocking или NonBlocking в зависимости от свойства Access), чтобы определить режим преобразования имени хоста в IP-адрес. Если значение FAccess равно NonBlocking, вызывается SetAsyncHostName. В противном случае функция StrpCopy преобразует FRemoteName из строки Паскаля в строку с нуль-терминатором. В листинге 5.7 показано, как это делается в CsSocket. Листинг 5.7. Метод TCsSocket.SetRemoteHostName
— преобразование
procedure TCsSocket.SetRemoteHostName(NameReqd
: String);
var
P : Pointer;
IPAddress : LongInt;
begin
FRemoteName := NameReqd;
if Length(NameReqd) = 0 then
begin
FStatus := Failure;
ErrorEvent(FStatus, 'No host name given!');
case FLookUpOp of
resHostName : LookUpEvent(resHostName,
FRemoteName, FALSE);
resIPAddress : LookUpEvent(resIPAddress,
FRemoteName, FALSE);
end;// case
raise ECsSocketError.create('No host
name given!');
Exit;
end;
if FAccess = NonBlocking then
SetAsyncHostName(FRemoteName)
else
begin
InfoEvent('Resolving host');
StrPCopy(FpHostName, FRemoteName);
{ Определяем тип введенного адреса }
IPAddress := inet_addr(FpHostName);
if IPAddress <>INADDR_NONE then
{ Это IP-адрес }
begin
FLookUpOp := resHostName;
FAddress := IPAddr;
P := addr(IPAddress);
case AddrType of
AFINET : FHost := gethostbyaddr(P, 4,
AF_INET);
end;
end
else { Нет, это больше похоже на
символьное имя хоста }
begin
FLookUpOp := resIPAddress;
FAddress := HostAddr;
FHost := gethostbyname(FpHostName);
end;
if FHost = NIL then
begin{ Неизвестный хост, отменяем попытку...}
LookUpEvent(FLookUpOp, '', FALSE);
FStatus := Failure;
if FOKToDisplayErrors then
raise ECsSocketError.create('Unable to
resolve ' + FpHostName);
Exit;
end;
InfoEvent('Host found');
FStatus := Success;
Move(FHost^.h_addr_list^, Fh_addr,
SizeOf(FHost^.h_addr_list^));
if FAddress = HostAddr then
begin
SetUpAddress;
FRemoteName
:= StrPas(inet_ntoa(FSockAddress.sin_addr));
end
else
if FAddress = IPAddr then
begin
FRemoteName := StrPas(FHost^.h_name);
InfoEvent('Host found...');
end;
case FLookUpOp of
resHostName : LookUpEvent(resHostName,
FRemoteName, TRUE);
resIPAddress : LookUpEvent(resIPAddress,
FRemoteName, TRUE);
end;// case
end;
end;
Затем метод SetRemoteHostName с помощью функции inet_addr проверяет, не содержит ли исходная строка числового IP-адреса. Если не содержит, метод предполагает, что в ней находится имя хоста, и вызывает функцию gethostbyname для преобразования его в IP-адрес. Если имя хоста отсутствует в локальном файле хостов, gethostbyname ищет имя в удаленном файле хостов, хранящемся в сети. Если имя не найдено, процесс поиска прекращает работу по тайм-ауту и присваивает protected-свойству FHost (которое представляет собой указатель на структуру pHostent) значение NIL. Затем SetRemoteHostName вызывает обработчик события LookUpEvent, чтобы сообщить о неудачном завершении просмотра, присваивает флагу FStatus значение Failure и возвращает управление вызывающему приложению. При удачном завершении поиска функция gethostbyname возвращает указатель на FHost, где содержится найденный адрес. Наконец, SetRemoteHostName возвращает IP-адрес в виде строки Паскаля, для чего используется следующий оператор: FRemoteName := StrPas(inet_ntoa(FSockAddress.sin_addr)); Функция inet_itoa переводит возвращаемый IP-адрес в строку с нуль-терминатором, а функция StrPas завершает преобразование в строку Паскаля. Адресная информация сокета размещается в поле FSockAddress, откуда она позднее извлекается для установки соединения с хостом. Полученный в результате поиска IP-адрес помещается в текстовое поле edIPName (см. рис. 5.4). Для этого RESOLVER32 использует обработчик события OnLookUp, который вызывается внутри процедуры LookUpEvent. В листинге 5.8 показано, как это делается.
Рис. 5.4. RESOLVER32 после преобразования имени хоста Листинг 5.8. Метод TfrmMain.CsSocket1Lookup,
используемый
procedure TfrmMain.CsSocket1Lookup (Sender: TObject; LookUpOp: TLookUpOp; Value: String; Result : Boolean); begin btnResolve.Enabled := TRUE; btnAbortRes.Enabled := FALSE; Screen.Cursor := crDefault; if Result then begin pnStatus.Color := clLime; case LookUpOp of resHostName : begin edHostName.Text := Value; pnStatus.Caption := 'IP address resolved'; end; resIPAddress : begin edIpName.Text := Value; pnStatus.Caption := 'Host name resolved'; end; resService : begin edPortName.Text := Value; pnStatus.Caption := 'Service resolved'; end; resPort : begin edServiceName.Text := Value; pnStatus.Caption := 'Port number resolved'; end; resProto : begin edProtoNo.Text := Value; pnStatus.Caption := 'Protocol resolved'; end; resProtoNo : begin edProtoName.Text := Value; pnStatus.Caption := 'Protocol number resolved'; end; end;// case end else begin pnStatus.Color := clRed; case LookUpOp of resHostName : begin edHostName.Text := ''; pnStatus.Caption := 'IP address resolution failed.'; end; resIPAddress : begin edIpName.Text := ''; pnStatus.Caption := 'Host name resolution failed'; end; resService : begin edPortName.Text := ''; pnStatus.Caption := 'Service resolution failed'; end; resPort : begin edServiceName.Text := ''; pnStatus.Caption := 'Port number resolution failed.'; end; resProto : begin edProtoNo.Text := ''; pnStatus.Caption := 'Protocol resolution failed.'; end; resProtoNo : begin edProtoName.Text := ''; pnStatus.Caption := 'Protocol number resolution failed.'; end; end;// case end; end; Как тебя зовут?RESOLVER32 также умеет определять имя хоста по его числовому IP-адресу. Для этого следует ввести адрес в текстовом поле edIPName (см. рис. 5.5). При нажатии кнопки Resolve программа передает адресную строку из edIPName.Text методу SetRemoteHostName через свойство Hostname.Метод SetRemoteHostName, как и ранее, с помощью функции inet_addr проверяет, является ли строка корректным IP-адресом. Кроме того, перед вызовом этой функции метод присваивает указателю P адрес переменной IPAddress1, используемый функцией gethostbyaddr в качестве параметра.
Рис. 5.5. IP-адрес готов к преобразованию Если inet_addr возвращает результат, отличный от INADDR_NONE (то есть строка представляет собой корректный числовой IP-адрес), SetRemoteHostName вызывает gethostbyaddr. Данный вызов, как и обращение к gethostbyname, может выполняться в режиме блокировки. Если вызов gethostbyaddr заканчивается успешно, он возвращает указатель на структуру pHostent. Если для заданного IP-адреса не нашлось соответствующего имени, FHost получает значение NIL, а SetRemoteHostName вызывает LookUpEvent, чтобы сообщить о неудачном поиске, устанавливает флаг FStatus и завершается. При успешном поиске свойство Hostname записывает полученное имя хоста обратно в текстовое поле edHostName через процедуру события LookUpEvent, предварительно преобразовав имя в строку Паскаля и присвоив его значение private-полю FRemoteName: FRemoteName := StrPas(FHost^.h_name); Асинхронное получение адресаБлокирующие функции gethostbyname и gethostbyaddr используются достаточно просто. С асинхронными версиями этих функций, WSAAsyncGetHostByName и WSA AsyncGetHostByAddr, дело обстоит несколько сложнее. Чтобы понять, как работает асинхронный процесс, мы посмотрим, как WSAAsyncGetHostByName вызывается в программе RESOLVER32.Прежде всего смените значение свойства Access с Blocking на NonBlocking — для этого следует установить переключатель NonBlocking в групповом поле TypeOfLookup (см. рис. 5.6). При нажатии кнопки Resolve имя передается свойству HostName.
Рис. 5.6. Переход от блокирующих функций к псевдоблокирующим Поскольку FAsync имеет значение NonBlocking, SetRemoteHostName передает его процедуре SetAsyncHostName (см. листинг 5.9). Листинг 5.9. Метод TCsSocket.SetAsyncHostName — преобразование имени хоста procedure TCsSocket.SetAsyncHostName
(ReqdHostName : String);
var
IPAddress : TInaddr;
SAddress: array[0..31] of char;
begin
FillChar(FAsyncBuff, SizeOf(FAsyncBuff), #0);
FAsyncRemoteName := ReqdHostName;
StrPcopy(SAddress, FAsyncRemoteName);
IPAddress.s_addr := inet_addr(SAddress);
if IPAddress.s_addr <> INADDR_NONE then
{ Это IP-адрес }
begin
FAddress := IPAddr;
FAsyncType := AsyncAddr;
if IPAddress.s_addr <> 0 then
FTaskHandle := WSAAsyncGetHostByAddr(FAsyncHWND,
ASYNC_EVENT,
pChar(@IPAddress), 4, PF_INET,
@FAsyncBuff[0], SizeOf(FAsyncBuff));
if FTaskHandle = 0 then
begin
if FNoOfBlockingTasks > 0 then
dec(FNoOfBlockingTasks);
FStatus := Failure;
ErrorEvent(FStatus,WSAErrorMsg);
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end else FStatus := Success;
end
else { Нет, это больше похоже на
символьное имя хоста }
begin
FAddress := HostAddr;
FAsyncType := AsyncName;
Inc(FNoOfBlockingTasks);
FTaskHandle := WSAAsyncGetHostByName
(FAsyncHWND, ASYNC_EVENT,
@FpHostName[0],
@FAsyncBuff[0],
MAXGETHOSTSTRUCT);
if FTaskHandle = 0 then
begin
FStatus := Failure;
if FNoOfBlockingTasks > 0 then
dec(FNoOfBlockingTasks);
ErrorEvent(FStatus,WSAErrorMsg);
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end else FStatus := Success;
end;
end;
SetAsyncHostName вызывает процедуру WSAAsyncGetHostByName с пятью важными аргументами. FASyncHWND — логический номер окна, которому асинхронная функция должна отправить сообщение о завершении операции просмотра. Он инициализируется в конструкторе TCsSocket.Create вызовом AllocateHWND с параметром-процедурой AsyncOperation. ASYNC_EVENT — константа события, используемая в WSAAsyncGetHostByName. Символьный массив FAsyncBuff содержит результат выполнения операции. Наконец, MAXGETHOSTSTRUCT — константа Winsock, определяющая максимальный размер буфера FAsyncBuff. Процедура WSAAsyncGet HostByName возвращает номер задачи в виде значения типа TaskHandle, которое затем присваивается полю FTaskHandle. WSAAsyncGetHostByName немедленно завершает работу с нулевым кодом, если вызов был неудачным; в случае удачного вызова она возвращает положительное число. Тем не менее отличное от 0 значение FTaskHandle свидетель ствует лишь об успешном вызове WSAAsyncGetHostByName, но не гарантирует успех последующей операции просмотра (которая продолжает выполняться в фоновом режиме). После завершения просмотра Winsock DLL инициирует событие ASYNC_EVENT, сообщая процедуре AsyncOperation о том, что она должна обработать сообщение ASYNC_EVENT (см. листинг 5.10). Листинг 5.10. Процедура AsyncOperation procedure TCsSocket.AsyncOperation(var Mess
: TMessage);
var
MsgErr : Word;
begin
if Mess.Msg = ASYNC_EVENT then
begin
MsgErr := WSAGetAsyncError(Mess.lparam);
if MsgErr <> 0 then
begin
FStatus := Failure;
ErrorEvent(FStatus,WSAErrorMsg);
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end
else
begin
FStatus := Success;
InfoEvent('WSAAsync operation succeeded!');
case FAsyncType of
AsyncName,
AsyncAddr : begin
FHost := pHostent(@FAsyncBuff);
if (FHost^.h_name = NIL) then
begin { Неизвестный хост, отменяем попытку... }
FStatus := Failure;
if FAsyncType = AsyncName then
LookUpEvent(resIPAddress,'',FALSE)
else
LookUpEvent(resHostName,'',FALSE);
if FOKToDisplayErrors then
raise ECsSocketError.create
('Unable to resolve host');
Exit;
end;
if length(StrPas(FHost^.h_name)) = 0 then
begin
InfoEvent('Host lookup failed!');
FStatus := Failure;
if FAsyncType = AsyncName then
LookUpEvent(resIPAddress,'',FALSE)
else
LookUpEvent(resHostName,'',FALSE);
if FOKToDisplayErrors then
raise ECsSocketError.create
('Unknown host');
Exit;
end;
case FAddress of
IPAddr : begin
Move(FHost^.h_addr_list^, Fh_addr,
SizeOf(FHost^.h_addr_list^));
FAsyncRemoteName :=
StrPas(FHost^.h_name);
LookUpEvent(resHostName,
FAsyncRemoteName, TRUE);
end;
HostAddr : begin
Move(FHost^.h_addr_list^, Fh_addr,
SizeOf(FHost^.h_addr_list^));
SetUpAddress;
FAsyncRemoteName:=
StrPas(inet_ntoa(FSockAddress.
sin_addr));
LookUpEvent(resIPAddress,FAsyncRemoteName,
TRUE);
end;
end;{case}
end;
AsyncServ : begin
FServ := pServent(@FAsyncBuff);
if FServ^.s_name = NIL then
begin { Сервис недоступен }
FStatus := Failure;
LookUpEvent(resService,'',FALSE);
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end;
FAsyncPort := IntToStr(ntohs(FServ^.s_port));
LookUpEvent(resService, FAsyncPort, TRUE);
end;
AsyncPort : begin
FServ := pServent(@FAsyncBuff);
if FServ^.s_name = NIL then
begin { Сервис недоступен }
FStatus := Failure;
LookUpEvent(resPort,'',FALSE);
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end;
FAsyncService := StrPas(FServ^.s_name);
LookUpEvent(resPort, FAsyncService, TRUE);
end;
AsyncProtoName : begin
FProto := pProtoEnt(@FAsyncBuff);
if FProto^.p_name = NIL then
begin
FStatus := Failure;
LookUpEvent(resProto,'',FALSE);
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end;
FAsyncProtoNo := IntToStr(FProto^.p_proto);
LookUpEvent(resProto, FAsyncProtoNo, TRUE);
end;
AsyncProtoNumber : begin
FProto := pProtoEnt(@FAsyncBuff);
if FProto^.p_name = NIL then
begin
FStatus := Failure;
LookUpEvent(resProtoNo,'',FALSE);
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
Exit;
end;
FAsyncProtocol := StrPas(FProto^.p_name);
LookUpEvent(resProtoNo, FAsyncProtocol, TRUE);
end;
end;
if FNoOfBlockingTasks > 0 then
dec(FNoOfBlockingTasks);
end;
end;
end;
Функция WSAGetAsyncError проверяет значение переменной Mess. Если переменная сообщает о происшедшей ошибке, AsyncOperation вызывает ErrorEvent для вывода причины ошибки из WSAErrorMsg, а затем завершает работу, присваивая флагу FStatus значение Failure. Если ошибки не было, мы анализируем переменную FAsyncType. При вызове WSAAsyncGetHostByName мы присваиваем FAsyncType значение AsyncName, чтобы установить признак асинхронного поиска имени. Затем оператор case переходит к фрагменту, соответствующему значению AsyncName. Здесь символьный массив FAsyncBuff, содержащий результаты поиска, преобразуется в структуру pHostent и сохраняется в поле FHost. SetUpAddress читает адресную структуру найденного хоста и получает искомый IP-адрес. Наконец, процедура LookUpEvent возвращает IP-адрес программе RESOLVER32. Кто находится по этому адресу?Мы поближе познакомимся с асинхронным режимом на примере определения имени хоста по Internet-адресу функцией WSAAsyncGetHostByAddr. Чтобы воспользоваться функцией в приложении RESOLVER32, установите переключатель NonBlocking в групповом поле TypeOfLookUp и введите Internet-адрес в текстовом поле edIPName.Как и ранее, имя передается свойству HostName для обработки с помощью метода TCsSocket.SetAsyncHostName. Если переданное имя является пустой строкой, SetRemoteHostName присваивает флагу FStatus значение Failure и вызывает процедуру ErrorEvent, которая посылает сообщение об ошибке. Затем вызывается другой обработчик ошибок, LookUpEvent, который сообщает RESOLVER32 о неудачной попытке поиска и завершается. Убедившись, что FRemoteName не является пустой строкой, мы вызываем метод SetAsyncHostName, в котором функция inet_addr определяет, соответствует ли строка символьному имени или IP-адресу с точками-разделителями. Код возврата, отличный от INADDR_NONE, свидетельствует о том, что строка соответствует формату IP-адреса. Затем эта строка передается WSAAsyncGetHostByAddr, чтобы получить информацию о хосте для данного Internet-адреса. При успешном вызове WSAAsyncGetHostByAddr свойству FTaskHandle присваивается положительное число, но это вовсе не гарантирует, что после завершения WSAAsyncGetHostByAddr также будет получен верный результат. Метод возвращает управление приложению RESOLVER32, и поиск продолжается в фоновом режиме. Winsock DLL сообщает CsSocket о завершении поиска, инициируя событие ASYNC_EVENT. При этом вызывается метод TCsSocket.AsyncOperation, в котором просматривается значение переменной Mess. Если Mess содержит информацию об ошибке, метод AsyncOperation вызывает ErrorEvent, чтобы выдать сообщение о причине ошибки из WSAErrormsg, присваивает флагу FStatus значение Failure и завершается. Если переменная Mess не содержит сведений об ошибках, оператор case анализирует поле FAsyncType. В данном случае FAsyncType имеет значение AsyncAddr, поэтому в результате выполняется фрагмент кода, уже знакомый нам по случаю AsyncName. Затем после анализа FAddress выполняется фрагмент, обрабатывающий результат WSAAsyncGetHostByAddr. Значение FAddress автоматически устанавливается методом SetAsyncHostName в соответствии с результатом операции inet_addr. Другими словами, FAddress получает значение IPAddr, если будет найден IP-адрес с точками-разделителями, и HostAddr в противном случае (то есть для символьного имени). Затем имя хоста извлекается с помощью следующего фрагмента кода: Move(FHost^.h_addr_list^, Fh_addr, SizeOf(FHost^.h_addr_list^));
Результат передается приложению через обработчик события OnLookUp. Отмена операции WSAAsyncПоскольку асинхронные операции нарушают нормальную логику работы приложения, отменить их оказывается не так просто. Для прерывания асинхронных операций в Winsock API предусмотрена специальная функция WSACancelAsyncRequest (тем не менее обратите внимание — эта функция не может отменять операции, запущенные функцией WSAAsyncSelect). В листинге 5.11 показана функция WSACancelAsyncRequest в «оболочке» метода CancelAsyncOperation.Листинг 5.11. Метод TCsSocket.CancelAsyncOperation
— отмена
procedure TCsSocket.CancelAsyncOperation
(CancelOP : Boolean);
begin
if WSACancelAsyncRequest(THandle(FTaskHandle))
= SOCKET_ERROR then
begin
FStatus := Failure;
ErrorEvent(FStatus,WSAErrorMsg);
if FOKToDisplayErrors then
raise ECsSocketError.create(WSAErrorMsg);
end
else
begin
FStatus := Success;
InfoEvent('WSAAsync lookup cancelled!');
end;
end;
Однако метод CancelAsyncOperation определен в секции protected и поэтому недоступен приложению RESOLVER32. Но как же RESOLVER32 отменяет WSAAsyncGetHostByName или WSAAsyncGetHostByAddr? Обращаясь к методу CancelAsyncOperation через логическое public-свойство CancelAsyncOp. Листинг 5.12 показывает, что происходит при нажатии кнопки Abort в групповом поле gbnameRes приложения RESOLVER32. Поскольку функция вызывается в псевдоблокирующем режиме, мы присваиваем CancelAsyncOp значение True. Тем самым мы приказываем CsSocket через CancelAsyncOperation вызвать WSACancelAsyncRequest и таким образом прервать асинхронную операцию. Обратите внимание — при вызове блокирующих функций кнопка Abort становится недоступной. Листинг 5.12. Метод TFrmMain.AbortAsyncHostBtnClick
— отмена
procedure TfrmMain.btnAbortResClick(Sender : TObject); begin CsSocket1.CancelAsyncOp := TRUE; pnStatus.Color := clYellow; pnStatus.Caption := 'Operation aborted'; btnAbortRes.Enabled := FALSE; btnResolve.Enabled := TRUE; Screen.Cursor := crDefault; end; Преобразование портов и сервисовПреобразование имен сервисов и портов, как и символьных имен с IP-адресами, может выполняться в блокирующем или псевдоблокирующем (асинхронном) режиме. В блокирующем режиме для этого используются функции getservbyname и getservbyport.Поиск порта, связанного с определенным сервисом, во многом похож на процесс получения имени хоста. Например, если мы захотим определить номер порта для FTP, следует ввести строку FTP в текстовом поле edServiceName и затем присвоить ее свойству WSService. При этом имя сервиса передается методу TCsSocket.SetServiceName для преобразования. После копирования строки Паскаля ReqdServiceName в строку с нуль-терминатором ServName с помощью функции StrPCopy в строку протокола заносится текст «TCP», один из обязательных параметров для getservbyname. По умолчанию используется протокол TCP, а это означает, что при попытке определить номер порта для сервиса, основанного на другом протоколе (обычно UDP), функция getservbyname вернет указатель NIL. Некоторые сервисы используют либо TCP, либо UDP, либо оба протокола сразу. Чтобы определить, доступен ли сервис для протокола UDP, следует установить переключатель UDP в групповом поле rgProtocol и затем нажать кнопку Resolve. Метод SetServiceName вызывает функцию getservbyname для получения соответствующего номера порта. Если сервис найден, функция getservbyname присваивает полю FServ указатель на структуру типа pServent. После этого структура будет содержать номер порта. В противном случае функция возвращает пустой указатель; тогда метод вызывает ErrorEvent, чтобы вывести причину ошибки из WSAErrorMsg, присваивает флагу FStatus значение Failure и возвращает управление вызывающему приложению. Номер порта определяется с помощью следующего оператора: FPortName := IntToStr(LongInt(abs(ntohs(FServ^.s_port)))); На рис. 5.7 показано, как выглядит результат преобразования. Поиск сервисаПроцесс преобразования номера порта в соответствующий ему тип сервиса почти не отличается от только что описанного, за исключением того, что на этот раз используется блокирующая функция Winsock getservbyport. Вместо того чтобы подробно рассматривать весь процесс, мы лучше рассмотрим WSAAsyncGetServByPort, асинхронную версию getservbyport.Чтобы воспользоваться асинхронным режимом, необходимо сначала изменить свойство Access установкой переключателя Non-blocking в групповом поле TypeOfLookup. Затем введите имя порта в текстовом поле edPortName и нажмите кнопку Resolve.
Рис. 5.7. Результат преобразования имени сервиса Когда мы присваиваем номер порта, хранящийся в edPortName.Text, свойству WSPort, он передается методу TCsSocket.SetPortName в качестве параметра ReqdPortName. Убедившись в том, что строка номера порта не пуста, SetPortName вызывает SetAsyncPort. Метод SetAsyncPort копирует номер порта в поле FPortNo — строку с нуль-терминатором. Затем вызов WSAAsyncGetServByPort извлекает номер порта. Результат этого вызова сохраняется в поле FTaskHandle. Если значение FTaskHandle равно нулю, вызов закончился неудачей. В противном случае он прошел успешно, и тогда SetAsyncPort возвращает управление приложению, оставляя процесс просмотра выполняться в фоновом режиме. После его завершения посредством сообщения от Winsock DLL инициируется AsyncOperation. Переменная Mess проверяется на предмет ошибки. Если ошибки не было, метод возвращает номер порта. В противном случае он вызывает ErrorEvent, чтобы вывести причину ошибки, присваивает флагу FStatus значение Failure и возвращает управление приложению. Преобразование протоколовПолучение имени и номера протокола требуется несколько реже других функций преобразования, но для полноты картины CsSocket поддерживает и их. Эти преобразования выполняются функциями API getprotobyname, getprotobyno, WSAAsyncGetProtoByName и WSAAsyncGetProtoByNo. По своей структуре и использованию эти функции похожи на те, что рассматривались выше.Использование свойства TagНаверное, вас давно интересует вопрос — как RESOLVER32 определяет, какое из введенных значений необходимо обработать? Все очень просто: у каждого элемента есть свойство Tag, по нему можно выделить текстовое поле, которое получает строку для преобразования. Свойствам Tag текстовых полей назначаются целые числа, начиная с 1 для текстового поля edIPName и заканчивая 6 для edProtoNo. Затем обработчики событий OnClick этих текстовых полей используются для изменения свойства Tag формы. Следующий фрагмент показывает, как это делается, на примере текстового поля edIPName1:procedure TfrmMain.edIPNameClick(Sender:
TObject);
При нажатии кнопки Resolve RESOLVER32 анализирует frmMain.tag в операторе case и присваивает значение нужному свойству. В листинге 5.13 показано, как это делается. Листинг 5.13. Использование свойства tag для определения того, какое из введенных значений следует преобразовать procedure TfrmMain.btnResolveClick(Sender: TObject);
begin
btnResolve.Enabled := FALSE;
Screen.Cursor := crHourGlass;
if CsSocket1.Access = NonBlocking then
btnAbortRes.Enabled := TRUE;
pnStatus.Color := clBtnFace;
pnStatus.UpDate;
case tag of
begin
edHostName.Text := '';
edHostName.Update;
pnStatus.Caption := Concat('Resolving ',edIPName.Text);
pnStatus.UpDate;
CsSocket1.HostName := edIPName.Text;
end;
begin
edIPName.Text := '';
edIPName.UpDate;
pnStatus.Caption := Concat('Resolving ',edHostName.Text);
pnStatus.UpDate;
CsSocket1.HostName := edHostName.Text
end;
begin
edPortName.Text := '';
edPortName.UpDate;
pnStatus.Caption := Concat('Resolving ', edServiceName.Text);
pnStatus.UpDate;
CsSocket1.WSService := edServiceName.Text
end;
begin
edServiceName.Text := '';
edServiceName.UpDate;
pnStatus.Caption := Concat('Resolving ',
edServiceName.Text);
pnStatus.UpDate;
CsSocket1.WSPort := edPortName.Text
end;
begin
edProtoNo.Text := '';
edProtoNo.UpDate;
pnStatus.Caption := 'Resolving protocol name.';
pnStatus.UpDate;
CsSocket1.WSProtoName := edProtoName.Text;
end;
begin
edProtoName.Text := '';
edProtoName.UpDate;
pnStatus.Caption := 'Resolving
protocol number.';
pnStatus.UpDate;
CsSocket1.WSProtoNo := edProtoNo.Text;
end;
end;
end;
Стоит ли блокировать?Если ваше приложение использует локальную систему DNS и целевой хост находится в локальной сети, использование блокирующих функций существенно уменьшает объем накладных расходов. Тем не менее, если приложение подключается к хостам за пределами локальной сети и при этом часто используется удаленная DNS, асинхронные вызовы обладают явным преимуществом — во время ожидания ваше приложение может выполнять полезную работу.CsSocket не претендует на звание идеального
компонента Winsock, и все же он образует неплохую основу для построения
других Internet-компонентов. Теперь, после знакомства с CsSocket, мы перейдем
к построению более интересных приложений, в которых участвуют дочерние
компоненты, созданные на базе CsSocket. В следующей
главе мы построим клиентское приложение FTP. Все дальнейшее оставляю
вашему воображению.
|
|
|