分享
 
 
 

ServerSocket,ClientSocket控件源码阅读笔记

王朝delphi·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

ServerSocket,ClientSocket控件源码阅读笔记

本篇将通过一次Socket通信操作来对ServerSocket和ClientSocket这两个控件的源码进行一次阅读,希望能理出一个脉络来,以供大家参考。其实说得确切一点,应该是对Scktcomp这个单元进行解读,但由于这个单元的代码太多了,所以不可能面面俱到,我试图以非阻塞式的通信来一步步说明它们是怎么样封装WinSock的API的,至于阻塞式的,在ServerSocket和ClientSocket并不常用,所以这一篇就不打算说了,可能源码中会有一些忽略掉,以后有时间来补一篇阻塞式的阅读吧。虽然现在Delphi已经用Indy控件替换了SS和CS等网络控件,但毕竟这两个控件是对Socket函数进行封装,学习它怎么样封装也好吧。

在这之前,须对WinSock有一个大概的了解,但也仅止大概,对Socket编程有一个总体的掌握行了,毕竟那不是我的能力所及。想要系统一点的学习Socket,可以去网上下WinSocket的中文文档以及参考MSDN。

Socket中分服务端和客户端,它们是怎么样互交的呢,请下面图例:

图3-1就是我们所说的UDP协议,而图3-2就是TCP协议,如果是用纯API编写,大概程序流程就是那上面那样子,这里将说的是图3-2的面向连接的Socket的应用,因为SS和CS就是封装这种形式的Socket编程的。

以下是说明这个过程中各个函数的声明

在开始使用WinSock的Api的时候,必须加载WinSock Dll的相应版本,这时用到的函数是:

int WSAStartup (

WORD wVersionRequested,

LPWSADATA lpWSAData

);

wVersionRequested指定用到的WinSock的最低版本,高字节指定副版本,低字节指定主版本,关于WinSock的版本,详见MSDN。

lpWSAData结构类型,系统把加载进去的版本信息添加到这个结构中。

该函数成功返回0,不成功则返回几个错误代码之一

这个函数和WSACleanup对应,查看MSDN

从MSDN列出一个例子:

WORD wVersionRequested;

WSADATA wsaData;

int err;

wVersionRequested = MAKEWORD( 2, 2 );

err = WSAStartup( wVersionRequested, &wsaData );

if ( err != 0 ) {

/* Tell the user that we could not find a usable */

/* WinSock DLL. */

return;

}

调用上面的函数之后,就可以用下面的函数来创建一个Socket了,Socket是WinSock定义的数据类型(整数),相当于句柄,用于标识系统中的每一个Socket连接

SOCKET socket (

int af,

int type,

int protocol

);

Af协议标志,Internet协议族的标志是PINET。

Type 协议类型志,SOCISTREAM表明是面向连接的字节流类型,SOCIDGRAM表明是面向无连接的数据报类型。

Protocol Socket采用的协议类型,如果采用IPPROTO_TCP常数就是采用了TCP协议。

如果调用失败,会返回INVALID_SOCKET值,正常的Socket取值范围是1~INVALID_SOCKET-1;

下面的函数将Socket与特定的主机进行绑定:

int bind (

SOCKET s,

const struct sockaddr FAR* name,

int namelen

);

S就是一个Socket,由Socket()函数返回得到的。

Name是sockaddr结构的变量

Namelen是Name结构的长度。

如果函数调用成功,将返回0,如果不成功,将返回SOCKET_ERROR,可以从WSAGetLastError.函数获得错误代码。

现在说sockaddr这个结构,在Winsock中其实有两个功能相似的结构,它们是:

struct sockaddr {

u_short sa_family;

char sa_data[14];

};

struct sockaddr_in {

short sin_family;

u_short sin_port;

struct in_addr sin_addr;

char sin_zero[8];

};

这两个结构在Delphi中被声明为一个变体记录,这两个指定通信的本地主机地址,本地协议端口,另外还有通信过程中使用的协议类型。

其中:sin_family规定了哪个协议用来实现套按字连接。WinSock必须设置常数AF_INET

sin_port;:WinSock应用程序使用的端口号

sin_addr:这个是32位IP地址

sin_zero[8];这个保留,没有使用。

