OverLapped I/O Socket 的问题-阿里云开发者社区

开发者社区> 开发与运维> 正文

OverLapped I/O Socket 的问题

简介: OverLapped I/O Socket 的问题,请教了!-Delphi知识大全  wsasend 异步投递一个发送请求,为了简单lpBuffers 参数只用了一个wsabuf结构,如果一次投递一个50M左右的请求可以成功,但是当我投递一个200M...

OverLapped I/O Socket 的问题,请教了!-Delphi知识大全
 wsasend 异步投递一个发送请求,为了简单lpBuffers 参数只用了一个wsabuf结构,如果一次投递一个50M左右的请求可以成功,但是当我投递一个200M的请求时返回
WSAENOBUFS(10055)错误,大概意思是“windows socket提供者报告一个缓冲区死锁”
我想应该是请求太大了,我的问题是:
1、我是应该把这个大块数据分解,然后投递若干个 wsasend请求呢还是将数据分解
到若干个wsabuf数组元素中一次投递?主要还是对lpBuffers这个参数的意义没理解。:(
2、不管是那种方法,数据分解成多大合适?最大值是多少?什么地方有相关的解释?

1:呵呵,都会有这个过程的。
可以多次提交多个异步请求,大小自己测一下就知道了,并不是BUF越大速度越快。根据网络情况来测一下吧。
2:谁能给解释一下“windows socket提供者报告一个缓冲区死锁”是什么意思,什么原因引起的?
我感觉异步i/o好象不应该限制请求的大小,如果有限制的话应该在相关文档中有说明啊
感兴趣的朋友帮忙顶一下,谢谢!
3:啊,迷糊:
怎么我这里 10055 错误的说明是:“系统缓冲区空间不足或队列已满,不能执行套接字上的操作。”呀,有什么区别不?
4:我也搞不清楚,我是在wsasend出错后getlasterror返回10055,就是WSAENOBUFS,对于WSAENOBUFS 错误msdn中的解释是这样的
WSAENOBUFS The Windows Sockets provider reports a buffer deadlock.
5:啊,那你就按我上面的解释处理吧,是同一个错误的不同回答,相对来讲,我这个更明确些。
6:异步发送应该是把数据交给系统内核去发送,如果系统缓冲区空间不足或队列已满,那么内核应该等待接受方接受数据腾出系统缓冲区继续发送啊,怎么会返回错误完事呢?
是不是我的理解错误,这个概念比较迷糊
7:缓冲区死锁
在网络中可能形成死锁状态的资源是缓冲区。

• 当一个方向传输的分组占用了太多的缓冲资源时必然影响其他方向的分组有序流动,最终造成死锁。

三种死锁形式:

• 最简单的一种死锁是直接存储—转发死锁。解决的方法是:如果不允许结点中的缓冲区全部分配给一个传输方向,或者对每一 3 传输方向都分配固定大小的缓冲区,这种死锁就不会发生。

• 另外一种死锁是间接存储—转发死锁。解决方法:采用结构化的缓冲池技术可防止发生这种死锁。在拥挤的民政部下,“低级的”分组被丢弃,网络尽量把“高级的”分组送往它们的目的地。

• 最后的一种死锁是重装配死锁。这种死锁在 ARPANET 这样的数据报网络中最容易出现。 ARPANET 采用的缓冲区管理方法称为最小分配是最大限制的共享分配法。
8:(10055)
No buffer space available.
An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full.
9:asf.sys会把你的缓冲区锁定为未分页内存,未分页的理论最大值就是你的物理内存最大值
10:有意思,收藏!
11:to 元元她哥:
“asf.sys会把你的缓冲区锁定为未分页内存”这句话不知怎么来理解。我的理解因为有
so_sndbuf 这个值的限制,asf.sys不应该把我的缓冲区全部缓冲到非分页内存中啊。
另外说明一下,我投递的缓冲区是一个映像文件,并且接受端接收到数据之后需要一个
确认,不知道这些因素是否有影响。
大虾们,是时候出手了!!!
12:为了提高高带宽、高延迟网络的性能,Windows 2000 TCP 支持RFC 1323 中定义的TCP 窗口缩放。通过在TCP 三次握手期间商定一个窗口缩放因子,支持T C P 接收窗口的大小可大于64KB 。它支持的接收窗口最大可达1GB 。
窗口缩放因子只在三次握手的前两个段中出现。缩放因子是2的s次方,其中s 为商定缩放因子。例如,对缩放因子为3 的通告窗口大小65535,实际接收窗口大小为524280 即8×65535 。

T C P 窗口缩放在缺省时是启用的,并且只要连接的TCP窗口大小值设定为大于6 4 K B 就自动使用

慢启动算法
Windows 2000 TCP 支持慢启动和拥塞避免算法。一个连接建立后,T C P 起先只缓慢地发送数据来估计连接的带宽,以避免淹没接收主机或通路上的其他设备和链路。发送窗口大小设为2 个T C P 段,当两个段都被应答后,窗口大小就扩大为三个段。当三个段都被应答后,发送窗口大小再次扩大。如此进行,直到每次突发传输的数据量达到远程主机所声明的接收窗口大小。此时,慢启动算法就不再用了,改用声明的接收窗口进行流控制。

以上是在《Windows 2000 Server资源大全第3卷TCP/IP连网核心技术》摘下来的。

可以用网络监视器监听一下你的通信里TCP SYN 段中的窗口缩放选项。看看它的发送窗口有多大。很可能是发送200M时,系统申请的窗口过大吧。
13:补充一句:我不是大虾! [^][^]
14:to painboy:
按照你提供的资料理解,窗口的大小是逐渐扩大的,那么出错也应该是在发送过程中发生,但是我这里的现象是wsasend 直接就返回错误了。
另外滑窗的大小也应该和接收端的receive的速度有关系,但是我的接收方还没有执行receive,所以我觉得滑窗不可能太大。或者我对异步io流程的理解不对。
15:相信以下内容就是你想要的 :)


