ServerSocket,ClientSocket控件源码阅读笔记(三)

翻译|其它|编辑:郝浩|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


为你推荐

  • 推荐视频
  • 推荐活动
  • 推荐产品
  • 推荐文章
  • 慧都慧问
扫码咨询


添加微信 立即咨询

电话咨询

客服热线
023-68661681

TOP