套接字架构

来源:互联网 发布:js统计当前在线人数 编辑:程序博客网 时间:2024/04/29 03:01

1:架构图

 

2:Transport protocols

1:此层是各种协议的具体实现,如TCP/IP运输层滑动窗口,Nagle算法

2:Transport protocols没有为发送窗口预留空间,它依靠AFD.SYS的发送缓存或者进程缓冲区来实现发送窗口

3:Transport protocols为接收窗口预留了17KB的空间,此空间是在非分页缓冲池中实现

 

3:AFD.SYS

1:Socket API都封装在ws2_32.dll中,ws2_32.dll调用AFD.SYS的函数

2:发送缓存和接受缓冲在AFD.SYS中实现

3:发送缓存采用类似Nagle的算法提交数据到Transport protocols发送窗口中,猜测算法为:每隔XX秒提交一次,如果XX秒未到达前数据达到报文段最大长度则立即提交

 

4:发送缓存对Send()的影响

1:阻塞调用Send()

(1):当发送缓存还能容纳此次调用的数据时,拷贝数据到发送缓存,Send()返回;

(2):当发送缓存容量不够或发送缓存为0时,系统锁定进程此次调用的数据页面,直到Transport protocols发送完发送缓存的数据,然后会直接发送锁定的进程数据,这里绕过了发送缓存,又由于Transport protocols驱动要访问进程数据块,所以必须预先锁定页面,以使数据块保存在物理内存中并防止被删除;

当Transport protocols发送完一块锁定的进程数据,它必须等到ACK到达后才能解锁进程数据,然后Send()返回,因为如果没有等到ACK就返回,进程可能会销毁此数据块,如果TCP在传输过程中出现错误,差错控制的重传要求就不可能实现,基于这个原因,Transport protocols即使锁定了多块数据,对于阻塞Send(),每次也只会发送一个报文段(备注:这是在发送缓存已满产生的情况)

2:非阻塞调用Send()

(1):当发送缓存还能容纳此次调用的数据时,拷贝数据到发送缓存,Send()返回;

(2):当发送缓存容量不够或发送缓存为0时,系统锁定进程此次调用的数据页面,Send()返回失败,WSAGetLastError()返回WSA_IO_PENDING,接下来处理过程和上面一样,同样绕过发送缓冲,区别是:对于非阻塞Send(),如果没启用Nagle,Transport protocols就可以发送多个数据块,但并不会合并这些数据块到一个报文段发送,因为他们是被锁定在不同的内存页面,单独发送Transport protocols就能使释放锁的粒度更加精细,如果启用了Nagle,数据块就只能一个一个发送并且要等待前一个数据报的ACK

 

5:关于发送缓存的思考

1:发送缓存能够节约需要锁定内存页面的数量,因为进程能够锁定的页面时有限的

2:发送缓存能够起到合并数据块的作用,参考3.3

 

6:接收缓存对Recv()的影响

1:当没有接收缓存时,数据到达Transport protocols,被缓存进17KB接收窗口中,这会导致接收窗口变小,从而影响发送窗口,当进程提交Recv(),数据被直接复制到进程缓冲区,但这节省了一次数据到接收缓存的拷贝时间,如果保证随时都有Recv(),则在接收窗口缓存一下,然后立即被复制到进程缓冲区也未尝不可

如果在Recv()时接收窗口没有数据,对于阻塞Recv()会一直等待,对于非阻塞Recv()会返回失败,进程缓冲区被锁定,WSAGetLastError()返回WSA_IO_PENDING,当数据到达接收窗口后,会被直接复制到进程缓冲区

2:当有接收缓存时,Transport protocols收到数据会首先保存到接收缓存中,在进程Recv()后,如果接收缓存有数据,则拷贝,然后返回,如果没有数据,则锁定进程缓冲区,后续步骤同上面一样

 

7:两个错误

1:如上所述,因为Transport protocols可能会直接访问进程缓冲区,所以进程缓冲区页面会被锁定,因为Transport protocols访问的数据只能在内存中,当提交了大量IO,并锁定了大量页面,可能会导致物理内存不够用,产ERROR_INSUFFICIENT_RESOURCES错误

2:当你创建,绑定,连接套接字时,系统会在非分页缓冲池中分配一些物理内存保存必要的数据,如果非分页缓冲池内存不够,可能会产生错误

 

8:几点思考

1:对于接收缓冲,当报文段的到达速度超过应用层处理速度,接收缓冲才有意义,因为在这种状况下,如果没有接收缓冲,由于应用层的迟缓处理,将导致接收窗口堵满,导致发送被迫终止,降低了效率

2:对于发送缓冲:

(1):不启用Nagle和设置发送缓冲为0:这将获得最大的实时性,应用层传递过来的数据在Transport protocols空闲时会被立即发送,但如果传过来的是例如1个字节的数据,将会浪费大量的带宽,同时,应用层缓冲区将被大量的锁定,可能会产生6(1)错误;当发送窗口满时,应用层缓冲区会被一直锁定

(2):不启用Nagle和设置发送缓冲不为0:效果和(1)基本相同,由于AFD.SYS有轻度的合并功能,实时性稍微减少的同时带宽的浪费量也会稍微降低

(3):启用Nagle和设置发送缓冲为0:这种组合是危险的做法,参考2.3,应用层投递的每一个包都会生成一个数据报,并要等到上一个数据报的ACK到达才能发送,如果只是单向通信,对方没有数据要发送过来,Windows发送ACK的实现是:

当到达一个报文段,如果可以捎带确认号则捎带,如果没有,则启动一个200ms的计时器,如果200ms内有其他报文段到达,则立即发送ACK,200ms超时同样发送ACK

也就是每一次都要等200ms才能发送下一个报文段,这样的效率显然是低下的

(4):启用Nagle和设置发送缓冲不为0(WinSocket默认实现):在高数据量情况下效率很高,并且带宽利用率也很高

因为数据被首先放置到AFD.SYS发送缓存中,然后到Transport protocols,由于Transport protocols被Nagle阻塞等待ACK,所以在等待ACK的过程中,AFD.SYS的发送缓存继续将数据搬到Transport protocols中,当下一次Transport protocols发送,就会发送经过这样合并的数据,这与7(3)的区别在于:如果没有发送缓冲作为中间件,Transport protocols一次只会复制一块锁定的应用层数据到Transport protocols中,并被构建成报文段发送出去,少了合并的步骤

此方法的缺点是:数据多经过了一次拷贝,经过以上讨论,如果能够选择一种适合当前应用的方式而省去这次拷贝是有价值的

 

9:备注

应当根据实际情况选择是否启用发送缓存,接收缓存,Nagle

原创粉丝点击