|
Windows NT和Windows 2000的套接字架构) Q9 Q, b+ u, p0 _2 Y+ N, a# @( e6 f
$ j" j* ]5 T' D! A( |对于开发大响应规模的Winsock应用程序而言,对Windows NT和Windows 2000的套接字架构有基本的了解是很有帮助的。
9 k" T7 L3 p% \5 f* I) V2 u- d3 t% D- ? V
与其它类型操作系统不同,Windows NT和Windows 2000的传输协议没有一种风格像套接字那样的、可以和应用程序直接交谈的界面,而是采用了一种更为底层的API,叫做传输驱动程序界面(Transport Driver Interface,TDI)。Winsock的核心模式驱动程序负责连接和缓冲区管理,以便向应用程序提供套接字仿真(在AFD.SYS文件中实现),同时负责与底层传输驱动程序对话。8 g2 w, F `8 e" f/ e* j) F- c
; ]) _6 n5 j! U) \ R谁来负责管理缓冲区?
& U; [* Z+ I0 m0 [* L% W/ v2 F
4 @8 z9 ^% Y6 d4 b, D+ `8 J正如上面所说的,应用程序通过Winsock来和传输协议驱动程序交谈,而AFD.SYS负责为应用程序进行缓冲区管理。也就是说,当应用程序调用send()或WSASend()函数来发送数据时,AFD.SYS将把数据拷贝进它自己的内部缓冲区(取决于SO_SNDBUF设定值),然后send()或WSASend()函数立即返回。也可以这么说,AFD.SYS在后台负责把数据发送出去。不过,如果应用程序要求发出的数据超过了SO_SNDBUF设定的缓冲区大小,那么WSASend()函数会阻塞,直至所有数据发送完毕。
: F4 h: D1 g$ T1 X4 e, k/ [0 o" S7 q
从远程客户端接收数据的情况也类似。只要不用从应用程序那里接收大量的数据,而且没有超出SO_RCVBUF设定的值,AFD.SYS将把数据先拷贝到其内部缓冲区中。当应用程序调用recv()或WSARecv()函数时,数据将从内部缓冲拷贝到应用程序提供的缓冲区。: q6 X" x$ L1 ~; |% z
8 z. u5 Z# s4 g7 g多数情况下,这样的架构运行良好,特别在是应用程序采用传统的套接字下非重叠的send()和receive()模式编写的时候。不过程序员要小心的是,尽管可以通过setsockopt()这个API来把SO_SNDBUF和SO_RCVBUF选项值设成0(关闭内部缓冲区),但是程序员必须十分清楚把AFD.SYS的内部缓冲区关掉会造成什么后果,避免收发数据时有关的缓冲区拷贝可能引起的系统崩溃。
+ X5 ~! u5 J3 V
* J# L8 G' d: W# V" y3 y举例来说,一个应用程序通过设定SO_SNDBUF为0把缓冲区关闭,然后发出一个阻塞send()调用。在这样的情况下,系统内核会把应用程序的缓冲区锁定,直到接收方确认收到了整个缓冲区后send()调用才返回。似乎这是一种判定你的数据是否已经为对方全部收到的简洁的方法,实际上却并非如此。想想看,即使远端TCP通知数据已经收到,其实也根本不代表数据已经成功送给客户端应用程序,比如对方可能发生资源不足的情况,导致AFD.SYS不能把数据拷贝给应用程序。另一个更要紧的问题是,在每个线程中每次只能进行一次发送调用,效率极其低下。
7 Q" _' }) p3 p4 L# X# a/ K) r: C
# p9 \; \; s8 X4 d. d把SO_RCVBUF设为0,关闭AFD.SYS的接收缓冲区也不能让性能得到提升,这只会迫使接收到的数据在比Winsock更低的层次进行缓冲,当你发出receive调用时,同样要进行缓冲区拷贝,因此你本来想避免缓冲区拷贝的阴谋不会得逞。8 C' d& q1 s: W9 S
/ J: R$ y& {; d* S" z
现在我们应该清楚了,关闭缓冲区对于多数应用程序而言并不是什么好主意。只要要应用程序注意随时在某个连接上保持几个WSARecvs重叠调用,那么通常没有必要关闭接收缓冲区。如果AFD.SYS总是有由应用程序提供的缓冲区可用,那么它将没有必要使用内部缓冲区。" B' r S r6 M: ` `
3 A2 Y( W1 C; P5 k+ I* d) b高性能的服务器应用程序可以关闭发送缓冲区,同时不会损失性能。不过,这样的应用程序必须十分小心,保证它总是发出多个重叠发送调用,而不是等待某个重叠发送结束了才发出下一个。如果应用程序是按一个发完再发下一个的顺序来操作,那浪费掉两次发送中间的空档时间,总之是要保证传输驱动程序在发送完一个缓冲区后,立刻可以转向另一个缓冲区。 |
|