Server端必须调用Bind()函数,设计时可以将地址设定为INADDR_ANY,这样WinSock会自动加入机器正确的地址.

以下是客户端向服务端连接时调用的函数

int connect (

SOCKET s,

const struct sockaddr FAR* name,

int namelen

);

函数里的参数和bind()一个,不多说了,函数成功时返回0,否则返回SOCKET_ERROR。

服务端在Bind之后,调用下面函数进行监视。

int listen (

SOCKET s,

int backlog

);

其中backlog是可以建立的最大连接数,如果值设为SOMAXCONN,将由Socket的服务提供商设定一个合理的值,这个值不确定。

函数成功时返回0,否则返回SOCKET_ERROR。

当客户端连接服务端,服务端调用下面函数接收客户的请求,并向客户机发送应答信息

SOCKET accept (

SOCKET s,

struct sockaddr FAR* addr,

int FAR* addrlen

);

其中中addr是用来保存客户机地址信息的指针

Addrlen是addr的长度一般是Sizeof(addr)

如果函数成功,则返回一个Socket,这个Socket才是与客户实际通信的套接字,原来的那个Socket继续监视其他客户端的连接

以下几个是用个服务机和客户机通信的函数,

int send (SOCKET s,const char FAR * buf, int len,int flags );

在面向连接的情况下发送数据

int recv (SOCKET s, char FAR* buf, int len, int flags );

在面向连接的情况下接收数据

另外还有sendto和recvfrom用于无连接情况,具体查MSDN

其中Buf是发送或接收的缓冲区,Len是缓冲区的大小,Flags是网络呼叫产生方式标志,值可以为0,MSG_DONTROUTE或 MSG_OOB用途具体看MSDN。

通信完毕后,需要关闭Socket,函数声明如下:

int closesocket (

SOCKET s

);

在程序的最后,需要调用下面的函数,结束WinSock DLL

int WSACleanup (void);

函数成功时返回0,否则返回SOCKET_ERROR。

下面开始TServerSockett和TClientSocket的原代码阅读:

不过在分析之前,先要明白,对于WinSock的封装其实是由ScktComp单元的几个类一起完成的,它们的关系是:

TServerSocket继承于TCustomServerSocket

TCustomServerSocket继承于TCustomSocket

TCustomSocket继承于TAbstractSocket而TAbstractSockeet的上级则是TComponent了

TClientSocket继承于TCustomSocket

另外还有几个类:

TServerWinSocket和TClientWinSocket以及TServerClientWinSocket继承TCustomSocket

它们才是真正封装Socket的类。作为上面提到的类的成员存在

此外还有几个用于阻塞式传输的类。这里就忽略不讲了,以后有机会再补上来吧。

以ServerSocket和ClientSocket的一次操作流程来跟踪它们的源代码,看看他们是怎么样封WinSocket的。以非阻塞方式来例子。

1. ServerSocket要创建,构造函数如下:

(11)

constructor TServerSocket.Create(AOwner: TComponent);

begin

inherited Create(AOwner);

FServerSocket := TServerWinSocket.Create(INVALID_SOCKET);

InitSocket(FServerSocket);

FServerSocket.ThreadCacheSize := 10;

end;

inherited Create(AOwner);是继承自TComponent的构造函数。

接下来创建它的一个成员,这是一个TServerWinSocket的对象,这个才是真正封装SocketApi的类,等一个讨论它。事实上这个构造函数继承自它的父类TCustomServerSocket

接下来调用InitSocket(FServerSocket);这是ServerSocket的祖先类TAbstractSocket的一个方法,传入参数是成员FserverSocket完成的功能是将ServerSocket的事件指针指向TServerWinSocket的事件,使其能处理Socket触发的事件。

最后,设置FServerSocket允许连接的线程数,这里为10。

好,现在回过头来看看FServerSocket := TServerWinSocket.Create(INVALID_SOCKET);做了什么:

(111)

constructor TServerWinSocket.Create(ASocket: TSocket);

begin

FConnections := TList.Create;

FActiveThreads := TList.Create;

FListLock := TCriticalSection.Create;

inherited Create(ASocket);

FAsyncStyles := [asAccept];

end;

