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

📄 svrsocket,cltsocket控件源码.htm

📁 ServerSocket,ClientSocket控件源码,讲述内部实现原理
💻 HTM
📖 第 1 页 / 共 3 页
字号:
      <br>
          else DoListen(QueueSize);<br> 
      <br>
        end;<br>
      <br>
    end;<br>
      <br>
    if FLookupState &lt;> lsIdle then<br> 
      <br>
      ASyncInitSocket(Name, Address, Service, Port, QueueSize, Client);//递归<br> 
      <br>
  except<br>
      <br>
    Disconnect(FSocket);<br>
      <br>
    raise;<br>
      <br>
  end;<br>
      <br>
      end;<br>
      <br>
      它从判断FLookupState的状态来执行相应的操作,而最后每一步都会执行到,它是怎么做到的呢,答案就是最后的这两句:<br>
      <br>
      if FLookupState &lt;> lsIdle then<br> 
      <br>
      ASyncInitSocket(Name, Address, Service, Port, QueueSize, Client);<br> 
      <br>
      我们先从头来看吧:<br>
      <br>
      首先,FLookupState的状态行为lsIdle,则执行isIdle这一大块,判断Client的值,这里是服务端,所以应该为False(从调用该方法的函数也可得知),所以只执行了这一块:<br>
      <br>
      FLookupState := lsLookupAddress;<br> 
      <br>
      FAddr.sin_addr.S_addr := INADDR_ANY;<br> 
      <br>
      其余都是对于客户端的来作的,所以以后再讨论它(由此也可以知道该函数的重要性了)。<br>
      <br>
      还记得INADDR_ANY的意义吗,它会使得WinSock自动加入正确的地址。<br>
      <br>
      好,方法执行到最后的这几句:<br>
      <br>
      if FLookupState &lt;> lsIdle then<br> 
      <br>
      ASyncInitSocket(Name, Address, Service, Port, QueueSize, Client);<br> 
      <br>
      这时FLookupState已经为lsLookupAddress了,所以又会调用ASyncInitSocket方法,它就是这样递归调用本身,直到所有操作都完毕,这样的设计思维实在是太精巧了。<br>
      <br>
      第一递归之后,它又执行了那一块呢,当然是lsLookupAddress:这一块啦,看看代码,知道它会先判断函数的参数Service是否为空,我们前面没有对该传递过来的这个参数并没有同值,所以为空,便执行了这两句:<br>
      <br>
      FLookupState := lsLookupService;<br> 
      <br>
      FAddr.sin_port := htons(Port);<br> 
      <br>
      可知,它对结构的端口值了,又进行第二次递归,这里应该是执行lsLookupService:这一块,并调用了这个方法DoListen(QueueSize);开始监听:<br>
      <br>
      FLookupState := lsIdle;<br> 
      <br>
      if Client then<br> 
      <br>
  DoOpen<br>
      <br>
      else DoListen(QueueSize);<br> 
      <br>
      我们看到FLookupState已经又回到了IsIdle,所以就不再递归了。<br>
      <br>
      最好,我们得来看看DoListen这个方法,这个大概就是万事具务,只欠东风的东风了,我们猜测它会在这里调用Bind和Listen等API,并触发Oolisten事件:<br>
      <br>
      (2211121)<br>
      <br>
      procedure TCustomWinSocket.DoListen(QueueSize: Integer);<br> 
      <br>
      begin<br>
      <br>
  CheckSocketResult(bind(FSocket, FAddr, SizeOf(FAddr)), 'bind');<br> 
      <br>
  DoSetASyncStyles;<br>
      <br>
  if QueueSize > SOMAXCONN then QueueSize := SOMAXCONN;<br> 
      <br>
  Event(Self, seListen);<br> 
      <br>
  CheckSocketResult(Winsock.listen(FSocket, QueueSize), 'listen');<br> 
      <br>
  FLookupState := lsIdle;<br> 
      <br>
  FConnected := True;<br> 
      <br>
      end;<br>
      <br>
      哈,一切都明朗了,所有API都在这里调用了,事件也触发了。现在就等客户来连接了<br>
      <br>
      只是还没有完,还有一个DoSetASyncStyles方法,坚持走下去吧,会到挑花园的:<br>
      <br>
      (22111211)<br>
      <br>
      procedure TCustomWinSocket.DoSetAsyncStyles;<br> 
      <br>
      var<br>
      <br>
  Msg: Integer;<br> 
      <br>
  Wnd: HWnd;<br> 
      <br>
  Blocking: Longint;<br> 
      <br>
      begin<br>
      <br>
  Msg := 0;<br> 
      <br>
  Wnd := 0;<br> 
      <br>
  if FAsyncStyles &lt;> [] then<br> 
      <br>
  begin<br>
      <br>
    Msg := CM_SOCKETMESSAGE;<br> 
      <br>
    Wnd := Handle;<br> 
      <br>
  end;<br>
      <br>
  WSAAsyncSelect(FSocket, Wnd, Msg, Longint(Byte(FAsyncStyles)));<br> 
      <br>
  if FASyncStyles = [] then<br> 
      <br>
  begin<br>
      <br>
    Blocking := 0;<br> 
      <br>
    ioctlsocket(FSocket, FIONBIO, Blocking);<br> 
      <br>
  end;<br>
      <br>
      end;<br>
      <br>
      有两个情况,当FAsyncStyles有元素时和没有元素时,从上面的代码中我们看到它是有元素的。则:<br>
      <br>
      Msg := CM_SOCKETMESSAGE;<br> 
      <br>
      Wnd := Handle;<br> 
      <br>
      第一句应该是指针Socket消息了,用于与客户端的读写事件的触发的吧。而第二句,则我仔细看了,Handle是TCustomWinSocket的一个属性:<br>
      <br>
      property Handle: HWnd read GetHandle;<br> 
      <br>
      再看看GetHandle方法<br>
      <br>
      function TCustomWinSocket.GetHandle: HWnd;<br> 
      <br>
      begin<br>
      <br>
  if FHandle = 0 then<br> 
      <br>
    FHandle := AllocateHwnd(WndProc);<br> 
      <br>
  Result := FHandle;<br> 
      <br>
      end;<br>
      <br>
      我们记得上面并没有对FHandle进行赋值,所以它应该为0,则调用了AllocateHwnd,这个方法产生一个不可见的窗口用于接收消息,窗口过程就是WndProc,在CustomWinSocket有声明:<br>
      <br>
      procedure TCustomWinSocket.WndProc(var Message: TMessage);<br> 
      <br>
      begin<br>
      <br>
  try<br>
      <br>
    Dispatch(Message);<br>
      <br>
  except<br>
      <br>
    if Assigned(ApplicationHandleException) then<br> 
      <br>
      ApplicationHandleException(Self);<br>
      <br>
  end;<br>
      <br>
      end;<br>
      <br>
      可以知道,他调用Object的Dispatch(Message);进行消息分配,而我们看到类中有消息函数的声明:<br>
      <br>
      procedure CMSocketMessage(var Message: TCMSocketMessage); message CM_SOCKETMESSAGE;<br> 
      <br>
      当事件发生时就会调用这些消息处理函数了。而这些消息是从那里发生的呢,得回过头去看看DoSetAsyncStyles方法,其中有这一个SocketAPI:<br>
      <br>
      WSAAsyncSelect(FSocket, Wnd, Msg, Longint(Byte(FAsyncStyles)));<br> 
      <br>
      就是通过它,当有网络消息发生的时候,才会触发上面事件的,而Longint(Byte(FAsyncStyles)<br>
      <br>
      则指定允许触发哪些事件。(具体的还是看看MSDN的说明吧)<br>
      <br>
      <br>
      <br>
      呼还没有完吗,其实差不多了,我们现在知道了当有客户端连接或读写,是通过什么方式来让服务端知道并触发相应的事件的,说到底还是用了WinSock的API,只不过Delphi用自己的事件处理方式将那些Socket异步消息转化成自己的事件了,这个过程是非常精彩的,很值得我们学习。<br>
      <br>
      但现在只剩下最一步了,TCustomWinSocket消息处理方法中,如何转化为事件,并传递到ServerSocket去的呢,答案只有在源代码中找了:<br>
      <br>
      procedure TCustomWinSocket.CMSocketMessage(var Message: TCMSocketMessage);<br> 
      <br>
      <br>
      <br>
  function CheckError: Boolean;<br> 
      <br>
  var<br>
      <br>
    ErrorEvent: TErrorEvent;<br> 
      <br>
    ErrorCode: Integer;<br> 
      <br>
  begin<br>
      <br>
    if Message.SelectError &lt;> 0 then<br> 
      <br>
    begin<br>
      <br>
      Result := False;<br> 
      <br>
      ErrorCode := Message.SelectError;<br> 
      <br>
      case Message.SelectEvent of<br> 
      <br>
        FD_CONNECT: ErrorEvent := eeConnect;<br> 
      <br>
        FD_CLOSE: ErrorEvent := eeDisconnect;<br> 
      <br>
        FD_READ: ErrorEvent := eeReceive;<br> 
      <br>
        FD_WRITE: ErrorEvent := eeSend;<br> 
      <br>
        FD_ACCEPT: ErrorEvent := eeAccept;<br> 
      <br>
      else<br>
      <br>
        ErrorEvent := eeGeneral;<br> 
      <br>
      end;<br>
      <br>
      Error(Self, ErrorEvent, ErrorCode);<br> 
      <br>
      if ErrorCode &lt;> 0 then<br> 
      <br>
        raise ESocketError.CreateResFmt(@sASyncSocketError, [ErrorCode]);<br> 
      <br>
    end else Result := True;<br> 
      <br>
  end;<br>
      <br>
      <br>
      <br>
      begin<br>
      <br>
  with Message do<br> 
      <br>
    if CheckError then<br> 
      <br>
      case SelectEvent of<br> 
      <br>
        FD_CONNECT: Connect(Socket);<br> 
      <br>
        FD_CLOSE: Disconnect(Socket);<br> 
      <br>
        FD_READ: Read(Socket);<br> 
      <br>
        FD_WRITE: Write(Socket);<br> 
      <br>
        FD_ACCEPT: Accept(Socket);//这个很特殊<br> 
      <br>
      end;<br>
      <br>
      end;<br>
      <br>
      呵,又是一个大函数,不过理解起来倒是容易多了,先检查有没有错误,如果有就调用Error(Self, ErrorEvent, ErrorCode);如果没有,就根据相应标识,调用相就的函数,其实我们已经可以确定,像Read这些内部一定会调用Event,再由Event调用<br> 
      <br>
      FOnSocketEvent(Self, Socket, SocketEvent);,这样才能使得ServerSocket这些外部的类获得事件(上面已经说到它们是怎么把事件关联起来的了)。<br> 
      <br>
      而源代码中确实也是这样的,这里只列出一个read,其他的一样:<br>
      <br>
      procedure TCustomWinSocket.Read(Socket: TSocket);<br> 
      <br>
      begin<br>
      <br>
  if (FSocket = INVALID_SOCKET) or (Socket &lt;> FSocket) then Exit;<br> 
      <br>
  Event(Self, seRead);<br> 
      <br>
      end;<br>
      <br>
      procedure TCustomWinSocket.Event(Socket: TCustomWinSocket; SocketEvent: TSocketEvent);<br> 
      <br>
      begin<br>
      <br>
  if Assigned(FOnSocketEvent) then FOnSocketEvent(Self, Socket, SocketEvent);<br> 
      <br>
      end;<br>
      <br>
      <br>
      <br>
      完了吧,可惜还没有,回到上面的函数去,发现还有一个方法没有分析:<br>
      <br>
      FServerSocket.Disconnect(FServerSocket.SocketHandle)可是按我们流程,还不会调用到它,所以这里暂不提它。<br>
      <br>
      <br>
      <br>
      好了,第二步就这样结束了,再说下去,我自己也晕了。不过我们知道了这一步所完成的任务:连接一个监听的套接字,并设定好了事件方法指针,在适当的时机会调用适当的事件处理函数。真的得谢谢Delphi为我们做了这么多的事,使我们在使用的时候感觉到是如此的简单。<br>
      <br>
      <br>
      <br>
      <br>
      <br>
      3.程序执行到这里,得看看ClientSocket吧,有了上面的分析,下面的分析都会简单得多:<br>
      <br>
      constructor TClientSocket.Create(AOwner: TComponent);<br> 
      <br>
      begin<br>
      <br>
  inherited Create(AOwner);<br> 
      <br>
  FClientSocket := TClientWinSocket.Create(INVALID_SOCKET);<br> 
      <br>
  InitSocket(FClientSocket);<br>
      <br>
      end;<br>
      <br>
      没有什么值得讨论的FClientSocket也是继承父类的构造方法,上面已经说过了。<br>
      <br>
      接设置Port,Address等,都是到祖先类设置,没有什么好说的,最后一句是<br>
      <br>
      Active:=true;<br>
      <br>
      上面的讨论已经知道,它最后会调用ClientSocket的覆盖方法:<br>
      <br>
      procedure TClientSocket.DoActivate(Value: Boolean);<br> 
      <br>
      begin<br>
      <br>
  if (Value &lt;> FClientSocket.Connected) and not (csDesigning in ComponentState) then<br> 
      <br>
  begin<br>
      <br>
    if FClientSocket.Connected then<br> 
      <br>
      FClientSocket.Disconnect(FClientSocket.FSocket)<br>
      <br>
    else FClientSocket.Open(FHost, FAddress, FService, FPort, ClientType = ctBlocking);<br> 
      <br>
  end;<br>
      <br>
      end;<br>
      <br>
      这里的Value为True,则调用<br>
      <br>
      FClientSocket.Open(FHost, FAddress, FService, FPort, ClientType = ctBlocking);<br> 
      <br>
      去看看吧:<br>
      <br>
      procedure TCustomWinSocket.Open(const Name, Address, Service: string; Port: Word; Block: Boolean);<br> 
      <br>
      begin<br>
      <br>
  if FConnected then raise ESocketError.CreateRes(@sSocketAlreadyOpen);<br> 
      <br>
  FSocket := socket(PF_INET, SOCK_STREAM, IPPROTO_IP);<br> 
      <br>
  if FSocket = INVALID_SOCKET then raise ESocketError.CreateRes(@sCannotCreateSocket);<br> 
      <br>
  try<br>
      <br>
    Event(Self, seLookUp);<br> 
      <br>
    if Block then<br> 
      <br>
    begin<br>
      <br>
      FAddr := InitSocket(Name, Address, Service, Port, True);<br> 
      <br>
      DoOpen;<br>
      <br>
      &amp;n <br>
    </td>
  </tr>
</table>

</body>

</html>

⌨️ 快捷键说明

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