基于Delphi使用API实现Sock通讯.doc

上传人:h**** 文档编号:138359 上传时间:2018-07-10 格式:DOC 页数:32 大小:160.50KB
下载 相关 举报
基于Delphi使用API实现Sock通讯.doc_第1页
第1页 / 共32页
基于Delphi使用API实现Sock通讯.doc_第2页
第2页 / 共32页
基于Delphi使用API实现Sock通讯.doc_第3页
第3页 / 共32页
基于Delphi使用API实现Sock通讯.doc_第4页
第4页 / 共32页
基于Delphi使用API实现Sock通讯.doc_第5页
第5页 / 共32页
点击查看更多>>
资源描述

1、基于 Delphi 使用 API实现 Sock 通讯编程 一、定址 要通过 Winsock 建立通信,必须了解如何利用指定的协议为工作站定址。 Winsock 2 引入了几个新的、与协议无关的函数,它们可和任何一个地址家族一起使用;但是大多数情况下,各协议家族都有自己的地址解析机制,要么通过一个函数,要么作为一个投给 getsockopt 的选项。 因为目前网络编程中用的最多最普遍的也许就是 TCP/IP 协议了,所以这里主要介绍此协议下的 WinSock 编程。 1、 IP 网际协议( Internet Protocol, IP)是一种用于互联网的网络协议,已经广为人知。它可广泛用于大多数计

2、算机操作系统上,也可用于大多数局域网 LAN(比如办公室小型网络)和广域网 WAN(比如说互联网)。从它的设计看来, IP 是一个无连接的协议,不能保证数据投递万 无一失。两个比它高级的协议( TCP 和 UDP)用于依赖 IP 协议的数据通信。 2、 TCP 面向连接的通信是通过“传输控制协议”( Transmission Control Protocol, TCP)来完成的。 TCP 提供两台计算机之间的可靠无错的数据传输。应用程序 利用 TCP 进行通信时,源和目标之间会建立一个虚拟连接。这个连接一旦建立,两台计算机之间就可以把数据当作一个双向字 节流进行交换。 3、 UDP 无连接通信

3、是通过“用户数据报协议”( User Datagram Protocol, UDP)来完成的。 UDP 不保障可靠数据的传输,但能够向若干个目标发送数据,接收发自若干个源的数据。简单地说,如果一个客户机向服务器发送数据,这一数据会立即发出,不管服务器是否已准备接收数据。如果服务器收到了客户机的数据,它不会确认收到与否。数据传输方法采用的是数据报。 TCP 和 UDP 两者都利用 IP来进行数据传输,一般称为 TCP/IP 和 UDP/IP。 Winsock通过 AF_INET 地址家族为 IP 通信定址。 4、定址 IP 中,计算机都分配有一个 IP 地址,用一个 32 位数来表示,正式的称呼

4、是“ IPv4 地址”。客户机需要通过 TCP 或 UDP 和服务器通信时,必须指定服务器的 IP地址和服务端口号。另外,服务器打算监听接入客户机请求时,也必须指定一个 IP 地址和一个端口号。Winsock 中,应用通过 SOCKADDR_IN 结构来指定 I P 地址和服务端口信息,该结构的 在 DELPHI 中的声明如下: sockaddr_in = record case Integer of 0: (sin_family: u_short; sin_port: u_short; sin_addr: TInAddr; sin_zero: array0.7 of Char); 1: (s

5、a_family: u_short; sa_data: array0.13 of Char) end; TSockAddrIn = sockaddr_in; 在 DELPHI 中, sockaddr_in 结构被声明为了一个变体记录(关于变体记录可以参看我其他的文章)。 sin_family: 字段必须设为 AF_INET,以告知 Winsock 我们此时正在使用 I P地址家族。 准备使用哪个 TCP 或 UDP 通信端口来标识服务器服务这一问题,则由 sin_port字段定义。在选择端口时,应用必须特别小心,因为有些可用端口号是为“已知的”(即固定的)服务保留的(比如说文件传 输协议和超文

6、本传输协议,即 FTP 和 HTTP)。“已知的协议”,即固定协议,采用的端口由“互联网编号分配认证( IANA)”控制和分配, RFC 1700 中说明编号。从本质上说,端口号分为下面这三类:“已知”端口、已注册端口、动态和(或)私用端口。 01023 由 IANA 控制,是为固定服务保留的。 1024 49151 是 IANA 列出来的、已注册的端口,供普通用户的普通用户进程或程序使用。 49152 65535 是动态和(或)私用端口。 普通用户应用应该选择 1024 49151 之间的已 注册端口,从而避免端口号已被另一个应用或系统服务所用。此外, 49152 65535 之间的端口可自

7、由使用,因为 IANA 这些端口上没有注册服务。在使用 bind API 函数时,如果一个应用和主机上的另一个应用采用的端口号绑定在一起,系统就会返回 Winsock 错误 WSAEADDRINUSE。 sockaddr_in 结构的 sin_addr 字段用于把一个 IP 地址保存为一个 4 字节的数,它是无符号长整数类型。根据这个字段的不同用法,还可表示一个本地或远程 IP 地址。 IP 地址一般是用“互联网标准点分表示法”(像 a.b.c.d 一样)指定的,每个字母代表一个字节数,从左到右分配一个 4 字节的无符号长整数。最后一个字段 sin_ zero ,只充当填充项的职责,以使 so

8、ckaddr_in 结构和 SOCKADDR 结构的长度一样。一个有用的、名为 inet_addr 的支持函数,可把一个点式 IP 地址转换成一个 32 位的无符号长整数。它的定义如下: unsigned long inet_addr( const char FAR *cp ); cp 字段是一个空中止字符串,它认可点式表示法的 IP 地址。注意,这个函数把 IP 地址当作一个按网络字节顺序排 列的 32 位无符号长整数返回 . 1. 特殊地址 对于特定情况下的套接字行为,有两个特殊 IP 地址可对它们产生影响。特殊地址 INADDR_ANY 允许服务器应用监听主机计算机上面每个网络接口上的客

9、户机活动。一般情况下,在该地址绑定套接字和本地接口时,网络应用才利用这个地址来监听连接。如果你有一个多址系统,这个地址就允许一个独立应用接受发自多个接口的回应。 特殊地址 INADDR_BROADCAST 用于在一个 IP 网络中发送广播 UDP 数据报。要使用这个特殊地址,需要应用设臵套接字选项 SO_BROADCAST。 2. 字节排序 针对“大头”( big-endian)和“小头”( little-endian)形式的编号,不同的计算机处理器的表示方法有所不同,这由各自的设计决定。比如, Intel 86 处理器上,用“小头”形式来表示多字节编号:字节的排序是从最无意义的字节到最有意义

10、的字节。在计算机中把 IP 地址和 端口号指定成多字节数时,这个数就按“主机字节”( host-byte)顺序来表示。但是,如果在网络上指定 I P 地址和端口号,“互联网联网标准”指定多字节值必须用“大头”形式来表示(从最有意义 的字节到最无意义的字节),一般称之为“网络字节”( network-byte)顺序。有一系列的函数可用于多字节数的转换,把它们从主机字节顺序转换成网络字节顺序,反之亦然。下面四个 API 函数便将一个数从主机字节顺序转换成网络字节顺序: HTONL,htons,WSAHtons,WSAHtonl 下面这四个是前面四个函数的反向函数:它们把网络字节顺序转换成主机字节顺

11、序: ntohl,WSANtohl,ntohs,WSANtohs Winsock 的初始化 每个 Winsock 应用都必须加载 Winsock DLL 的相应版本。如果调用 Winsock 之前,没有加载 Winsock 库,这个函数就会返回一个 SOCKET_ERROR,错误信息是 WSANOTINITIALISED。 加载 Winsock 库是通过调用 WSAStartup 函数实现的。这个函数在 DELPHI 中的 WinSock单元被定义如下: function WSAStartup(wVersionRequired: word; var WSData: TWSAData): Int

12、eger; stdcall; ScktComp 中这样使用了此函 数 procedure Startup; var ErrorCode: Integer; begin ErrorCode := WSAStartup($0101, WSAData); if ErrorCode 0 then raise ESocketError.CreateResFmt(sWindowsSocketError, SysErrorMessage(ErrorCode), ErrorCode, WSAStartup); end; 错误检查和控制 对编写成功的 Winsock 应用程序而言,错误检查和控制是至关重要的。事

13、实上,对 Winsock 函数来说,返回错误是非常常见的。但是,多数情况下,这些错误都是无关紧要的,通信仍可在套接字上进行。尽管其返回的值并非一成不变,但不成功的 Winsock 调用返回的最常见的值是 SOCKET_ERROR。在详细介绍各个 API 调用时,我们打算指出和各个错误对应的返回值。实际上, SOCKET_ERROR 常量是 - 1。 如果调用一个 Winsock 函数,错误情况发生了,就可用 WSAGetLastError 函 数来获得一段代码,这段代码明确地表明发生的状况。该函数的定义如下: function WSAGetLastError: Integer; stdcall

14、; 发生错误之后调用这个函数,就会返回所发生的特定错误的完整代码。 针对 TCP/IP 的 WinSock 编程 因为 TCP 协议是一个面向连接的协议,它存在一个概念上的“服务器”端和“客户端”,在编码时,要区分对待。 1、服务器端的编程 “服务器”在某种概念上我们可以理解为一个进程,它需要等待任意数量的客户机连接,以便为它们的请求 提供服务。对服务器监听的连接来说,它必须在一个已知的名字上。在 TCP/IP 中,这个名字就是本地接口的 I P 地址,加上一个端口编号。每种协议都有一套不同的定址方案,所以有一种不同的命名方法。在 Winsock 中,第一步是将指定协议的套接字绑定到它已知的名

15、字上。这个过程是通过 API 调用 bind 来完成的。下一步是将套接字臵为监听模式。这时,用 API 函数 listen 来完成的。最后,若一个客户机试图建立连接,服务器必须通过 accept 或 WSAAccept 调用来接受连接。 1.socket function socket(af, Struct, protocol: Integer): TSocket; stdcall; 在加载 Winsock DLL 的相应版本之后,你要做的第一件事就是建立一个套接字了。在 1.1 版本中通过使用 socket 这个 API 来实现。第一个参数是你要使用的协议家族,第二个参数为套接字类型,最后一

16、个参数指名你要使用的具体协议。下面的代码创建了一个使用 IP 协议家族中的 TCP 协议创建的流模式的套接字。 skc := socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); 2. bind 一旦为某种特定协议创建了套接字,就必须将套接字绑定到一个已知地址。 bind 函数可将指定的套接字同一个已知地址绑定到一起。该函数声明如下; function bind(s: TSocket; var addr: TSockAddr; namelen: Integer): Integer; stdcall; 其中第一个参数 s 代表我们希望在上面等待客户连接的那个套接字

17、第二个参数 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(Soc

18、kAdd_In); 一旦出错, bind 就会返回 SOCKET_ERROR。对 bind 来说,最常见的错误是 WSAEADDRINUSE。如使用的是 TCP/IP,那么 WSAEADDRINUSE 就表示另一个进程已经同本地IP 接口和端口号绑定到了一起,或者那个 IP 接口和端口号处于 TIME_WAIT 状态。假如你针对一个套接字调用 bind,但那个套接字已经绑定,便会返回 WSAEFFAULT 错误。 3. listen 我们接下来要做的是将套接字臵入监听模式。 bind 函数的作用只是将一个套接字和一个指定的地址关联在一起。指示一个套接字等候进入连接的 API 函数则是 list

19、en,其定义如下: function listen(s: TSocket; backlog: Integer): Integer; stdcall; 第一个参数同样是限定套接字。 backlog 参数指定了正在等待连接的最大队列长度。这个参数非常重要,因为完全可能同时出现几个服务器连接请求。例如,假定 backlog 参数为2。如果三个客户机同时发出请求,那么头两个会被放在一个“待决”(等待处 理)队列中,以便应用程序依次为它们提供服务。而第三个连接会造成一个 WSAECONNREFUSED 错误。注意,一旦服务器接受了一个连接,那个连接请求就会从队列中删去,以便别人可继续发出请求。 back

20、log 参数其实本身就存在着限制,这个限制是由基层的协议提供者决定的。如果出现非法值,那么会用与之最接近的一个合法值来取代。除此以外,对于如何知道实际的 backlog 值,其实并不存在一种标准手段。与 listen 对应的错误是非常直观的。到目前为止,最常见的错误是 WSAEINVAL。该错误通常意味着,你忘记在 listen 之前调用 bind。否则,与 bind 调用相反,使用 listen 时可能收到 WSAEADDRINUSE。这个错误通常是在进行 bind调用时发生的。 4. accept 现在,我们已做好了接受客户连接的准备。这是通过 accept 或 WSAAccept 函数来

21、完成的。 accept 格式如下: function accept(s: TSocket; addr: PSockAddr; addrlen: PInteger): TSocket; stdcall; 其中,参数 s 是一个限定套接字,它处在 监听模式。第二个参数应该是一个有效的 SOCKADDR_IN 结构的地址,而 addrlen 应该是 SOCKADDR_IN 结构的长度。对于属于另一种协议的套接字,应当用与那种协议对应的 SOCKADDR 结构来替换 SOCKADDR_IN。通过对 accpet 函数的调用,可为待决连接队列中的第一个连接请求提供服务。 accept 函数返回后,add

22、r 结构中会包含发出连接请求的那个客户机的 I P 地址信息,而 addrlen 参数则指出结构的长度。此外, accept 会返回一个新的套接字描述符,它对应于已经接受的那个客户机连接。对于该客 户机后续的所有操作,都应使用这个新套接字。至于原来那个监听套接字,它仍然用于接受其他客户机连接,而且仍处于监听模式。 2、客户机 API 函数 客户机要简单得多,建立成功连接所需的步骤也要少得多。客户机只需三步操作: 1) 用 socket 创建一个套接字。 2) 解析服务器名(以基层协议为准)。 3) 用 connect 初始化一个连接。 connect 函数 关于创建套接字和解析服务器名的方法,

23、前面已有简单叙述,这里介绍最后一步连接的API 函数。我们先来看看该函数的 Winsock 1 版本,其 定义如下: function connect(s: TSocket; var name: TSockAddr; namelen: Integer): Integer; stdcall; 该函数的参数是相当清楚的: s 是即将在其上面建立连接的那个有效 TCP 套接字; name 是针对 TCP(说明连接的服务器)的套接字地址结构( SOCKADDR_IN); namelen 则是名字参数的长度。 3、数据传输 收发数据是网络编程的主题。要在已建立连接的套接字上接收数据,在 Winsock

24、1 版本中,可 用这个 A P I 函数: 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 或 MS

25、G_OOB。另外, flags 还可以是对那些标志进行按位“或运算”的一个结果。 MSG_DONTROUTE 标志要求传送层不要将它发出的包路由出去。由基层的传送决定是否实现这一请求(例如,若传送协议不支持该选项,这一请求就会被忽略)。 MSG_OOB 标志预示数据应该被带外发送。对返回数据而言, send返回发送的字节数;若发生错误,就返回 SOCKET_ERROR。常见的错误是 WSAECONNABORTED,这一错 误一般发生在虚拟回路由于超时或协议有错而中断的时候。发生这种情况时,应该关闭这个套接字,因为它不能再用了。远程主机上的应用通过执行强行关闭或意外中断操作重新设臵虚拟虚路时,或

26、远程主机重新启动时,发生的则是 WSAECONNRESET 错误。再次提醒大家注意,发生这一错误时,应该关闭这个套接字。最后一个常见错误是WSAETIMEOUT,它发生在连接由于网络故障或远程连接系统异常死机而引起的连接中断时。 同样地,在已建立了连接的套接字上接收数据也有个函数: int recv ( SOCKET s, char FAR* buf, int len, int flags ); delphi 中声明如下: function recv(s: TSocket; var Buf; len, flags: Integer): Integer; stdcall; 从 API 的原型中,

27、我们可以看到,所有关系到收发数据的缓冲都属于简单的 char 类型。也就是说,这些函数没有“ Unicode”版本。所有收发函数返回的错误代码都是 SOCKET_ERROR。一旦返回错 误,系统就会调用 WSAGetLastError 获得详细的错误信息。最常见的错误是 WSAECONNABORED 和 WSAECONNRESET。两者均涉及到即将关闭连接这一问题 要么通过超时,要么通过通信方关闭连接。另一个常见错误是 WSAEWOULDBLOCK,一般出现在套接字处于非暂停模式或异步状态时。这个错误主要意味着指定函数暂不能完成。 4、流协议 由于大多面向连接的协议同时也是流式传输协议,所以,

28、在此提一下流式协议。对于流套接字上收发数据所用的函数,需要明白的是:它们不能保证对请求的数据量 进行读取或写入。比如说,一个 2048 字节的字符缓冲,准备用 send 函数来发送它。对 send 函数而言,可能会返回已发出的少于 2048 的字节。是因为对每个收发数据的套接字来说,系统都为它们分配了相当充足的缓冲区空间。在发送数据时,内部缓冲区会将数据一直保留到应该将它发到线上为止。几种常见的情况都可导致这一情形的发生。比方说,大量数据的传输可以令缓冲区快速填满。同时,对 TCP/IP 来说,还有一个窗口大小的问题。接收端会对窗口大小进行调节,以指出它可以接收多少数据。如果有大量数据涌入接收

29、端,接收端就会将窗口大小设为 0,为待发数据做好准备。对发送端来说,这样会强令它在收到一个新的大于 0 的窗口大小之前,不得再发数据。在使用 send 调用时,缓冲区可能只能容纳 1024 个字节,这时,便有必要再提取剩下的 1024 个字节。 5、中断连接 一旦完成任务,就必须关掉连接,释放关联到那个套接字句柄的所有资源。要真正地释放与一个开着的套接字句柄关联的资源,执行 closesocket 调用即可。但要明白这一点, closesocket 可能会带来负面影响(和如何调用它有关),即可能会导致数据的丢失。鉴于此,应该在调用 closesocket 函数之前,利用 shutdown 函数

30、从容中断连接。接下来,我们来谈谈这两个 API 函数。 1. shutdown 为了保证通信方能够收到应用发出的所有数据,对一个编得好的应用来说,应该通知接收端“不再发送数据”。同样,通信方也应该如此。这就是所谓的“从容关闭”方法,并由 shutdown 函数来执行。 shutdown 的定义如下: int shutdown ( SOCKET s, int how ); how 参数可以是下面的任何一个值: SD_RECEIVE、 SD_SEND或 SD_BOTH。如果是SD_RECEIVE,就表示不允许再调用接收函数。这对底部的协议层没有影响。另外,对 TCP 套接字来说,不管数据在等候接收

31、,还是数据接连到达,都要重设连接。尽管如此, UDP 套接字上,仍然接受并排列接入的数据。如果选择 SE_SEND,表示不允许再调用发送函数。对 TCP 套接字来说,这样会在所有数据发出,并得到接收端确认之后,生成一个 FIN包。最后,如果指定 SD_BOTH,则表示取消连接两端的收发操作。 2. closesocket closesocket 函数用于关闭套接字,它的 定义如下: int closesocket ( SOCKET s ); 如果没有对该套接字的其他引用,所有与其描述符关联的资源都会被释放。其中包括丢弃所有等侯处理的数据。对这个进程中任何一个线程来说,它们执行的待决异步调用都在未投递任何通知消息的情况下被删除。待决的重叠操作也被删除。与该重叠操作关联的任何事件,完成例程或完成端口能执行,但最后会失败,出现 WSA_OPERATION_ABORTED 错误。还有一点会对 closesocket 的行为产生影响:套接字选项 SO_LINGER 是否已经设臵。 LINGER 是“拖延”的意思。 SO_LINGER 用于控制在未发送的数据排队等候于套接字上的时候,一旦执行了 closesocket 命令,那么该采取什么样的行动。 应用 WinSock 建立客户机 /服务器程序的活动图

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 学术论文资料库 > 毕业论文

Copyright © 2018-2021 Wenke99.com All rights reserved

工信部备案号浙ICP备20026746号-2  

公安局备案号:浙公网安备33038302330469号

本站为C2C交文档易平台,即用户上传的文档直接卖给下载用户,本站只是网络服务中间平台,所有原创文档下载所得归上传人所有,若您发现上传作品侵犯了您的权利,请立刻联系网站客服并提供证据,平台将在3个工作日内予以改正。