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

📄 svrsocket,cltsocket控件源码.htm

📁 ServerSocket,ClientSocket控件源码,讲述内部实现原理
💻 HTM
📖 第 1 页 / 共 3 页
字号:
<html>

<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<meta name="GENERATOR" content="Microsoft FrontPage 4.0">
<meta name="ProgId" content="FrontPage.Editor.Document">
<title>New Page 1</title>
</head>

<body>

<p align="center"><font size="5">ServerSocket,ClientSocket控件源码</font></p>
<table border="1" width="100%" bgcolor="#C0C0C0">
  <tr>
    <td width="100%">ServerSocket,ClientSocket控件源码&nbsp;<br>
      <br>
      本篇将通过一次Socket通信操作来对ServerSocket和ClientSocket这两个控件的源码进行一次阅读,希望能理出一个脉络来,以供大家参考。其实说得确切一点,应该是对Scktcomp这个单元进行解读,但由于这个单元的代码太多了,所以不可能面面俱到,我试图以非阻塞式的通信来一步步说明它们是怎么样封装WinSock的API的,至于阻塞式的,在ServerSocket和ClientSocket并不常用,所以这一篇就不打算说了,可能源码中会有一些忽略掉,以后有时间来补一篇阻塞式的阅读吧。虽然现在Delphi已经用Indy控件替换了SS和CS等网络控件,但毕竟这两个控件是对Socket函数进行封装,学习它怎么样封装也好吧。<br>
      <br>
      在这之前,须对WinSock有一个大概的了解,但也仅止大概,对Socket编程有一个总体的掌握行了,毕竟那不是我的能力所及。想要系统一点的学习Socket,可以去网上下WinSocket的中文文档以及参考MSDN。<br>
      <br>
      以下是说明这个过程中各个函数的声明<br>
      <br>
      在开始使用WinSock的Api的时候,必须加载WinSock Dll的相应版本,这时用到的函数是:<br> 
      <br>
      int WSAStartup (<br> 
  WORD wVersionRequested,&nbsp;<br>
  LPWSADATA lpWSAData&nbsp;<br>
      );<br>
      wVersionRequested指定用到的WinSock的最低版本,高字节指定副版本,低字节指定主版本,关于WinSock的版本,详见MSDN。<br>
      <br>
      lpWSAData结构类型,系统把加载进去的版本信息添加到这个结构中。<br>
      <br>
      该函数成功返回0,不成功则返回几个错误代码之一<br>
      <br>
      这个函数和WSACleanup对应,查看MSDN<br>
      <br>
      从MSDN列出一个例子:<br>
      <br>
      WORD wVersionRequested;<br> 
      <br>
      WSADATA wsaData;<br> 
      <br>
      int err;<br> 
      <br>
      <br>
      <br>
      wVersionRequested = MAKEWORD( 2, 2 );<br> 
      <br>
      <br>
      <br>
      err = WSAStartup( wVersionRequested, &amp;wsaData );<br> 
      <br>
      if ( err != 0 ) {<br> 
      <br>
    /* Tell the user that we could not find a usable */<br> 
      <br>
    /* WinSock DLL.                                  */<br> 
      <br>
    return;<br>
      <br>
      }<br>
      <br>
      调用上面的函数之后,就可以用下面的函数来创建一个Socket了,Socket是WinSock定义的数据类型(整数),相当于句柄,用于标识系统中的每一个Socket连接<br>
      <br>
      SOCKET socket (<br> 
  int af,&nbsp;<br>
  int type,&nbsp;<br>
  int protocol&nbsp;<br>
      );<br>
      Af协议标志,Internet协议族的标志是PINET。<br>
      <br>
      Type 协议类型志,SOCISTREAM表明是面向连接的字节流类型,SOCIDGRAM表明是面向无连接的数据报类型。<br> 
      <br>
      Protocol Socket采用的协议类型,如果采用IPPROTO_TCP常数就是采用了TCP协议。<br> 
      <br>
      如果调用失败,会返回INVALID_SOCKET值,正常的Socket取值范围是1~INVALID_SOCKET-1;<br>
      <br>
      下面的函数将Socket与特定的主机进行绑定:<br>
      <br>
      int bind (<br> 
  SOCKET s,&nbsp;<br>
  const struct sockaddr FAR*  name,&nbsp;<br>
  int namelen&nbsp;<br>
      );<br>
      S就是一个Socket,由Socket()函数返回得到的。<br>
      <br>
      Name是sockaddr结构的变量<br>
      <br>
      Namelen是Name结构的长度。<br>
      <br>
      如果函数调用成功,将返回0,如果不成功,将返回SOCKET_ERROR,可以从WSAGetLastError.函数获得错误代码。<br>
      <br>
      现在说sockaddr这个结构,在Winsock中其实有两个功能相似的结构,它们是:<br>
      <br>
      struct sockaddr {<br> 
      <br>
        u_short    sa_family;<br> 
      <br>
        char       sa_data[14];<br> 
      <br>
      };<br>
      <br>
      struct sockaddr_in {<br> 
      <br>
        short   sin_family;<br> 
      <br>
        u_short sin_port;<br> 
      <br>
        struct  in_addr sin_addr;<br> 
      <br>
        char    sin_zero[8];<br> 
      <br>
      };<br>
      <br>
      这两个结构在Delphi中被声明为一个变体记录,这两个指定通信的本地主机地址,本地协议端口,另外还有通信过程中使用的协议类型。<br>
      <br>
      其中:sin_family规定了哪个协议用来实现套按字连接。WinSock必须设置常数AF_INET<br>
      <br>
      sin_port;:WinSock应用程序使用的端口号<br>
      <br>
      sin_addr:这个是32位IP地址<br>
      <br>
      sin_zero[8];这个保留,没有使用。<br>
      <br>
      Server端必须调用Bind()函数,设计时可以将地址设定为INADDR_ANY,这样WinSock会自动加入机器正确的地址.<br>
      <br>
      以下是客户端向服务端连接时调用的函数<br>
      <br>
      int connect (<br> 
  SOCKET s,&nbsp;<br>
  const struct sockaddr FAR*  name,&nbsp;<br>
  int namelen&nbsp;<br>
      );<br>
      函数里的参数和bind()一个,不多说了,函数成功时返回0,否则返回SOCKET_ERROR。<br>
      <br>
      服务端在Bind之后,调用下面函数进行监视。<br>
      <br>
      int listen (<br> 
  SOCKET s,&nbsp;<br>
  int backlog&nbsp;<br>
      );<br>
      其中backlog是可以建立的最大连接数,如果值设为SOMAXCONN,将由Socket的服务提供商设定一个合理的值,这个值不确定。<br>
      <br>
      函数成功时返回0,否则返回SOCKET_ERROR。<br>
      <br>
      当客户端连接服务端,服务端调用下面函数接收客户的请求,并向客户机发送应答信息<br>
      <br>
      SOCKET accept (<br> 
  SOCKET s,&nbsp;<br>
  struct sockaddr FAR* addr,&nbsp;<br>
  int FAR* addrlen&nbsp;<br>
      );<br>
      其中中addr是用来保存客户机地址信息的指针<br>
      <br>
      Addrlen是addr的长度一般是Sizeof(addr)<br>
      <br>
      如果函数成功,则返回一个Socket,这个Socket才是与客户实际通信的套接字,原来的那个Socket继续监视其他客户端的连接<br>
      <br>
      以下几个是用个服务机和客户机通信的函数,<br>
      int send (SOCKET s,const char FAR * buf, int len,int flags );<br> 
      在面向连接的情况下发送数据<br>
      int recv (SOCKET s, char FAR* buf,  int len, int flags );<br> 
      在面向连接的情况下接收数据<br>
      另外还有sendto和recvfrom用于无连接情况,具体查MSDN<br>
      其中Buf是发送或接收的缓冲区,Len是缓冲区的大小,Flags是网络呼叫产生方式标志,值可以为0,MSG_DONTROUTE或 MSG_OOB用途具体看MSDN。<br> 
      通信完毕后,需要关闭Socket,函数声明如下:<br>
      int closesocket (<br> 
  SOCKET s&nbsp;<br>
      );<br>
      在程序的最后,需要调用下面的函数,结束WinSock DLL<br> 
      int  WSACleanup (void);<br> 
      函数成功时返回0,否则返回SOCKET_ERROR。<br>
      <br>
      <br>
      下面开始TServerSockett和TClientSocket的原代码阅读:<br>
      不过在分析之前,先要明白,对于WinSock的封装其实是由ScktComp单元的几个类一起完成的,它们的关系是:<br>
      <br>
      TServerSocket继承于TCustomServerSocket<br>
      <br>
      TCustomServerSocket继承于TCustomSocket<br>
      <br>
      TCustomSocket继承于TAbstractSocket而TAbstractSockeet的上级则是TComponent了<br>
      <br>
      TClientSocket继承于TCustomSocket<br>
      <br>
      另外还有几个类:<br>
      <br>
      TServerWinSocket和TClientWinSocket以及TServerClientWinSocket继承TCustomSocket<br>
      <br>
      它们才是真正封装Socket的类。作为上面提到的类的成员存在<br>
      <br>
      此外还有几个用于阻塞式传输的类。这里就忽略不讲了,以后有机会再补上来吧。&nbsp;<br>
      <br>
      <br>
      <br>
      以ServerSocket和ClientSocket的一次操作流程来跟踪它们的源代码,看看他们是怎么样封WinSocket的。以非阻塞方式来例子。<br>
      <br>
      <br>
      <br>
      1.  ServerSocket要创建,构造函数如下:<br> 
      <br>
      (11)<br>
      <br>
      constructor TServerSocket.Create(AOwner: TComponent);<br> 
      <br>
      begin<br>
      <br>
  inherited Create(AOwner);<br> 
      <br>
  FServerSocket := TServerWinSocket.Create(INVALID_SOCKET);<br> 
      <br>
  InitSocket(FServerSocket);<br>
      <br>
  FServerSocket.ThreadCacheSize := 10;<br> 
      <br>
      end;<br>
      <br>
      inherited Create(AOwner);是继承自TComponent的构造函数。<br> 
      <br>
      接下来创建它的一个成员,这是一个TServerWinSocket的对象,这个才是真正封装SocketApi的类,等一个讨论它。事实上这个构造函数继承自它的父类TCustomServerSocket<br>
      <br>
      接下来调用InitSocket(FServerSocket);这是ServerSocket的祖先类TAbstractSocket的一个方法,传入参数是成员FserverSocket完成的功能是将ServerSocket的事件指针指向TServerWinSocket的事件,使其能处理Socket触发的事件。<br>
      <br>
      最后,设置FServerSocket允许连接的线程数,这里为10。<br>
      <br>
      <br>
      <br>
      好,现在回过头来看看FServerSocket := TServerWinSocket.Create(INVALID_SOCKET);做了什么:<br> 
      <br>
      (111)<br>
      <br>
      constructor TServerWinSocket.Create(ASocket: TSocket);<br> 
      <br>
      begin<br>
      <br>
  FConnections := TList.Create;<br> 
      <br>
  FActiveThreads := TList.Create;<br> 
      <br>
  FListLock := TCriticalSection.Create;<br> 
      <br>
  inherited Create(ASocket);<br> 
      <br>
  FAsyncStyles := [asAccept];<br> 
      <br>
      end;<br>
      <br>
      首先创建两个TList对象,一个是FConnections,代表各个处理客户连接的Socket,它对应于属性:property Connections[Index: Integer]: TCustomWinSocket,你可以通过这个属性对各个客户连接进行操作。FActiveThreads 管理由Connections 数组确定的的客户端连接线程TServerClientThread,它对应的属性是ActiveThreads,这个是只读属性,返回当前正在使用的TServerClientThread对象的个数。接下来创建互斥量对象,用于线程同步的处理。<br> 
      <br>
      到这里又调用其父类的构造函数,传递的参数就是ServerSocket给的值INVALID_SOCKET,(想想上面提到的这个值的意义)<br>
      <br>
      好,再继续跟踪,到其父类的构造函数去看一下,我们这时应该保留一个问题,按照WinSock的编程模式,刚开始应该是初始化Winsock.DLL,并调用绑定监听函数,这些API是什么在地方被调用呢?<br>
      <br>
      (1111)<br>
      <br>
      constructor TCustomWinSocket.Create(ASocket: TSocket);<br> 
      <br>
      begin<br>
      <br>
  inherited Create;<br> 
      <br>
  Startup;<br>
      <br>
  FSocketLock := TCriticalSection.Create;<br> 
      <br>
  FASyncStyles := [asRead, asWrite, asConnect, asClose];<br> 
      <br>
  FSocket := ASocket;<br> 
      <br>
  FAddr.sin_family := PF_INET;<br> 
      <br>
  FAddr.sin_addr.s_addr := INADDR_ANY;<br> 
      <br>
  FAddr.sin_port := 0;<br> 
      <br>
  FConnected := FSocket &lt;> INVALID_SOCKET;<br> 
      <br>
      end;<br>
      <br>
      首先调用TObject的构造函数,<br>
      <br>
      再调用Sartup;分析完这个函数再看里面的代码,这里可以猜测它里面会调用WSAStartup函数。<br>
      <br>
      接下来看到从ServerSocket传过来的参数指定给了TCustomWinSocket的成员,还有下面几个成员的设置,可以肯定,这里是对Socket进行初始化,结合开头所讲的知识,再看看源代码。应该不难理解了吧。<br>
      <br>
      再看看Startup的源码:<br>
      <br>
      (11111)<br>
      <br>
      procedure Startup;<br> 
      <br>
      var<br>
      <br>
  ErrorCode: Integer;<br> 
      <br>
      begin<br>
      <br>
  ErrorCode := WSAStartup($0101, WSAData);<br> 
      <br>
  if ErrorCode &lt;> 0 then<br> 
      <br>
    raise ESocketError.CreateResFmt(@sWindowsSocketError,<br> 
      <br>
      [SysErrorMessage(ErrorCode), ErrorCode, 'WSAStartup']);<br> 
      <br>
      end;<br>
      <br>
      这是一个全局函数,看到其中一行重要的代码了,<br>
      <br>
      ErrorCode := WSAStartup($0101, WSAData);WinSock就是在这里被初始化的。<br> 
      <br>
      而在服务端没有建立监听之时,Socket就在上面被默认设置了。<br>
      <br>
      好,现在ServerSocket的成员FserverSocket的创建就完成了,再回到最上面,<br>
      <br>
      FServerSocket := TServerWinSocket.Create(INVALID_SOCKET);之后,便是<br> 
      <br>
      InitSocket(FServerSocket);了,看看它的代码:<br>
      <br>
      (12)<br>
      <br>
      procedure TAbstractSocket.InitSocket(Socket: TCustomWinSocket);<br> 
      <br>
      begin<br>
      <br>
  Socket.OnSocketEvent := DoEvent;<br> 
      <br>
  Socket.OnErrorEvent := DoError;<br> 
      <br>
      end;<br>
      <br>
      正好和上面所说的一样,现在可以认为当Socket发生了事件(比如连接,接收等)之后,就是调用了DoEvent了,错误发生了也一样。不妨看看DoEvent代码:<br>
      <br>
      (121)<br>
      <br>
      procedure TAbstractSocket.DoEvent(Sender: TObject; Socket: TCustomWinSocket;<br> 
      <br>
  SocketEvent: TSocketEvent);<br> 
      <br>
      begin<br>
      <br>
  Event(Socket, SocketEvent);<br> 
      <br>
      end;<br>
      <br>
      里面是调用Event,再看看Event的声明:<br>
      <br>
      procedure Event(Socket: TCustomWinSocket; SocketEvent: TSocketEvent);virtual; abstract;<br> 
      <br>
      这是一个抽象函数,由此可以知道,TAbstractSocket是一个抽象的类,它只是封装了一般的操作,具体地都留到了它的子类中去了,所以它的子类一定覆盖了这个Event方法,而DoEvent中调用的Event实际上就是调用它子类的Event方法,这个就是典型的模板模式。<br>
      <br>
      好,看看它的子类有没有覆盖这个方法,果然在TCustomSocket中看到了这个方法,并实现了它,看看它的源代码:<br>
      <br>
      (1211)<br>
      <br>
      procedure TCustomSocket.Event(Socket: TCustomWinSocket; SocketEvent: TSocketEvent);<br> 
      <br>
      begin<br>
      <br>
  case SocketEvent of<br> 
      <br>
    seLookup: if Assigned(FOnLookup) then FOnLookup(Self, Socket);<br> 
      <br>
    seConnecting: if Assigned(FOnConnecting) then FOnConnecting(Self, Socket);<br> 
      <br>
    seConnect:<br>
      <br>
      begin<br>
      <br>
        FActive := True;<br> 
      <br>
        if Assigned(FOnConnect) then FOnConnect(Self, Socket);<br> 
      <br>
      end;<br>
      <br>
    seListen:<br>
      <br>
      begin<br>
      <br>
        FActive := True;<br> 
      <br>
        if Assigned(FOnListen) then FOnListen(Self, Socket);<br> 
      <br>
      end;<br>
      <br>
    seDisconnect:<br>

⌨️ 快捷键说明

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