📄 socket始解.txt
字号:
看看Startup的源码:
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就在上面被默认设置了。
2.2. 设定事件指针
好,现在ServerSocket的成员FserverSocket的创建就完成了,再回到最上面,
FServerSocket := TServerWinSocket.Create(INVALID_SOCKET);之后,便是
InitSocket(FServerSocket);了,看看它的代码:
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中看到了这个方
法,并实现了它,看看它的源代码:
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.3. 指定端口开始监听
我们创建完ServerSocket后,下步是指定端口,然后Open开始监视,这个过程会触
发一些事件,我们正好看看这些事件是怎么样发生的。
由于这一次操作是非阻塞方式的,所以ServerType默认为stNonBlocking,我们开
始设置Port,这个属性的声明在哪里,这个属性一直到跟踪祖先类AbstractSocket
中去,因为无论客户端和服务端都是要设置端口的,所以理所当然要封装到最高层
的类去了,以让它的子类拥有这些属性:
property
Port: Integer read FPort write SetPort;
再看看SetPort方法:
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:
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了:
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
否则,则Value为True,
调用FServerSocket.Listen来开始进行监视。
现在我们就要打开FServerSocket的源码,看看它是怎么样完成断开连接和开始连
接的了。
先看开始监听的:
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方法:
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);
end;
end;
这里第一句重要函数出现了:
FSocket := socket(PF_INET, SOCK_STREAM, IPPROTO_IP);
记得上面Fsocket曾被赋过值吗,是INVALID_SOCKET;,而这里终于被替回了一个可
用的套接字了。
3. 事件指针
第二个重要的函数:Event(Self, seLookUp);可以先猜测这个方法最终于会调用事
件指针。好了,再看一下Even吧:
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就有这个事件。
接下来判断Block的值,如果为阻塞方式,则为True,否则为False,非阻塞时为
False,调用下方法:
AsyncInitSocket(Name, Address, Service, Port, QueueSize, False);
按我们所设的方式,接下来要调用如下函数了(另外一些方法以后再说):
AsyncInitSocket(Name, Address, Service, Port, QueueSize, False);
这函数很大,但所用到的技巧也非常精彩,得好好分析它:
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);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -