|
|
缓冲区由谁来管理
如上所说,对于使用socket接口和传输协议层交流的应用程序来说,AFD.SYS负责缓冲区的管理。也就是说,当一个程序调用send或WSASend函数发送数据的时候,数据被复制到AFD.SYS的内部缓冲里(大小根据SO_SNDBUF设置),然后send和WSASend立刻返回。之后数据由AFD.SYS负责发送到网络上,与应用程序无关。当然,如果应用程序希望发送比SO_SNDBUF设置的缓冲区还大的数据,WSASend函数将会被堵塞,直到所有数据均被发送完毕为止。
同样,当从远地客户端接受数据的时候,如果应用程序没有提交receive请求,而且线上数据没有超出SO_RCVBUF设置的缓冲大小,那么AFD.SYS就把网络上的数据复制到自己的内部缓冲保存。当应用程序调用recv或WSARecv函数的时候,数据即从AFD.SYS的缓冲复制到应用程序提供的缓冲区里。
在大多数情况下,这个体系工作的很好。尤其是应用程序使用一般的发送接受例程不牵涉使用Overlapped的时候。开发人员可以通过使用setsockopt API函数把SO_SNDBUF和SO_RCVBUF这两个设置的值改为0关闭AFD.SYS的内部缓冲。但是,这样做会带来一些后果:
比如,应用程序把SO_SNDBUF设为0,关闭了发送缓冲(指AFD.SYS里的缓冲),并发出一个同步堵塞式的发送操作,应用程序提供的数据缓冲区就会被内核锁定,send函数不会返回,直到连接的另一端收到整个缓冲区的数据为止。这貌似一种挺不错的方法,用来判断是否你的数据已经被对方全部收取。但实际上,这是很糟糕的。问题在于:网络层即使收到远端TCP的确认,也不能保证数据会被安全交到客户端应用程序那里,因为客户端可能发生“资源不足”等情况,而导致应用程序无法从AFD.SYS的内部缓冲复制得到数据。而更重大的问题是:由于堵塞,程序在一个线程里只能进行一次send操作,非常的没有效率。
如果关闭接受缓冲(设置SO_RCVBUF的值为0),也不能真正的提高效率。接受缓冲为0迫使接受的数据在比winsock内核层更底层的地方被缓冲,同样在调用recv的时候进行才进行缓冲复制,这样你关闭AFD缓冲的根本意图(避免缓冲复制)就落空了。关闭接收缓冲是没有必要的,只要应用程序经常有意识的在一个连接上调用重叠WSARecvs操作,这样就避免了AFD老是要缓冲大量的到来数据。
到这里,我们应该清楚关闭缓冲的方法对绝大多数应用程序来说没有太多好处的了。
然而,一个高性能的服务程序可以关闭发送缓冲,而不影响性能。这样的程序必须确保它在同时执行多个Overlapped发送,而不是等待一个Overlapped发送结束之后,才执行另一个。这样如果一个数据缓冲区数据已经被提交,那么传输层就可以立刻使用该数据缓冲区。如果程序“串行”的执行Overlapped发送,就会浪费一个发送提交之后另一个发送执行之前那段时间。
资源约束
鲁棒性是每一个服务程序的一个主要设计目标。就是说,服务程序应该可以对付任何的突发问题,比如,客户端请求的高峰,可用内存的暂时贫缺,以及其他可靠性问题。为了平和的解决这些问题,开发人员必须了解典型的WindowsNT和Windows2000平台上的资源约束。
最基本的问题是网络带宽。使用UDP协议进行发送的服务程序对此要求较高,因为这样的服务程序要求尽量少的丢包率。即使是使用TCP连接,服务器也必须注意不要滥用网络资源。否则,TCP连接中将会出现大量重发和连接取消事件。具体的带宽控制是跟具体程序相关的,超出了本文的讨论范围。
程序所使用的虚拟内存也必须小心。应该保守的执行内存申请和释放,或许可以使用旁视列表(一个记录申请并使用过的“空闲”内存的缓冲区)来重用已经申请但是被程序使用过,空闲了的内存,这样可以使服务程序避免过多的反复申请内存,并且保证系统中一直有尽可能多的空余内存。(应用程序还可以使用SetWorkingSetSize这个Win32API函数来向系统请求增加该程序可用的物理内存。)
有两个winsock程序不会直接面对的资源约束。第一个是页面锁定限制。无论应用程序发起send还是receive操作,也不管AFD.SYS的缓冲是否被禁止,数据所在的缓冲都会被锁定在物理内存里。因为内核驱动要访问该内存的数据,在访问期间该内存区域都不能被解锁。在大部分情况下,这不会产生任何问题。但是操作系统必须确认还有可用的可分页内存来提供给其他程序。这样做的目的是防止一个有错误操作的程序请求锁定所有的物理RAM,而导致系统崩溃。这意味着,应用程序必须有意识的避免导致过多页面锁定,使该数量达到或超过系统限制。
在WinNT和Win2000中,系统允许的总共的内存锁定的限制大概是物理内存的1/8。这只是粗略的估计,不能作为一个准确的计算数据。只是需要知道,有时重叠IO操作会发生ERROR_INSUFFICIENT_RESOURCE失败,这是因为可能同时有太多的send/receives操作在进行中。程序应该注意避免这种情况。
另一个的资源限制情况是,程序运行时,系统达到非分页内存池的限制。WinNT和Win2000的驱动从指定的非分页内存池中申请内存。这个区域里分配的内存不会被扇出,因为它包含了多个不同的内核对象可能需要访问的数据,而有些内核对象是不能访问已经扇出的内存的。一旦系统创建了一个socket (或打开一个文件),一定数目的非分页内存就被分配了。另外,绑定(binding)和连接socket也会导致额外的非分页内存池的分配。更进一步的说,一个I/O请求,比如send或receive,也是分配了很少的一点非分页内存池的(为了跟踪I/O操作的进行,包含必须信息的一个很小的结构体被分配了)。积少成多,最后还是可能导致问题。因此操作系统限制了非分页内存的数量。在winNT和win2000平台上,每个连接分配的非分页内存的准确数量是不相同的,在未来的windows版本上也可能保持差异。如果你想延长你的程序的寿命,就不要打算在你的程序中精确的计算和控制你的非分页内存的数量。
虽然不能准确计算,但是程序在策略上要注意避免冲击非分页限制。当系统的非分页池内存枯竭,一个跟你的程序完全无关的的驱动都有可能出问题,因为它无法正常的申请到非分页内存。最坏的情况下,会导致整个系统崩溃。比如那些第三方设备或系统本身的驱动。切记:在同一台计算机上,可能还有其他的服务程序在运行,同样在消耗非分页内存。开发人员应该用最保守的策略估算资源,并基于此策略开发程序。
资源约束的解决方案是很复杂的,因为事实上,当资源不足的情况发生时,可能不会有特定的错误代码返回到程序。程序在调用函数时可能可以得到类似WSAENOBUFS或
ERROR_INSUFFICIENT_RESOURCES的这种一般的返回代码。如何处理这些错误呢,首先,合理的增加程序的工作环境设置(Working set,如果想获得更多信息,请参考MSDN里John Robbins关于 Bugslayer的一章)。如果仍然不能解决问题,那么你可能遇上了非分页内存池限制。那么最好是立刻关闭部分连接,并期待情况恢复正常。
关于接受连接
上一页 [1] [2] [3] [4] [5] [6] 下一页
网友评论:(评论内容只代表网友观点,与本站立场无关!) |
阅读排行
|