Socket 体系结构

Winsock2.0规范支持多种协议以及相关的支持服务。这些用户模式服务支持可以基于其他现存服务提供者来扩展他们自己的功能。比如,一个代理层服务支持(LSP)可以把自己安装在现存的TCP/IP服务顶层。这样,代理服务就可以截取和重定向一个对底层功能的调用。

与其他操作系统不同的是,WinNT和Win2000的传输协议层并不直接给应用程序提供socket风格的接口,不接受应用程序的直接访问。而是实现了更多的通用API,称为传输驱动接口(Transport Driver Interface,TDI).这些API把WinNT的子系统从各种各样的网络编程接口中分离出来。然后,通过Winsock内核模式驱动提供了sockets方法(在AFD.SYS里实现)。这个驱动负责连接和缓冲管理,对应用程序提供socket风格的编程接口。AFD.SYS则通过TDI和传输协议驱动层交流数据。

缓冲区由谁来管理

如上所说,对于使用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发送,就会浪费一个发送提交之后另一个发送执行之前那段时间。
16:资源约束

鲁棒性是每一个服务程序的一个主要设计目标。就是说,服务程序应该可以对付任何的突发问题,比如,客户端请求的高峰,可用内存的暂时贫缺,以及其他可靠性问题。为了平和的解决这些问题,开发人员必须了解典型的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的一章)。如果仍然不能解决问题,那么你可能遇上了非分页内存池限制。那么最好是立刻关闭部分连接,并期待情况恢复正常。
17:谢谢 painboy,你贴得资料我仔细看了一下,很有帮助。首先声明,我的程序中没有关闭发送缓冲,所以不可能是afd.sys的问题。我现在觉得问题可能使这段描述所说的情况“无论应用程序发起send还是receive操作,也不管AFD.SYS的缓冲是否被禁止,数据所在的缓冲都会被锁定在物理内存里”,但是对这句话的意思还是有些不理解:
当我wsasend时投递的缓冲区会全部被锁定在物理内存中吗?如果是,那我的错误就很好理解了,但是adf.sys的so_sndbound限制是什么?tcp的滑窗又限制的什么?
大家在异步发送大的数据块时一般怎么处理?分成小块么?
wsasend/wsarecv的lpBuffers这个参数指向一个wsabuf数组,这个数组怎么用?
18:Scatter, Gather and MSG_PARTIAL