首先创建两个TList对象,一个是FConnections,代表各个处理客户连接的Socket,它对应于属性:property Connections[Index: Integer]: TCustomWinSocket,你可以通过这个属性对各个客户连接进行操作。FActiveThreads 管理由Connections 数组确定的的客户端连接线程TServerClientThread,它对应的属性是ActiveThreads,这个是只读属性,返回当前正在使用的TServerClientThread对象的个数。接下来创建互斥量对象,用于线程同步的处理。

到这里又调用其父类的构造函数,传递的参数就是ServerSocket给的值INVALID_SOCKET,(想想上面提到的这个值的意义)

好,再继续跟踪,到其父类的构造函数去看一下,我们这时应该保留一个问题,按照WinSock的编程模式,刚开始应该是初始化Winsock.DLL,并调用绑定监听函数,这些API是什么在地方被调用呢?

(1111)

constructor TCustomWinSocket.Create(ASocket: TSocket);

begin

inherited Create;

Startup;

FSocketLock := TCriticalSection.Create;

FASyncStyles := [asRead, asWrite, asConnect, asClose];

FSocket := ASocket;

FAddr.sin_family := PF_INET;

FAddr.sin_addr.s_addr := INADDR_ANY;

FAddr.sin_port := 0;

FConnected := FSocket <> INVALID_SOCKET;

end;

首先调用TObject的构造函数,

再调用Sartup;分析完这个函数再看里面的代码,这里可以猜测它里面会调用WSAStartup函数。

接下来看到从ServerSocket传过来的参数指定给了TCustomWinSocket的成员,还有下面几个成员的设置,可以肯定,这里是对Socket进行初始化,结合开头所讲的知识,再看看源代码。应该不难理解了吧。

再看看Startup的源码:

(11111)

procedure Startup;

var

ErrorCode: Integer;

begin

ErrorCode := WSAStartup($0101, WSAData);

if ErrorCode <> 0 then

raise ESocketError.CreateResFmt(@sWindowsSocketError,

[SysErrorMessage(ErrorCode), ErrorCode, 'WSAStartup']);

end;

这是一个全局函数,看到其中一行重要的代码了,

ErrorCode := WSAStartup($0101, WSAData);WinSock就是在这里被初始化的。

而在服务端没有建立监听之时,Socket就在上面被默认设置了。

好,现在ServerSocket的成员FserverSocket的创建就完成了,再回到最上面,

FServerSocket := TServerWinSocket.Create(INVALID_SOCKET);之后,便是

InitSocket(FServerSocket);了,看看它的代码:

(12)

procedure TAbstractSocket.InitSocket(Socket: TCustomWinSocket);

begin

Socket.OnSocketEvent := DoEvent;

Socket.OnErrorEvent := DoError;

end;

正好和上面所说的一样,现在可以认为当Socket发生了事件(比如连接,接收等)之后,就是调用了DoEvent了,错误发生了也一样。不妨看看DoEvent代码:

(121)

procedure TAbstractSocket.DoEvent(Sender: TObject; Socket: TCustomWinSocket;

SocketEvent: TSocketEvent);

begin

Event(Socket, SocketEvent);

end;

里面是调用Event,再看看Event的声明:

procedure Event(Socket: TCustomWinSocket; SocketEvent: TSocketEvent);virtual; abstract;

这是一个抽象函数,由此可以知道,TAbstractSocket是一个抽象的类,它只是封装了一般的操作,具体地都留到了它的子类中去了,所以它的子类一定覆盖了这个Event方法,而DoEvent中调用的Event实际上就是调用它子类的Event方法,这个就是典型的模板模式。

好,看看它的子类有没有覆盖这个方法,果然在TCustomSocket中看到了这个方法,并实现了它,看看它的源代码:

(1211)

procedure TCustomSocket.Event(Socket: TCustomWinSocket; SocketEvent: TSocketEvent);

begin

case SocketEvent of

seLookup: if Assigned(FOnLookup) then FOnLookup(Self, Socket);

seConnecting: if Assigned(FOnConnecting) then FOnConnecting(Self, Socket);

seConnect:

begin

FActive := True;

if Assigned(FOnConnect) then FOnConnect(Self, Socket);

end;

seListen:

begin

FActive := True;

if Assigned(FOnListen) then FOnListen(Self, Socket);

end;

seDisconnect:

begin

FActive := False;

if Assigned(FOnDisconnect) then FOnDisconnect(Self, Socket);

end;

seAccept: if Assigned(FOnAccept) then FOnAccept(Self, Socket);

seRead: if Assigned(FOnRead) then FOnRead(Self, Socket);

seWrite: if Assigned(FOnWrite) then FOnWrite(Self, Socket);

end;

end;

其中FonAccept等这些都是TCusotmSocket的成员,都是TSocketNotifyEvent类型的,TCustomSocket还通过下面这些属性,使该类拥有了这些事件处理的能力:

property OnLookup: TSocketNotifyEvent read FOnLookup write FOnLookup;

property OnConnecting: TSocketNotifyEvent read FOnConnecting write FOnConnecting;

property OnConnect: TSocketNotifyEvent read FOnConnect write FOnConnect;

property OnDisconnect: TSocketNotifyEvent read FOnDisconnect write FOnDisconnect;

property OnListen: TSocketNotifyEvent read FOnListen write FOnListen;

property OnAccept: TSocketNotifyEvent read FOnAccept write FOnAccept;

property OnRead: TSocketNotifyEvent read FOnRead write FOnRead;

property OnWrite: TSocketNotifyEvent read FOnWrite write FOnWrite;

而上面的Event实现,我们可以看到它是根据SocketEvent的类型来调用相应的事件指针的,而它的子类ServerSocket和ClientSocket也拥有了这些事件的处理能力,到这里我们终于明白了,它们的事件是怎么样出来的,但如果想得知这些事件是怎么触发的,还要看这一句:Socket.OnSocketEvent,OnSocketEvent到后面再说,这里先略。

错误处理事件原理上也是一样,到后面再说。

好了,到这里第一步解读完毕,我们所知道的ServerSocket创建之后所完成事件就是初始化Socket DLL,并指定好事件处理的方法指针和错误处理的方法指针。我们接下来看第二步。

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为我们做了这么多的事,使我们在使用的时候感觉到是如此的简单。

3.程序执行到这里,得看看ClientSocket吧,有了上面的分析,下面的分析都会简单得多:

constructor TClientSocket.Create(AOwner: TComponent);

begin

inherited Create(AOwner);

FClientSocket := TClientWinSocket.Create(INVALID_SOCKET);

InitSocket(FClientSocket);

end;

没有什么值得讨论的FClientSocket也是继承父类的构造方法,上面已经说过了。

接设置Port,Address等,都是到祖先类设置,没有什么好说的,最后一句是

Active:=true;

上面的讨论已经知道,它最后会调用ClientSocket的覆盖方法:

procedure TClientSocket.DoActivate(Value: Boolean);

begin

if (Value <> FClientSocket.Connected) and not (csDesigning in ComponentState) then

begin

if FClientSocket.Connected then

FClientSocket.Disconnect(FClientSocket.FSocket)

else FClientSocket.Open(FHost, FAddress, FService, FPort, ClientType = ctBlocking);

end;

end;

这里的Value为True,则调用

FClientSocket.Open(FHost, FAddress, FService, FPort, ClientType = ctBlocking);

去看看吧:

procedure TCustomWinSocket.Open(const Name, Address, Service: string; Port: Word; Block: Boolean);

begin

if FConnected then raise ESocketError.CreateRes(@sSocketAlreadyOpen);

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, True);

DoOpen;

end else

AsyncInitSocket(Name, Address, Service, Port, 0, True);

except

Disconnect(FSocket);

raise;

end;

end;

没什么特别之处,以我们设置的方式,也一样调用AsyncInitSocket,同样也是完成Socket的设置

再回去看看第2步中的代码吧,而它的Client参数是设为True,所以最后的一定会调用:

DoOpen:

procedure TCustomWinSocket.DoOpen;

begin

DoSetASyncStyles;

Event(Self, seConnecting);

CheckSocketResult(WinSock.connect(FSocket, FAddr, SizeOf(FAddr)), 'connect');

FLookupState := lsIdle;

if not (asConnect in FAsyncStyles) then

begin

FConnected := FSocket <> INVALID_SOCKET;

