翻译|其它|编辑:郝浩|2005-02-25 09:56:00.000|阅读 1978 次
概述:
# 界面/图表报表/文档/IDE等千款热门软控件火热销售中 >>
2.我们创建完ServerSocket后,下步要做什么呢,当然是指定端口啦,然后Open开始监视啦,这个过程会触发一些事件,我们正好看看这些事件是怎么样发生的。
由于这一次操作是非阻塞方式的,所以ServerType默认为stNonBlocking,我们开始设置Port,这个属性的声明在哪里,这个属性一直到跟踪祖先类AbstractSocket中去,因为无论客户端和服务端都是要设置端口的,所以理所当然要封装到最高层的类去了,以让它的子类拥有这些属性:
property Port: Integer read FPort write SetPort;
再看看SetPort方法:
(21)
procedure TAbstractSocket.SetPort(Value: Integer);
begin
if FPort <> Value then
begin
if not (csLoading in ComponentState) and FActive then
raise ESocketError.CreateRes(@sCantChangeWhileActive);
FPort := Value;
end;
end;
if not (csLoading in ComponentState) and FActive then
raise ESocketError.CreateRes(@sCantChangeWhileActive);
这一句是防止你在运行时改变端口,最后才将值赋给FPort。
好了,设置后Port后,就要Open了,开始监视客户端了。
监视有两个方式,一种是直接设Active属性为True,一种直接调用Open方法。
其中Open方法如下:
procedure TAbstractSocket.Open;
begin
Active := True;
end;
它还是用到了属性Active,所以我们可以集中来讨论Active属性。
property Active: Boolean read FActive write SetActive;
看看SetActive:
(22)
procedure TAbstractSocket.SetActive(Value: Boolean);
begin
if Value <> FActive then
begin
if (csDesigning in ComponentState) or (csLoading in ComponentState) then
FActive := Value;
if not (csLoading in ComponentState) then
DoActivate(Value);
end;
end;
我们可以这样认为,当设计时就直接设FActive,当运行时,就调用DoActivate(Value);
而TAbstractSocket覆盖了Loaded方法,则当窗体开始运行,从Form文件中开始流入时,也调用了DoActive方法。
再看DoActive了:
(221)
procedure DoActivate(Value: Boolean); virtual; abstract;又是一种抽象方法,得到它的子类去看了,在它的子类TCustomServerSocket找到这个方法:
procedure TCustomServerSocket.DoActivate(Value: Boolean);
begin
if (Value <> FServerSocket.Connected) and not (csDesigning in ComponentState)
then
begin
if FServerSocket.Connected then
FServerSocket.Disconnect(FServerSocket.SocketHandle)
else FServerSocket.Listen(FHost, FAddress, FService, FPort, SOMAXCONN);
end;
end;
可以这样理解,不在设计时,当Value不等它的成员FServerSocket.Connected时(我们可先认为这个表示当前的监听状态),而如果此时FServerSocket.Connected为True,则表示Value为False,,那么这时要断开连接,调用FServerSocket.Disconnect(FServerSocket.SocketHandle)
很多时候ServerSocket都是调用它的成员FServerSocket来完成操作的,这一点我已经看到了,下面还有更多这样的情况。否则,则Value为True,调用
FServerSocket.Listen(FHost, FAddress, FService, FPort, SOMAXCONN);来开始进行监视。好的,现在我们就要打开FServerSocket的源码,看看它是怎么样完成断开连接和开始连接的了。
先看开始监听的:
(2211)
procedure TServerWinSocket.Listen(var Name, Address, Service: string; Port:
Word;
QueueSize: Integer);
begin
inherited Listen(Name, Address, Service, Port, QueueSize, ServerType =
stThreadBlocking);
if FConnected and (ServerType = stThreadBlocking) then
FServerAcceptThread := TServerAcceptThread.Create(False, Self);
end;
这里调用其父类的Listen方法,如果它是阻塞方式的,则还要生成一个线程类,由于我用的非阻塞方式,所以暂不去理它,看看他的父类的Listen方法:
(22111)
procedure TCustomWinSocket.Listen(const Name, Address, Service: string; Port:
Word;
QueueSize: Integer; Block: Boolean);
begin
if FConnected then raise ESocketError.CreateRes(@sCannotListenOnOpen);
FSocket := socket(PF_INET, SOCK_STREAM, IPPROTO_IP);
if FSocket = INVALID_SOCKET then raise ESocketError.CreateRes(@sCannotCreateSocket);
try
Event(Self, seLookUp);
if Block then
begin
FAddr := InitSocket(Name, Address, Service, Port, False);
DoListen(QueueSize);
end else
AsyncInitSocket(Name, Address, Service, Port, QueueSize, False);
except
Disconnect(FSocket);
raise;
end;
end;
这里第一句重要函数出现了:
FSocket := socket(PF_INET, SOCK_STREAM, IPPROTO_IP);
记得上面Fsockeet曾被赋过值吗,是INVALID_SOCKET;,而这里终于被替回了一个可用的套接字了。
第二个重要的函数:Event(Self, seLookUp);可以先猜测这个方法最终于会调用事件指针,等一下再看。
接下来判断Block的值,回头看看这一句:
inherited Listen(Name, Address, Service, Port, QueueSize, ServerType =
stThreadBlocking);
如果为阻塞方式,则为True,否则为False,非阻塞为这时为False,调用下方法:
AsyncInitSocket(Name, Address, Service, Port, QueueSize, False);
好了,再看一下Even吧:
(221111)
procedure TCustomWinSocket.Event(Socket: TCustomWinSocket; SocketEvent:
TSocketEvent);
begin
if Assigned(FOnSocketEvent) then FOnSocketEvent(Self, Socket, SocketEvent);
end;
不出所料,调用事件指针了,这时ServerSocket的事件就会触发了,回过头去看看,
seLookup: if Assigned(FOnLookup) then FOnLookup(Self, Socket);
这里CustomSocket的OnLookup事件发生,但ServerSocket并没有显化它,ClientSocket就有这个事件。
按我们所设的方式,接下来要调用如下函数了(另外一些方法以后再说):
AsyncInitSocket(Name, Address, Service, Port, QueueSize, False);
这函数很大,但所用到的技巧也非常精彩,得好好分析它:
(221112)
procedure TCustomWinSocket.AsyncInitSocket(const Name, Address,
Service: string; Port: Word; QueueSize: Integer; Client: Boolean);
var
ErrorCode: Integer;
begin
try
case FLookupState of
lsIdle:
begin
if not Client then
begin
FLookupState := lsLookupAddress;
FAddr.sin_addr.S_addr := INADDR_ANY;
//下面的情况到客户端时才会用到
end else if Name <> '' then
begin
if FGetHostData = nil then
FGetHostData := AllocMem(MAXGETHOSTSTRUCT);
FLookupHandle := WSAAsyncGetHostByName(Handle, CM_LOOKUPCOMPLETE,
PChar(Name), FGetHostData, MAXGETHOSTSTRUCT);
CheckSocketResult(Ord(FLookupHandle = 0), 'WSAASyncGetHostByName');
FService := Service;
FPort := Port;
FQueueSize := QueueSize;
FClient := Client;
FLookupState := lsLookupAddress;
Exit;
end else if Address <> '' then
begin
FLookupState := lsLookupAddress;
FAddr.sin_addr.S_addr := inet_addr(PChar(Address));
end else
begin
ErrorCode := 1110;
Error(Self, eeLookup, ErrorCode);
Disconnect(FSocket);
if ErrorCode <> 0 then
raise ESocketError.CreateRes(@sNoAddress);
Exit;
end;
end;
lsLookupAddress:
begin
if Service <> '' then
begin
if FGetHostData = nil then
FGetHostData := AllocMem(MAXGETHOSTSTRUCT);
FLookupHandle := WSAASyncGetServByName(Handle, CM_LOOKUPCOMPLETE,
PChar(Service), 'tcp' , FGetHostData, MAXGETHOSTSTRUCT);
CheckSocketResult(Ord(FLookupHandle = 0), 'WSAASyncGetServByName');
FLookupState := lsLookupService;
Exit;
end else
begin
FLookupState := lsLookupService;
FAddr.sin_port := htons(Port);
end;
end;
lsLookupService:
begin
FLookupState := lsIdle;
if Client then
DoOpen
else DoListen(QueueSize);
end;
end;
if FLookupState <> lsIdle then
ASyncInitSocket(Name, Address, Service, Port, QueueSize, Client);//递归
except
Disconnect(FSocket);
raise;
end;
end;
它从判断FLookupState的状态来执行相应的操作,而最后每一步都会执行到,它是怎么做到的呢,答案就是最后的这两句:
if FLookupState <> lsIdle then
ASyncInitSocket(Name, Address, Service, Port, QueueSize, Client);
我们先从头来看吧:
首先,FLookupState的状态行为lsIdle,则执行isIdle这一大块,判断Client的值,这里是服务端,所以应该为False(从调用该方法的函数也可得知),所以只执行了这一块:
FLookupState := lsLookupAddress;
FAddr.sin_addr.S_addr := INADDR_ANY;
其余都是对于客户端的来作的,所以以后再讨论它(由此也可以知道该函数的重要性了)。
还记得INADDR_ANY的意义吗,它会使得WinSock自动加入正确的地址。
好,方法执行到最后的这几句:
if FLookupState <> lsIdle then
ASyncInitSocket(Name, Address, Service, Port, QueueSize, Client);
这时FLookupState已经为lsLookupAddress了,所以又会调用ASyncInitSocket方法,它就是这样递归调用本身,直到所有操作都完毕,这样的设计思维实在是太精巧了。
第一递归之后,它又执行了那一块呢,当然是lsLookupAddress:这一块啦,看看代码,知道它会先判断函数的参数Service是否为空,我们前面没有对该传递过来的这个参数并没有同值,所以为空,便执行了这两句:
FLookupState := lsLookupService;
FAddr.sin_port := htons(Port);
可知,它对结构的端口值了,又进行第二次递归,这里应该是执行lsLookupService:这一块,并调用了这个方法DoListen(QueueSize);开始监听:
FLookupState := lsIdle;
if Client then
DoOpen
else DoListen(QueueSize);
我们看到FLookupState已经又回到了IsIdle,所以就不再递归了。
最好,我们得来看看DoListen这个方法,这个大概就是万事具务,只欠东风的东风了,我们猜测它会在这里调用Bind和Listen等API,并触发Oolisten事件:
(2211121)
procedure TCustomWinSocket.DoListen(QueueSize: Integer);
begin
CheckSocketResult(bind(FSocket, FAddr, SizeOf(FAddr)), 'bind');
DoSetASyncStyles;
if QueueSize > SOMAXCONN then QueueSize := SOMAXCONN;
Event(Self, seListen);
CheckSocketResult(Winsock.listen(FSocket, QueueSize), 'listen');
FLookupState := lsIdle;
FConnected := True;
end;
哈,一切都明朗了,所有API都在这里调用了,事件也触发了。现在就等客户来连接了
只是还没有完,还有一个DoSetASyncStyles方法,坚持走下去吧,会到挑花园的:
(22111211)
procedure TCustomWinSocket.DoSetAsyncStyles;
var
Msg: Integer;
Wnd: HWnd;
Blocking: Longint;
begin
Msg := 0;
Wnd := 0;
if FAsyncStyles <> [] then
begin
Msg := CM_SOCKETMESSAGE;
Wnd := Handle;
end;
WSAAsyncSelect(FSocket, Wnd, Msg, Longint(Byte(FAsyncStyles)));
if FASyncStyles = [] then
begin
Blocking := 0;
ioctlsocket(FSocket, FIONBIO, Blocking);
end;
end;
有两个情况,当FAsyncStyles有元素时和没有元素时,从上面的代码中我们看到它是有元素的。则:
Msg := CM_SOCKETMESSAGE;
Wnd := Handle;
第一句应该是指针Socket消息了,用于与客户端的读写事件的触发的吧。而第二句,则我仔细看了,Handle是TCustomWinSocket的一个属性:
property Handle: HWnd read GetHandle;
再看看GetHandle方法
function TCustomWinSocket.GetHandle: HWnd;
begin
if FHandle = 0 then
FHandle := AllocateHwnd(WndProc);
Result := FHandle;
end;
我们记得上面并没有对FHandle进行赋值,所以它应该为0,则调用了AllocateHwnd,这个方法产生一个不可见的窗口用于接收消息,窗口过程就是WndProc,在CustomWinSocket有声明:
procedure TCustomWinSocket.WndProc(var Message: TMessage);
begin
try
Dispatch(Message);
except
if Assigned(ApplicationHandleException) then
ApplicationHandleException(Self);
end;
end;
可以知道,他调用Object的Dispatch(Message);进行消息分配,而我们看到类中有消息函数的声明:
procedure CMSocketMessage(var Message: TCMSocketMessage); message
CM_SOCKETMESSAGE;
当事件发生时就会调用这些消息处理函数了。而这些消息是从那里发生的呢,得回过头去看看DoSetAsyncStyles方法,其中有这一个SocketAPI:
WSAAsyncSelect(FSocket, Wnd, Msg, Longint(Byte(FAsyncStyles)));
就是通过它,当有网络消息发生的时候,才会触发上面事件的,而Longint(Byte(FAsyncStyles)
则指定允许触发哪些事件。(具体的还是看看MSDN的说明吧)
呼还没有完吗,其实差不多了,我们现在知道了当有客户端连接或读写,是通过什么方式来让服务端知道并触发相应的事件的,说到底还是用了WinSock的API,只不过Delphi用自己的事件处理方式将那些Socket异步消息转化成自己的事件了,这个过程是非常精彩的,很值得我们学习。
但现在只剩下最一步了,TCustomWinSocket消息处理方法中,如何转化为事件,并传递到ServerSocket去的呢,答案只有在源代码中找了:
procedure TCustomWinSocket.CMSocketMessage(var Message: TCMSocketMessage);
function CheckError: Boolean;
var
ErrorEvent: TErrorEvent;
ErrorCode: Integer;
begin
if Message.SelectError <> 0 then
begin
Result := False;
ErrorCode := Message.SelectError;
case Message.SelectEvent of
FD_CONNECT: ErrorEvent := eeConnect;
FD_CLOSE: ErrorEvent := eeDisconnect;
FD_READ: ErrorEvent := eeReceive;
FD_WRITE: ErrorEvent := eeSend;
FD_ACCEPT: ErrorEvent := eeAccept;
else
ErrorEvent := eeGeneral;
end;
Error(Self, ErrorEvent, ErrorCode);
if ErrorCode <> 0 then
raise ESocketError.CreateResFmt(@sASyncSocketError, [ErrorCode]);
end else Result := True;
end;
begin
with Message do
if CheckError then
case SelectEvent of
FD_CONNECT: Connect(Socket);
FD_CLOSE: Disconnect(Socket);
FD_READ: Read(Socket);
FD_WRITE: Write(Socket);
FD_ACCEPT: Accept(Socket);//这个很特殊
end;
end;
呵,又是一个大函数,不过理解起来倒是容易多了,先检查有没有错误,如果有就调用Error(Self, ErrorEvent, ErrorCode);如果没有,就根据相应标识,调用相就的函数,其实我们已经可以确定,像Read这些内部一定会调用Event,再由Event调用
FOnSocketEvent(Self, Socket, SocketEvent);,这样才能使得ServerSocket这些外部的类获得事件(上面已经说到它们是怎么把事件关联起来的了)。
而源代码中确实也是这样的,这里只列出一个read,其他的一样:
procedure TCustomWinSocket.Read(Socket: TSocket);
begin
if (FSocket = INVALID_SOCKET) or (Socket <> FSocket) then Exit;
Event(Self, seRead);
end;
procedure TCustomWinSocket.Event(Socket: TCustomWinSocket; SocketEvent:
TSocketEvent);
begin
if Assigned(FOnSocketEvent) then FOnSocketEvent(Self, Socket, SocketEvent);
end;
完了吧,可惜还没有,回到上面的函数去,发现还有一个方法没有分析:
FServerSocket.Disconnect(FServerSocket.SocketHandle)可是按我们流程,还不会调用到它,所以这里暂不提它。
好了,第二步就这样结束了,再说下去,我自己也晕了。不过我们知道了这一步所完成的任务:连接一个监听的套接字,并设定好了事件方法指针,在适当的时机会调用适当的事件处理函数。真的得谢谢Delphi为我们做了这么多的事,使我们在使用的时候感觉到是如此的简单。
本站文章除注明转载外,均为本站原创或翻译。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,如果存在内容上的异议请邮件反馈至chenjj@evget.com