The multiple buffer (WSABUF) input arguments for WSARecv()/WSARecvFrom() and WSASend()/WSASendto() provide support for scatter and gather operations (similar to those in the readv() and writev() functions in BSD Unix). The MSG_PARTIAL flag might also do this, but the specification isn't entirely clear what the intended use of the flag is, and current implementations don't support it (as described below).

These operations are designed for datagram sockets that preserve message boundaries, not for stream sockets that do not (so may not fill buffers), though they do seem to work with stream sockets. The advantage that the gather operation provides is that it can assemble a single outgoing datagram from multiple buffers--e.g. put together different fields--and the scatter operation can "parse" fixed field lengths in an incoming datagram into multiple buffers.

WSARecv()/WSARecvFrom(): Works fine -- scatters to input buffers on both Win95 and NT4 with datagram sockets. Stream sockets also work on both Win95 and NT4 SP3 in the testing I've done (which I would not recommend, since with a TCP byte stream the spec doesn't indicate that each buffer must be filled to the specified size on scatters, so behavior may be unpredictable under some circumstances with stream sockets).

WSASend()/WSASendTo(): Works fine -- gathers from output buffers on both Win95 if you use datagram sockets. It also works with datagram sockets on NT4 SP3, although it failed with 10040 - WSAEMSGSIZE, if the message was larger than the MTU, so required IP fragmentation (e.g. greater than 1472 on Ethernet). This also works with stream sockets, but a similar warning as given for scatters goes for gathers as well (there's no guarantee that all bytes will be sent)

 

以下是 WSASend在UNIX下的实现:

sockapi int __stdcall WSASend
(
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesSent,
DWORD dwFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
)
{
int rc;

TRACE("WSASend");

if (lpOverlapped != NULL) panic("Overlapped I/O not implemented in WSASend");
if (lpCompletionRoutine != NULL) panic("Completion routines not implemented in WSASend");

rc = writev(s, lpBuffers, dwBufferCount);
if (rc < 0) return -1;

if (lpNumberOfBytesSent) *lpNumberOfBytesSent = rc;
return 0;
}

 

WRITEV的解释:
NAME
readv, writev - read or write data into multiple buffers

SYNOPSIS
#include

int readv(int filedes, const struct iovec *vector,
size_t count);

int writev(int filedes, const struct iovec *vector,
size_t count);

DESCRIPTION
The readv() function reads count blocks from the file
associated with the file descriptor filedes into the mul-
tiple buffers described by vector.

The writev() function writes at most count blocks
described by vector to the file associated with the file
descriptor vector.

The pointer vector points to a struct iovec defined in
as

struct iovect
{
void *iovbase; /* Starting address */
size_t iov_len; /* Number of bytes */
} ;

Buffers are processed in the order vector[0], vector[1],
... vector[count].

The readv() function works just like read(2) except that
multiple buffers are filled.

The writev() function works just like write(2) except that
multiple buffers are written out.


看完了以上说明后,相信你会将WSASEND里面的dwBufferCount设为1吧!:)
19:是不是应该这样理解so_sndBuf的限制:

用WSASend发送一个N大小的内容,当N<=so_sndBuf时,ADF.SYS就直接将要发的内容复制到其缓冲区内,函数立即返回。当N>so_sndBuf时,这时系统会堵塞:先锁定要发送的N空间,然后每次从N中读so_sndBuf大小的内容去发送,直到发送完毕函数才返回。

但由于当N=200M时,太“小”了,系统无法把它装进内存并锁定,导致出错。

……(这里省去6000字) ^_^
看过好多例程,缓冲区大小取4至6K 。

最后一个问题……没用过

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
开发与运维
使用钉钉扫一扫加入圈子
+ 订阅

集结各类场景实战经验,助你开发运维畅行无忧

其他文章