Event(Self, seConnect);

end;

end;

和上面说到的Dolisten简直像极了,这里也没有必多说了。我们已经可以向更加简单的第四步进发了。

4. ClientSocket向服务端连接后,WinSock会向服务端发送一个事件,,而上面已经过了事件获取函数:CMSocketMessage

其中的消息标识码是:FD_ACCEPT,所以会调用Accept(Socket);函数,这个与其他的事件方法不同,我们来看看它的源码就知道了:

procedure TCustomWinSocket.Accept(Socket: TSocket);

begin

end;

Socket的父类将这个方法设为虚方法,并什么事也不做,可知它的子类ServerWinSocket一定覆盖了这个方法,并可能要做一些线程分配方面的工作,但这又是一个大函数了,又要花一大篇来分析了,但离胜利之地已经不远了,这一步完成,其他就全部都是小菜一碟了。

(41)

procedure TServerWinSocket.Accept(Socket: TSocket);

var

ClientSocket: TServerClientWinSocket;

ClientWinSocket: TSocket;

Addr: TSockAddrIn;

Len: Integer;

OldOpenType, NewOpenType: Integer;

begin

Len := SizeOf(OldOpenType);

//得到与指定套接口相关的属性选项。

if getsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE, PChar(@OldOpenType),

Len) = 0 then

try

if FServerType = stThreadBlocking then

begin

NewOpenType := SO_SYNCHRONOUS_NONALERT;

//设置与指定套接口相关的属性选项。

setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE, PChar(@NewOpenType), Len);

end;

Len := SizeOf(Addr);

调用SocketAPI,返回一个新的套接字与客户端进行通信

ClientWinSocket := WinSock.accept(Socket, @Addr, @Len);

if ClientWinSocket <> INVALID_SOCKET then

begin

ClientSocket := GetClientSocket(ClientWinSocket);

if Assigned(FOnSocketEvent) then

FOnSocketEvent(Self, ClientSocket, seAccept);

if FServerType = stThreadBlocking then

begin

ClientSocket.ASyncStyles := [];

GetServerThread(ClientSocket);

end;

end;

finally

Len := SizeOf(OldOpenType);

setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE, PChar(@OldOpenType), Len);

end;

end;

我们只针对我们非阻塞模式来看几个重要的语句,上面已经用蓝色标识出来了。首先:

ClientWinSocket := WinSock.accept(Socket, @Addr, @Len);

调用WinSock的API,Accept得到一个与客户端进行通信的Socket,而原来的Socket则继续监听。

ClientSocket := GetClientSocket(ClientWinSocket);

ClientSocket是一个TServerClientWinSocket类的对象,这个对象就是代表与客户端通信的Socket类,那么GetClientSocket必定是以参数WinSocket创建了一个TServerClientWinSocket对象,看代码去:

(411)

function TServerWinSocket.GetClientSocket(Socket: TSocket): TServerClientWinSocket;

begin

Result := nil;

if Assigned(FOnGetSocket) then FOnGetSocket(Self, Socket, Result);

if Result = nil then

Result := TServerClientWinSocket.Create(Socket, Self);

end;

果然其中创建了这个类的对象,不但传入了上面的WinSocket,还传递了ServerWinSocket自己,继续吧,不要言累:

(4111)

constructor TServerClientWinSocket.Create(Socket: TSocket; ServerWinSocket: TServerWinSocket);

begin

FServerWinSocket := ServerWinSocket;

if Assigned(FServerWinSocket) then

begin

FServerWinSocket.AddClient(Self);

if FServerWinSocket.AsyncStyles <> [] then

begin

OnSocketEvent := FServerWinSocket.ClientEvent;

OnErrorEvent := FServerWinSocket.ClientError;

end;

end;

inherited Create(Socket);

if FServerWinSocket.ASyncStyles <> [] then DoSetAsyncStyles;

if FConnected then Event(Self, seConnect);

end;

这里两个重要的语句已经标出来了,第一句,它调用了传进来的ServerWinSocket的方法AddClient,看看这个怎么实现的:

(41111)

procedure TServerWinSocket.AddClient(AClient: TServerClientWinSocket);

begin

FListLock.Enter;

try

if FConnections.IndexOf(AClient) < 0 then

