⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 socket始解.txt

📁 socket知识介绍
💻 TXT
📖 第 1 页 / 共 4 页
字号:

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

  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,并触发DoListen事件:

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方法,坚持走下去吧:

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)

则指定允许触发哪些事件。

 

呼还没有完吗,其实差不多了,我们现在知道了当有客户端连接或读写,是通过什
么方式来让服务端知道并触发相应的事件的,说到底还是用了WinSock的API,只不
过Delphi用自己的事件处理方式将那些Socket异步消息转化成自己的事件了,这个
过程是非常精彩的,很值得我们学习。

但现在只剩下最一步了。

 


    2.4. 事件触发流程图

 

 

 

 


    2.5. 消息处理方法

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分析

 


    3.1 ClientSocket构造函数

有了上面的分析,下面的分析都会简单得多:

constructor TClientSocket.Create(AOwner: TComponent);

begin

  inherited Create(AOwner);

  FClientSocket := TClientWinSocket.Create(INVALID_SOCKET);

  InitSocket(FClientSocket);

end;

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

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

Active:=true;


    3.2 开启客户端发起连接

 

上面的讨论已经知道,它最后会调用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;

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -