您的位置:首页 > 编程语言 > Delphi

Delphi下的winsock-api 函数(二)

2020-03-02 03:47 1171 查看
Delphi下的winsock-api 函数(二)
2010年09月10日
  Winsock的初始化
  每个Winsock应用都必须加载Winsock DLL的相应版本。如果调用Winsock之前,没有加载Winsock库,这个函数就会返回一个SOCKET_ERROR,错误信息是WSANOTINITIALISED。
  加载winsock库是通过调用WSAStartup函数实现的。这个函数在DELPHI、winsock单元,定义如下: function WSAStartup(wVersionRequired: word; //Windows Sockets API提供的调用方可使用的最高版本号 var WSData: TWSAData//指向TWSAData数据结构的指针,用来接收windows sockets实现的细节 ): Integer; stdcall;
  WSAStartup函数必须是应用程序或动态链接库(DLL)调用的第一个Windows Sockets函数。它允许应用程序指明Windows Socket API的版本号及获得特定Windows Sockets实现的细节。应用程序只有在成功调用
  WSAStartup函数之后才能进一步调用WinSock API函数。也就是说,在用Winsock API开发通讯程序时,应首先调用WSAStartup函数,来初始化WinSock DLL,然后再开始使用别的WinSock API函数。在应用程序结束时,应该调用WSACleanup函数来释放为该应用程序分配的资源。
  function WSAStartup(wversionRequired:word;var WSData:TWSAData):Integer;stdcall;
  此函数在sckcomp中的使用
  procedure Startup;
  var
  ErrorCode:Integer;
  begin
  ErrorCode:=WSAStartup($0101,WSAData);
  if ErrorCode0 then
  raise ESocketError.createResFmt(@sWindowsSocketError,[sy sErrorMessage(ErrorCode),Errorcode,'WSAStartup']);
  end;
  初始化Windows Sockets DLL的使用。在Delphi中的引用声明如下:
  错误检查和控制
  对编写成功的Winsock应用程序而言,错误检查和控制是至关重要的。事实上,对Winsock函数来说,返回错误是非常常见的。但是,多数情况下,这些错误都是无关紧要的,通信仍可在套接字上进行。尽管其返回的值并非一成不变,但不成功的Winsock调用返回的最常见的值是SOCKET_ERROR。在详细介绍各个API调用时,我们打算指出和各个错误对应的返回值。实际上,SOCKET_ERROR常量是- 1。
  如果调用一个Winsock函数,错误情况发生了,就可用WSAGetLastError函数来获得一段代码,这段代码明确地表明发生的状况。该函数的定义如下:function WSAGetLastError: Integer; stdcall; 发生错误之后调用这个函数,就会返回所发生的特定错误的完整代码。
  1、服务器端的编程
  "服务器"在某种概念上我们可以理解为一个进程,它需要等待任意数量的客户机连接,以便为它们的请求提供服务。对服务器监听的连接来说,它必须在一个已知的名字上。在TCP/IP中,这个名字就是本地接口的I P地址,加上一个端口编号。每种协议都有一套不同的定址方案,所以有一种不同的命名方法。在Winsock中,第一步是将指定协议的套接字绑定到它已知的名字上。这个过程是通过API调用bind来完成的。下一步是将套接字置为监听模式。这时,用API函数listen来完成的。最后,若一个客户机试图建立连接,服务器必须通过accept或WSAAccept调用来接受连接。
  1.socket
  function socket(af, Struct, protocol: Integer): TSocket; stdcall;
  在加载Winsock DLL的相应版本之后,你要做的第一件事就是建立一个套接字了。在1.1版本中通过使用socket这个API来实现。第一个参数是你要使用的协议家族,第二个参数为套接字类型,最后一个参数指名你要使用的具体协议。下面的代码创建了一个使用IP协议家族中的TCP协议创建的流模式的套接字。
  skc := socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
  function socket(af: Integer; //地址描述符,目前只支持PF_INET格式
  Struct: Integer; //新套接字的类型描述
  protocol: Integer//套接字所用的协议
  ): TSocket; stdcall;
  参数Struct代表套接字的类型,其取值如表8-2所示。
  参数Struct取值 含义
  SOCK_STREAM 提供有序、可靠、双向的和基于连接的字节流,对应于TCP
  SOCK_DGRAM 支持无连接、不可靠和使用固定大小缓冲区的数据报服务,对应于UDP
  若无错误发生,Socket函数返回引用新套接字的描述符。否则的话返回SOCKET_ERROR错误。
  2. bind
  一旦为某种特定协议创建了套接字,就必须将套接字绑定到一个已知地址。bind函数可将指定的套接字同一个已知地址绑定到一起。该函数声明如下;
  function bind(s: TSocket; var addr: TSockAddr; namelen: Integer): Integer; stdcall;
  其中第一个参数s代表我们希望在上面等待客户连接的那个套接字第二个参数addr,针对自己打算使用的那个协议,必须把该参数填充一个地址缓冲区,第三个参数是要传递的、由协议决定的地址的长度。例如这样一段代码
  var
  ErrorCode : integer;
  SockAdd_In : TSockAddrIn;
  ...
  begin
  ...
  SockAdd_In.sin_family := PF_INET;
  SockAdd_In.sin_port := htons(FPort);
  SockAdd_In.sin_addr.S_addr := htonl(INADDR_ANY);
  ErrorCode := bind(FSock,SockAdd_In,sizeof(SockAdd_In));
  一旦出错, bind就会返回SOCKET_ERROR。对bind 来说,最常见的错误是WSAEADDRINUSE。如使用的是TCP/IP,那么WSAEADDRINUSE就表示另一个进程已经同本地IP接口和端口号绑定到了一起,或者那个IP接口和端口号处于TIME_WAIT状态。假如你针对一个套接字调用bind,但那个套接字已经绑定,便会返回WSAEFFAULT错误。
  将一本地地址和一套接字捆绑起来
  function bind(s: TSocket; //套接字
  var addr: TSockAddr; //套接字的本地地址
  namelen: Integer//缓冲区的长度
  ): Integer; stdcall;
  本函数用于未连接的数据报或者流类套接字,在Connect和Listen函数调用前使用,当用Socket函数创建套接字之后,它便存在于一个名字空间中,但未赋值。Bind函数通过给一个未命名套接字分配一个本地名字来为套接字建立本地捆绑(本机地址/端口号)。
  在Internet地址中,一个名字包括几个组成部分,对于SOCKET PGRAM和SOCK STREAM类套接字,名字由三部分组成:主机地址、协议号和用以区分应用的端口号。如果一个应用并不关心分配给它的地址和端口,则可将Internet地址设置为INADDR ANY,将端口号置0。如果Internet地址段为INADDR ANY,则可使用任意网络接口,在有多种主机环境下可简化编程。如果端口号置为0,则Window套接字将给Socket分配一个值在1024到5000之间的惟一的端口。
  如无错误发生,则Bind函数返回0,否则的话,将返回SOCKET_ERROR。
  3. listen
  我们接下来要做的是将套接字置入监听模式。bind函数的作用只是将一个套接字和一个指定的地址关联在一起。指示一个套接字等候进入连接的API函数则是listen,其定义如下:
  function listen(s: TSocket; backlog: Integer): Integer; stdcall;
  创建一个连接套接字并监听申请的连接,在Delphi中的引用声明如下:
  第一个参数同样是限定套接字。backlog参数指定了正在等待连接的最大队列长度。这个参数非常重要,因为完全可能同时出现几个服务器连接请求。
  function listen(s: TSocket; //一个已捆绑、未连接套接字
  backlog: Integer//等待连接队列的最大长度
  ): Integer; stdcall;
  例如,假定backlog参数为2。如果三个客户机同时发出请求,那么头两个会被放在一个"待决"(等待处理)队列中,以便应用程序依次为它们提供服务。
  而第三个连接会造成一个WSAECONNREFUSED错误。注意,一旦服务器接受了一个连接,那个连接请求就会从队列中删去,以便别人可继续发出请求。backlog
  参数其实本身就存在着限制,这个限制是由基层的协议提供者决定的。如果出现非法值,那么会用与之最接近的一个合法值来取代。除此以外,对于如何知道实际
  的backlog值,其实并不存在一种标准手段。与listen对应的错误是非常直观的。到目前为止,最常见的错误是WSAEINVAL。该错误通常意味着,你忘记在listen之前调用bind。否则,与bind调用相反,使用listen时可能收到WSAEADDRINUSE。这个错误通常是在进行bind调用时发生的。
  4. accept
  现在,我们已做好了接受客户连接的准备。这是通过accept或WSAAccept函数来完成的。
  accept格式如下:
  function accept(s: TSocket; addr: PSockAddr; addrlen: PInteger): TSocket; stdcall;
  其中,参数s是一个限定套接字,它处在监听模式。第二个参数应该是一个有效的SOCKADDR_IN结构的地址,而addrlen应该是SOCKADDR_IN结构的长度。对于属于另一种协议的套接字,应当用与那种协议对应的SOCKADDR结构来替换SOCKADDR_IN。通过对accpet函数的调用,可为待决连接队列中的第一个连接请求提供服务。accept函数返回后,addr结构中会包含发出连接请求的那个客户机的I P地址信息,而addrlen参数则指出结构的长度。此外,accept会返回一个新的套接字描述符,它对应于已经接受的那个客户机连接。对于该客户机后续的所有操作,都应使用这个新套接字。至于原来那个监听套接字,它仍然用于接受其他客户机连接,而且仍处于监听模式。
  2、客户机API函数
  客户机要简单得多,建立成功连接所需的步骤也要少得多。客户机只需三步操作:
  1) 用socket创建一个套接字。
  2) 解析服务器名(以基层协议为准)。
  3) 用connect初始化一个连接。
  connect函数
  关于创建套接字和解析服务器名的方法,前面已有简单叙述,这里介绍最后一步连接的API函数。我们先来看看该函数的Winsock 1版本,其定义如下:
  function connect(s: TSocket; var name: TSockAddr; namelen: Integer): Integer; stdcall;
  该函数的参数是相当清楚的: s是即将在其上面建立连接的那个有效TCP套接字; name是针对TCP(说明连接的服务器)的套接字地址结构(SOCKADDR_IN);namelen则是名字参数的长度。
  3、数据传输
  收发数据是网络编程的主题。要在已建立连接的套接字上接收数据,在Winsock 1版本中,可用这个A P I函数:
  向一个已链接的套接字发送数据。在Delphi中的引用声明如下:
  function send(s: TSocket; //套接字
  var Buf; //待发送的缓冲区
  len: Integer; //缓冲区的长度
  flags: Integer//调用执行的方式
  ): Integer; stdcall;
  Send函数适用于向已连接的数据报或流式套接字发送数据。对于数据报类套接字,必需注意发送数据的长度不应超过通讯子网的IP包最大长度。IP包最大长度在WSAStartup函数调用返回的WSAData的iMaxUdpDg元素中。如果数据太长而无法自动通过下层协议,则返回WSAEMESGSIZE错误,数据不会被发送。请注意成功地完成Send函数调用并不意味着数据传送到达。
  若无错误发生,Send函数返回所发送数据的总数;否则的话,返回SOCKET_ERROR错误。应用程序可通过WSAGetLastERROR函数获取响应的错误代码。
  int send (
  SOCKET s,
  const char FAR * buf,
  int len,
  int flags
  );
  delphi中声明如下:
  function send(s: TSocket; var Buf; len, flags: Integer): Integer; stdcall;
  SOCKET参数是已建立连接的套接字,将在这个套接字上发送数据。第二个参数buf,则是字符缓冲区,区内包含即将发送的数据。第三个参数len,指定即将发送的缓冲区内的字符数。最后,flags可为0、MSG_DONTROUTE或MSG_OOB。另外, flags还可以是对那些标志进行按位"或运算"的一个结果。MSG_DONTROUTE标志要求传送层不要将它发出的包路由出去。由基层的传送决定是否实现这一请求(例如,若传送协议不支持该选项,这一请求就会被忽略)。MSG_OOB标志预示数据应该被带外发送。对返回数据而言,send返回发送的字节数;若发生错误,就返回SOCKET_ERROR。常见的错误是WSAECONNABORTED,这一错误一般发生在虚拟回路由于超时或协议有错而中断的时候。发生这种情况时,应该关闭这个套接字,因为它不能再用了。远程主机上的应用通过执行强行关闭或意外中断操作重新设置虚拟虚路时,或远程主机重新启动时,发生的则是WSAECONNRESET错误。再次提醒大家注意,发生这一错误时,应该关闭这个套接字。最后一个常见错误是WSAETIMEOUT,它发生在连接由于网络故障或远程连接系统异常死机而引起的连接中断时。
  在已建立了连接的套接字上接收数据也有个函数:
  Recv函数
  从一个套接字接收数据,在Delphi中的引用声明如下:
  function recv(s: TSocket; //要接收的套接字
  var Buf; //接收的缓冲区
  len: Integer; //接收缓冲区长度
  flags: Integer//指定调用方式
  ): Integer; stdcall;
  如无错误发生,Recv函数返回读入的字节数,如果连接已终止,返回0,否则的话,返回SOCKET_ERROR错误。
  int recv (
  SOCKET s,
  char FAR* buf,
  int len,
  int flags
  );
  delphi中声明如下:
  function recv(s: TSocket; var Buf; len, flags: Integer): Integer; stdcall;
  从API的原型中,我们可以看到,所有关系到收发数据的缓冲都属于简单的char类型。也就是说,这些函数没有"Unicode"版本。所有收发函数返回的错误代码都是SOCKET_ERROR。一旦返回错误,系统就会调用WSAGetLastError获得详细的错误信息。最常见的错误是WSAECONNABORED和WSAECONNRESET。两者均涉及到即将关闭连接这一问题-要么通过超时,要么通过通信方关闭连接。另一个常见错误是WSAEWOULDBLOCK,一般出现在套接字处于非暂停模式或异步状态时。这个错误主要意味着指定函数暂不能完成。
  4、流协议
  由于大多面向连接的协议同时也是流式传输协议,所以,在此提一下流式协议。对于流套接字上收发数据所用的函数,需要明白的是:它们不能保证对请求的数据量进行读取或写入。比如说,一个2048字节的字符缓冲,准备用send函数来发送它。对send函数而言,可能会返回已发出的少于2048的字节。是因为对每个收发数据的套接字来说,系统都为它们分配了相当充足的缓冲区空间。在发送数据时,内部缓冲区会将数据一直保留到应该将它发到线上为止。几种常见的情况都可导致这一情形的发生。比方说,大量数据的传输可以令缓冲区快速填满。同时,对TCP/IP来说,还有一个窗口大小的问题。接收端会对窗口大小进行调节,以指出它可以接收多少数据。如果有大量数据涌入接收端,接收端就会将窗口大小设为0,为待发数据做好准备。对发送端来说,这样会强令它在收到一个新的大于0的窗口大小之前,不得再发数据。在使用send调用时,缓冲区可能只能容纳1024个字节,这时,便有必要再提取剩下的1024个字节。
  5、中断连接
  一旦完成任务,就必须关掉连接,释放关联到那个套接字句柄的所有资源。要真正地释放与一个开着的套接字句柄关联的资源,执行closesocket调用即可。但要明白这一点,closesocket可能会带来负面影响(和如何调用它有关),即可能会导致数据的丢失。鉴于此,应该在调用closesocket函数之前,利用shutdown函数从容中断连接。接下来,我们来谈谈这两个A P I函数。
  1. shutdown
  为了保证通信方能够收到应用发出的所有数据,对一个编得好的应用来说,应该通知接收端"不再发送数据"。同样,通信方也应该如此。这就是所谓的"从容关闭"方法,并由shutdown函数来执行。shutdown的定义如下:
  int shutdown (
  SOCKET s,
  int how
  );
  how参数可以是下面的任何一个值: SD_RECEIVE、SD_SEND或SD_BOTH。如果是SD_RECEIVE,就表示不允许再调用接收函数。这对底部的协议层没有影响。另外,对TCP套接字来说,不管数据在等候接收,还是数据接连到达,都要重设连接。尽管如此, UDP套接字上,仍然接受并排列接入的数据。如果选择SE_SEND,表示不允许再调用发送函数。对TCP套接字来说,这样会在所有数据发出,并得到接收端确认之后,生成一个FIN包。最后,如果指定SD_BOTH,则表示取消连接两端的收发操作。
  2. closesocket
  closesocket函数用于关闭套接字,它的定义如下:
  int closesocket (
  SOCKET s
  );
  如果没有对该套接字的其他引用,所有与其描述符关联的资源都会被释放。其中包括丢弃所有等侯处理的数据。对这个进程中任何一个线程来说,它们执行的待决异步调用都在未投递任何通知消息的情况下被删除。待决的重叠操作也被删除。与该重叠操作关联的任何事件,完成例程或完成端口能执行,但最后会失败,出现WSA_OPERATION_ABORTED错误。还有一点会对closesocket的行为产生影响:套接字选项SO_LINGER是否已经设置。LINGER是"拖延"的意思。SO_LINGER用于控制在未发送的数据排队等候于套接字上的时候,一旦执行了closesocket命令,那么该采取什么样的行动。
  4.Connect函数
  与一个套接字建立链接,在Delphi中的引用声明如下:
  function connect(s: TSocket; //套接字
  var name: TSockAddr; //欲进行连接的套接字的地址
  namelen: Integer//欲进行连接的套接字的地址长度
  ): Integer; stdcall;
  本函数用于创建与指定外部套接字的连接。S函数指定了一个未链接的数据报或流类套接字。如无错误发生,则Connect函数返回0,否则的话,返回SOCKET_ERROR错误。
  5.Htons函数
  将主机的16位无符号短整型数转换为TCP/IP网络字节顺序,在Delphi中的引用声明如下:
  function htons(hostshort: u_short): u_short; stdcall;
  6.Inet addr函数
  将一个点分表示法表示的字符串地址转化成网际地址in_addr形式,在Delphi中的引用声明如下:
  function inet_addr(cp: PChar): u_long; stdcall;
  9.Select函数确定一个或多个套接字的状态,如需要可以等待。在Delphi中的引用声明如下:function select(nfds: Integer; //本地参数忽略
  readfds, writefds, exceptfds: PFDSet;//指向一组可读(可写、错误)检查的套接字
  timeout: PTimeVal//select函数最多等待时间,设置为nil,则为阻塞操作
  ): Longint; stdcall;
  本函数用于确定一个或多个套接字的状态。对每一个套接字,调用者可查询它的可读性、可写性以及错误状态信息。用TFDSet结构表示一组等待检查的套接字。在调用返回时,这个结构存有满足一定套接字组的子集,并且Select函数返回满足条件的套接字的数目。Select函数调用返回就绪状态并且已经包含在TFDSet结构中的描述符的总数;如果超时则返回0;返回的话,返回SOCKET_ERROR。
  
  • 点赞
  • 收藏
  • 分享
  • 文章举报
站内首发文章 iteye_10950 发布了16 篇原创文章 · 获赞 0 · 访问量 400 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: