文章目录
前言 设计Winsock
在第1章,我们介绍Winsock 的基础知识。本文我们将深入讲述系统体系结构,讨论如何使Winsock 适应总体系统设计的要求。然后讨论协议特性,并介绍应用程序如何放举已安装的协议。最后讨论如何通过 sockel 和 WSASocket 函数创套接子,以及这些函数如何与 Winock 编录进行交互
2.1 系统体系结构
在了解系统休系结构之前,请注意:将火的版本中,驱动程序名称和 DLL 等可能会改变,所以不能完全依赖于冰章中讲述的具体细节设计应用程序,而应在全面了解系统总体设计的情况下,尽量遵守 Winsock 规范。下面所述内容适用十基于 Windows NT 的操作系统(本节最后将介绍 WindowsNT 和Windows 95、Windows 98 及Windows Me之间的一些区别)。
大部分 Winsock API 在 WS2_32DLL 中实现,在 WINSOCK2. 中声明,的例外是MSWSOCKDLL 中的微软特有的 Winsock 扩展,如 TransmitFile AcceptEx 等:因为这些扩展并不是 Winsock 正式规范的一部分,而是由微软所增加的,所以一些扩展 API 仅在某些特定版本的Windows 操作系统中(第6 章将详组叙还这些 API)
应用程序在调用 Winsock API,会调用 WS2_32.DLL。Winsock DLL 行某种参数输证,进而确定应该将调用发送到哪、种协议服务供祝序。在 Winsock 编录中可能安了多个提供程,而WS2 32DLL 则确定由哪个提供程序处理该调用。
提供程序分为两种类型:
- 基础提供程序
- 分层提供程序。
基础提供程序位于传输协议顶端,如微软TCP/IP和UDP/IP 提供程序,实现QOS(请见第 10 章)的 RSVP(Resource Reservation Prolocol
,资源保留协议)提供程序。微软某础提供序中 MSAFD.DLL 和 MSWSOCK.DLL 成,但实际上为TCP/IP、IPX/SPX、NetBIOS、AppleTalk 等个体协议公开了一个或多个提供程序本稍后将叙述怎样编程列举系统中可用的提供程序。
分层提供程序位于 WS2_32.DLL 之下,基础提供程序之上;能截获并操纵 Winsock 调用,如果个应用程序利用分层提供程序创建了一个套接字,分层提供程序将使用该套接宁截获所有的 Winsock调用。分层提供程序可能会阻塞、修改调用,也可能将未经修改的调用传递给底层的提供程序。第12 童将详介绍分层服务提供程序。
一旦Winsock 调用到达基础提供程序,基础提供程序将接着依次调用 Winsock 核心模式组件。与其他操作系统不问,Windows NT 传输协议没有类似 Winsock 的接口,这使得应用程序无法用这些接口同它们直接对话。Windows NT 传输协议使用种更为通用的 API 传输驱动程序接口(Transport DriverInterface,TDI),这种API所具的通用性能,使Windows NT 子系统不局限在特定旧版本的网络编程接口上。套孩宁模拟出 Winsock 核心模式驱动序(目前 AFDSYS 中)实现。在为应用程序提供类似丁套接字的接口时,这个驱动程序负责相关的连接和缓冲管理。AFD 接着将 TDI传递给传输协议驱动程序(参见图 2.1)。
对Windows95 Windows98和Windows Me来说,除核心模式组件之外总体结构和Windows NT相似。Windows 95、Windows 98 和 Windows Me在VXD 内实现驱动程序,所以没有AFDSYS和TCPIPSYS。这样,它们驱动程序就为AFVXDVXD、WSOCK2VXD和WSOCKVXD等。当然,正如前这所述,应用程序中不应该考虑实际的驰动程序或系统组件的文件名。Winsock API 规范在所有版本的 Windows 操作系统中都可用。
2.2 协议的特征
在关于 Winsock 内核的一些信息之后,下面接着讨论以访问的协议。
前面已经提到,Winsock 提供程序实现的协议会表现出某种特征。Windows 支持各种不同的输协议,如TCP、UDPIPX及SPX等。每种协议各自有着不同的特性。有些要求在收发数据之前建立连接,有些则不能确你数的可靠性或完整性。本节将叙述这些协议的各种特征.
2.2.1 面向消息
对每个离散的写命令米说,如果某个传送协议只将那些学节当做一条独立的消息在网上传输,我们就说这种协议是面向消息的。同时,这还意味着接收端发出数据请求后,接收到的数据是发送端写的一条离散消息。接收端只能收到一条消息。
例如,在图 2.2 中,无边的工作站向右边的工作站提交了3条长度分别为 128、64 和32 字节的消息,作为接收端的工作站发出3条recv 调用,调用的缓冲区是256 个字节。每次调用次返回 128、64 和32 个字节第次recv 调用不会把所有的3个数据包都返回,即使它已经全部收到这些数据包。
这种原理被称为“保留消息边界”(preserving messagcboundaries)
,“一般在交换结构化数据的时候出现。 “保留消息边界”的一个范例是网络游戏。
在网络游戏中,每个玩家均向别的玩家发出一个带有物理位置信息的数据包。这种通信后面隐令的代码很简单:
一个玩家发出一个数据包请求,而且也从参与该游戏的另的玩家处获得个物理位置信息的包。
2.2.2 面向流
不保留消息边界的协议通常称作“基于流的协议”。要注意基于流这一术语常用来指代附加特性。
流服务的定义是连续的数据传输:
不管消息边界是否存在,接收端都会尽量地读取有效数据。
对发送端来说,流服务允许系统将原始消息分解成小消息或把几条消息积累在一起,并形成一个较大的数据包。对接收端来说,数据到达网络堆栈,网络准栈就开始读收它,并将它放在缓冲区中等状进程处理。在进程请求处理大量数据时,系统会尽快返回史多的数据。织有个前提,那就是不能溢出为客户端调用提供的缓冲区。在图2.3 巾,发送端提交了3个数据:长度分别为 128、64 32个学节本地系统堆栈把这些数据聚合一起,形成个更大的数据包,这种情况下,重组后的两个数据包就会一起传输。是价将各个独立的数据包累积在一起,要由许多因系决定,例如最大传输单元或 Nagle算法。
在 TCP/IP 中,Nagle 算法要求主机等行数据积果起来以后再将它们发到线上,这个卡机将一直等下,直到而要发送的数据积累到一定数量,或超过预定时间为止。实施 Nagle 算法时,主机的通信对方会在向主机发送确认之前,花费一段预定的时间等候来要传出的数据,这样,主机的通信对方就不必发送一个只确认消息的数据包。发送小数据不仅没有多少意义,而只还会徒增错误检查和确认的开销。
在接收端,网络堆栈会针对给定进程把所有传入的数据聚集在一起。我们来看看图 2.2。只要接收端一执行 256 宁节缓冲区的 recv 调用,系统就会马上返回所有的 224 个字节。如果接收端只要求读取 20 个字节,则系统就只返回20个字节。
2.2.3 伪流
伪流(pseudo stream)这个术识常用下这样的系统中:
这种系统使用的协议是基下消息的,该协议将数据放在离数的包中发送,接收端读取这些数据包并把它们放到缓冲池中,这样,接收成用程序使可读取任意大小的数据块。
把图 2.2 中的发送端和图 23 中的接收端结合起来,使可说明伪流的工作原班。发送端必须分别发送每个独立的数据包,仙接收端可以任何适用的大小自由合并收到的数据位。多数情况下,可把伪流视作一个普通的“面向流的协议”。
2.2.4 面向连接和无连接
通常情况下,一个协议要么提供面向连接的服务,要么提供无连接的服务。
在向向连接的服务中,进行数据交换之前,通信双方必须建立 条路径,这样既确保了通信双方之间存在路由,又保证了通信双方是活动的,可彼此响应。但这样做同时也意味着在通信双方之问建立个通信信道需要大量的开销。
除此以外,大部分面向连接的协议为保证投递无误,可能会协行额外的计算来验让正确性,从而进一步增加开销。而尤连接协议却不保证接收端是否正在侦听,无连接服务类似于邮政服务:发信人把信装入邮箱即可。
至于收信人是否盼望收到这封信,或邮局是否会因为暴风雨未能按时将信件投递到收信人处,发信人都不得而知。
应注意到,对于某些无连接协议(如 UDP),Winsock 应用程序能会使用目的地的IP 地址来调用connect 函数,但这不意味着建立了任何连接。这仅仅是将目标地址和套接字关联起来的“种方使途径,以使可以使用 send和WSASend API雨数来代 sendto WSASendTo函数。
2.2.5 可靠性和有序性
在设计··个使用某种协议的应用程序时,可靠性和有序性是必须注意的最关键的特件。可靠性(或有保证的交付)保证发送端发出的每个宇节都能到达既定的接收端,不具备可靠性的协议,则不能保证每个字节都能到达接收端,也就不能保证数据的完整性。
有序性足指对数据到达接收端的顺序进行处理。如果一个协议保留了有序性,它就能够保证接收端所收到数据的顺序就是数据的发送顺序。显然,没有保留有序性的协议就没有次序保证。
大多数情况下,可靠性和有序性与协议是无连接的还是面向连接的特性密切相关。乔面向连接的通信中,如果试图在通信双方之间建立一个清晰的通信信道,则一般希望所采用的协议能够保证数据的完整性和有序性。多数情况下,面向连接的协议总是能够保证数据的可靠性。注意,可靠性和有序性两者不能兼而得之,你保证了数据包顺序,就不能自动保证数据的完整性。不言而喻,无连接协议的过人之处就尽速度;因为它根本不必费心去建立个指向接收端的虚拟连接。为什么错误检查降低了速度呢?这使是通常情况下无连接协议不能你证数据完整性和序性,而向连接的协议却能做到这-点的原因。数据报如此不完善,为什么大家都喜欢用它呢? 一般来说,无连接协议比面向连接的通信要快得多。另外,它不必验证数据光整性和收到数拆的确认信息一这两者即使在发送小量数据时也会大人增加复杂性。对十分关键的数据传输,数据报正常有用。
而对于前面讨论过的网络游戏之类的应用程序而言,数据报简直就是为它们量身定做的:每个玩家都可利用数据报周期性地向划的玩家发送他 /她在游戏中的位置。如果某一个客户机丢失了一个数据包,它很快就能收到另外一个,这就给玩家带来一种不间断通信的印象。
2.2.6 正常关闭
正常关闭只山现在面向连接的协议中。在这种关闭过程中,其中一方开始关闭通信会话,但此时另一方仍然可以读取线路上或网络堆栈上持起的数据。如果面向连接的协议不支持正常关闭,则只要其中·方关闭了通信信道,就会出现连接立即中断、接收端丢失还未读取的数据这些情况。如果使用的是 TCP 协议,则连接双方都必须执行·-次关闭,才能完全中断连接。发起方先向另一·个通信方发出一个带有 FIN 控制标志的片段(数据报。接收方则发起力返回一个 ACK 控制标志,确认已收到FIN,但此时它仍然可发送数据。FIN 控制标志表示,发起关闭的这一方再也不会发送数据。一旦对方决定不需要再发送数据,它也会发出一个 FIN 控制标志。随后,发起方用ACK 控制标志加以确认。这时,连接就完全关闭了。
2.2.7 广播数据
所谓广播数据,就是从一个工作站发出数据,局域网内的其他所有工作站都能收到,因为局城网上的所有计算机都可获得并处理广播消息,所以,这一特征适用于无连接协议。使用广播消息的不利之处是,每台计算机都必须对该消息进行处理。例如,如果 个用户在局域网上广播 条消息,则每台计算机上的网卡都会收到这条消息,并把它上传到网络堆栈。然后,堆栈会在所有的网络应用程序内循坏,看它们是否应该接收这条消息。通常,这个局域网上的多数计算机对该消息都不会感兴趣,最后会草草地将该消息一弃了之。但是,这些计算机仍然需要花时间来处理这个数据包,看是否有应用程序对它感兴趣。结果,每个工作站都会检查这个数据包,这样,高广播通信量就会致使局域网上的计算机陷入困境。一般情况下,路山器不会在网络间传送广播数据包。
2.2.8 多播数据
多播是指一个进程发送数据而一个或多个接收端可接收到数据的能力。进程加入一个多播会话所伟用的方法和下层协议有关。
例如,IP 协议下,多播就是广播的一种变体。IP 多播要求对收发数据感兴趣的所有主机加入一个特定的组。当进程希望加入某个多播组时,网卡上便会增添一筛选器,这样,只有被绑定多播组地址的数据才会被网络硬件接收,并被上传到网络堆栈进行适当处理。视频会议应用程序常常使用多播。第9 章将详细论述 Winsock 的多播编程和其他一些多播的关键问题。
2.2.9 服务质量
服务质量(QOS)体现了应用程序请求独占网络带宽的能力。实时视频流就较好地利用了QOS。对实时视频流应用程序的接收端而言,要显示平滑清晰的图像,发送的数据必须被限制在特定范用内。过去,应用程序是将数据缓存下米,再从缓冲区中重播顿,从而获得流畅的视频。这样,当某个时段接收数据的速度不够快时,重播例程也会拥有一定量缓任起来的顿可供播放。QOS 允许在网络上预留带宽,因此,可以在一系列有保证的限制条件下,离线读取帧。理论上讲,这意味着同一个实时视频流应用程序可以使用QOS,从而避免使用缓有顿。第 10 章将对 QOS 作详细讨论。
2.2.10 部分消息
部分消总只用于面向消息的协议。例如,一个应用程序想接收一条消息,但本地计算机仅收到了它的部分数据。这种情况是可以出现的,特别是当发送端计算机正在传输大型消息,而本地计算机却没有处够多的资源来容纳整条消息的时候。实际上,多数面向消息的协议对数据报的最大长度都右一个合理限制,因此,这特殊事件不会经常发牛。但是,大多数数据报协议都支持大型消息一一大得足以需要分解成许多小块才能在物理媒体上传输。如此一来,用户的应用程序请求读取这·消息时,就有这样一种可能性存在:
- 用户系统可能只收到了部分消息。
如果所用的协议支持部分消息,它就会通知读取方已返回的数据只足整条消息中的一部分。如果所用的协议不支持部分消息,下层网络堆栈就会继续按收其余的消息,直到收完为止。如果由于某种原因,导致整条消息个能全部到达,那么收到的数据报就会不完整,而多数不支持部分消息的不可靠协议都会把这个不完整的数据报丢弃。
2.2.11 路由选择的考虑
对应用程序开发者而言,一个需要考虑的重要方面,就是协议是否可路由。
如果协议可路由,就可在两个工作站之间顺利地建立一条通信路径(要么是虚拟的面向连接的回路,要么是数据报通信的数据路径),至于这两个工作站之间的网络硬件是什么,则无关紧要。例如,计算机 A 和计算机B分别在各自的子网中,连按两个子网的路由器 A 把这两台计算机分开了。
可路由的协议将意识到计算机B 和计算机 A 不在同一个子网上;因此它会把数据包定向到路由器,由路由器来决定将数据送达计算机 B 的最佳转发方式。非路由协议则没有这个能力路由器会将它收到的路由协议数据包统统丢弃。
对发自非路由协议的数据包,即便它们的既定目的地是在路由器已连接的子网上,路由器也不会转发这些数据包。NetBEUI是Windows 平台支持的唯一个不能路由的协议。
2.2.12 其他特征
Windows 平台上支持的各个协议都展现出一些特定的或特殊的特征,同时,还有大量不胜枚举的其他特征,例如字节序和最大传输单元等,都可用来描述目前网络上可用的各种协议。然而,在编写Winsock 应用程序时,并非所有这些特征都是关键的。
Winsock 2提供了一种功能,可把每个实用的协议提供程序列举出来,还可以查询它们的特征。本章下·节对这一功能进行闸释,并给出了一个代码示例。
2.3 Winsock编录
Winsock 编录是一个数据库,它包含系统中可用的各种不同的协议。Winsock2 提供了一种办法可以确定在·个给定工作站上安装了哪些协议,并返回每种协议的各种特征。
如果一个协议支持多种行为,则每一种不同的行为类型在系统中都会有各负的编录条目。
例如,如果在系统中安装了TCP/LP系统中就会有两个IP 条目:
- 一个条目针对 TCP,是可靠的而且面向连接的
- 而另一个条目则针对IP是不可靠且又无连接的。
要想获得系统中安装的网络协议的相关信息,可以调用WSAEnumProtocols 函数,这个函数的定义为:
int WSAEnumProtocols(
IPINT lpiProtocols,
LPWSAPROTOCOL_INFO lpProtocolBuffer,
LPDWORD lpdwBufferLength
);
这个函数取代了 Winsock 1.1中的 EnumProtocos 函数,EnumProtocols 函数是 Windows CE中不可少的。
两者惟一的不同是:
- WSAEnumProtocols 返回的是 WSAPROTOCOLS INFO 结构数组;
而EnumProtocols 返回的则是 PROTOCOL INFO 结构数组,PROTOCOL INFO 结构中包含的字段少于WSAPROTOCOLS INFO 结构中的字段(但包含的信息是差不的)WSAPROTOCOL INFO 结构的定义为:
typedef stract _WSAPROTOCOL INFO(
DWORD dwServiceFlagsl;
DWORD dwserviceFlags2;
DWORD dwServiceFlags3;
DWORD dwserviceFlags4;
DWORD dwProviderFlags;
GUID ProviderId;
DWORD dwCatalogEntryId;
WSAPROTOCOLCHAIN ProtocolCnain;
int iVersion;
int iAddressFamily;
int iMaxSockAddr;
int iMinSockAddr;
int isocketType;
int iProtocol;
int iProtocolMaxoffset;
int iNetworkByteorder;
int isecurityscheme;
DWORD dwMessagesize;
DWORD dwProviderReserved;
TCHAR SzProtoColIWSAPROTOCOL LEN +1];
)WSAPROTOCOL_INFO,FAR *LPWSAPROTOCOL_INFO;
要调用 WSAEnumProtocols 函数,最简单的方法是令 pProtocolBuffer 等于 NULL,并将lpdwBufferLength 设置为0,然后进行第1次调用。这个调用会失败,并返WSAENOBUFS错误但返问的 lpdwBufferLength 中将包含汇确的缓冲区长度(这”-长度是返回所有协议信息所必需的)。旦分配了正确的缓冲区长度,并利用这个缓冲区进行第 2次调用,WSAEnumProtocols 两数会返回WSAPROTOCOL INFO 结构的数量。这时,就可逐深入各结构,查找自己所需要的协议条目了。
补充材料各上的示例程 ENUMCATC 列举了所有已安装的协议及其特性
WSAPROTOCOI_INFO 结构最常用的字段是 dwServiceFlags1,它是代表不同协议属性的一-个位了段。表 2.1 列出了可在字段中设置的各种标志,并对各属性的含义进行了简要说明。
2.3.1 Winsock编录和Win64
在64位Windows中,可以在WOW64(Windows on Windows,Windows之上的Windows)子系统中运行32 位应用程序。因为32 位和 64 位应用程序可能都要访 insock 目录,因此系统将保留个独立的目录。当运行64位Winsock应用程序并调用WSAEnumProtocls函数时,使用64位录而运行32位Winsock 应用程序并调用 WSAEmumProtocos 函数时则使用32位目录在涉及到第12章中讨论的“Winsock 服务接供程序接口”时,Winsock 目录将变得尤为重要.
2.3.2 创建套接字
第1章的简单例子展示了如何使用 socket 函数来创建套接字。
这个函数具有3 个参数:
- 地址族
- 套接字类型
- 协议。
应用程序创建套接字时,它将参考 Wisock 目录,并试图将提供的参数和每个WSAPROTOCOL INFO 结构中包含的参数匹配。如果参数匹配成功,就根据参数的提供程创建个套接字。应注意到,某些情况下协议参数可以是 0。如果 WSAPROTOCOLINFO 结构小的dwProviderFlags(提供程序标志)字段是 PFL MATCHES PROTOCOL ZERO,且请求的地址族及套接字类型与结构中的相应条月匹配,就使用相应的提供程序来创建套接字。例如,考虑下面的调用
SOCKET s;
s =socket(AF_INET,SOCK_STREAM,0);
Winsock 将列举目录,并首先匹配后面紧随套接字类型的地址族。出于协议值是 0,而且提供程序MSAFD TCP 也包含了 PFL MATCHES PROTOCOL ZERO 标志,所以,这个调将根据MSAFDTCP 提供程序创建一个 TCP 套接字。之后,系统不会再把这个请求与任何其他的提供科序匹配。
在某些情况下,多个提供程序可以共享同 个地址族、套接字类型及协议。RSVP 提供程序就存在这种情况。RSVP提供程序为TCP/P 和UDP/IP 都提供Q0S。因为多个提供程序可以共享网一个地址族、套接字类型及协议,所以不可能用 socket API从RSVP 提供程序创建套接字。为了解决这个问题,必须使用 Winsock 2的WSASocket 函数,该函数的定义为:
SOCKET WSASocket(
int af,
int type,
int protocol,
LPWSAPROTOCOL_INFO lpProtocolInfo,
GROUP g,
DWORD dwFlags
);
前3个参数和 socket 两数的相应参数相同,而第4 个参数采用了 WSAPROTOCOL INFO 结构形式。如果 lpProtocolInfo 引用了一个提供程序条目,且前 3个参数的值都是预先设定的 FROMPROTOCOL_INFO,函数就根据给定提供程序创建一个套接字应用程序使用这种方法便能创建一个RSVP 套接字。
第4个参数处理套接字组,该套接字组在 Winsock 规范中有所叙述,但并不在 Windows 中实现最后一个参数是可选标志,可以忽略。由此看来,仅仅WSA FLAG OVERLAPPED 标志比较重要。如果打算使用重叠的 /0(如第 5 所述),则创建接字时,就需要给出这个标志。应注意到,使用socket API 时,总是意味着同时也使用了重叠标志。其他套接字标志和多播有关,将在第9 竟中讲到。
总结
在这一章中,我们了解到 Winsock 如适应总体系统结构,以及各种不同的协议如何插入到系统中。另外,我们看到了各种协议表现出来的特征,还了解到如何编程列举 Winsock 目录,以便获取这些特征。最后,我们了解到如何使用 WSASocket API 根据显式提供序创建套接。在下一章,我们将更详细地分析IP协议,包括IPV4 和IPv6。
参考:
Microsoft Windows网络编程 第二章设计Winsock
私信回复:
windows网络编程pdf
可获得【Microsoft Windows网络编程】pdf版
关于博主
wx/qq:binary-monster/1113673178
wxgzh: 二进制怪兽
CSDN:https://blog.csdn.net/qq1113673178
码云:https://gitee.com/shiver
Github: https://github.com/ShiverZm
个人博客:www.shiver.fun