FConnections.Add(AClient);

finally

FListLock.Leave;

end;

end;

哈,这个方法的意思已经很明显了,他就是把这个与客户端的通信的Socket加入了一个列表中,这个列表已经在前面看到创建了的,便于以后维护。

上面函数接下的另一句inherited Create(Socket);则是其父类TCustomWinSocket的构造函数了,上面我们已经分析过了,就是将这个与客户端的连接的Socket赋给成员FSocket。

最后我们得回过头到Accept方法再去看最后一个重要语句:

FOnSocketEvent(Self, ClientSocket, seAccept);

OnAccept终于发生了,我们可以在这里事件中向客户端发送一些信息!

5.分析到这里,接下来就是WinSock的事了,客户端和服务端开始通信,于是它向这些类发送消息,然后传递事件处理函数,就可以进行非阻塞的数据传输,而那些数据传递函数最终也都是调用Socket的API,所以我想也不要多说了,看看源代码吧。大概的脉络其实在第2步中说得也差不多了。

执行到最后,会设Active为False,回顾上面的代码,则会调用Socket的父类:

TCustomWinSocket.Disconnect,不过ServerSokcet又覆盖了这个方法,它所完成的功能只是释放掉所有的线程,最后还是会调用父类的这个方法。

(51)

procedure TCustomWinSocket.Disconnect(Socket: TSocket);

begin

Lock;

try

if FLookupHandle <> 0 then

CheckSocketResult(WSACancelASyncRequest(FLookupHandle), 'WSACancelASyncRequest');

FLookupHandle := 0;

if (Socket = INVALID_SOCKET) or (Socket <> FSocket) then exit;

Event(Self, seDisconnect);

CheckSocketResult(closesocket(FSocket), 'closesocket');

FSocket := INVALID_SOCKET;

FAddr.sin_family := PF_INET;

FAddr.sin_addr.s_addr := INADDR_ANY;

FAddr.sin_port := 0;

FConnected := False;

FreeAndNil(FSendStream);

finally

Unlock;

end;

end;

CheckSocketResult(closesocket(FSocket), 'closesocket');这一句即完成了Socket的关闭了。

通信完后,两个类都要释放了,在析构函数中做一些收尾的工作,大想也可以猜它要做什么了。

先来看看ServerSocket的析构函数:

(52)

destructor TCustomServerSocket.Destroy;

begin

FServerSocket.Free;

inherited Destroy;//这是TComponent的析构函数

end;

先释放FServerSocket,再调用父类的析构函数,一个个来看。它到底做了些什么收尾的工作。

(521)

destructor TServerWinSocket.Destroy;

begin

inherited Destroy;

FConnections.Free;

FActiveThreads.Free;

FListLock.Free;

end;

没有什么可说的,再追上去:

(5211)

destructor TCustomWinSocket.Destroy;

begin

FOnSocketEvent := nil; { disable events }

if FConnected and (FSocket <> INVALID_SOCKET) then

Disconnect(FSocket);

//释放由AllocateHWnd.生成的窗口。

if FHandle <> 0 then DeallocateHWnd(FHandle);

FSocketLock.Free;

Cleanup;

FreeMem(FGetHostData);

FGetHostData := nil;

inherited Destroy;//这是TObject的析构函数

end;

看下去:

procedure Cleanup;

var

ErrorCode: Integer;

begin

ErrorCode := WSACleanup;

if ErrorCode <> 0 then

raise ESocketError.CreateResFmt(@sWindowsSocketError,

[SysErrorMessage(ErrorCode), ErrorCode, 'WSACleanup']);

end;

总算是最后了,看到SocketAPI的调用了吗。

那么ClientSocket的呢:

(53)

destructor TClientSocket.Destroy;

begin

FClientSocket.Free;

inherited Destroy; //这是TComponent的析构函数

end;

我们已经很有经验了,看FClientSocket的析构函数去,但ClientSocket没有构析函数,他的父类才有,即我们说到的TCustomWinSocket,那么一切都结束了。

这一大块,非阻塞式的一次操作就这样结束了。请各位对着源代码看吧,多跟踪一下,也许发现的东西比我还多。如果下次有机会,我会进行一次阻塞式的代码阅读的。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有