.net socket与完成端口、异步发送相关研究

来源:互联网 发布:网络衣服模特 编辑:程序博客网 时间:2024/05/29 14:23

原帖地址:http://www.cnsw.org/bbs/thread-68634-1-1.html

 

经过一番研究,终于可以确认,.net socketbeginSendbeginReceive用的是完成端口。(windows 98上不是,因为98没有这样的机制)。如果微软没有撒谎的话。
发送大量数据时,Socket.BeginSendSocket.Send的速度是有差别的。在局域网里面,这种差别表现不明显。但是在一个高延迟的网络中,差别就很大。

Socket.Send方法是可靠的。但是Send的时候,是等到缓冲区发出的包被确认以后才继续发送后续的包。所以,即使网络的带宽很大,但是如果网络延迟高,发送速度也会很慢。

Socket.BeginSend是把要发送的数据直接写入缓冲区,然后调用返回。BeginSend发送的时候,并不能确定发送是否成功。 BeginSend的时候,指定了一个回调方法,发送成功后,系统会调用这个回调方法。在回调方法中,可以通过EndSend来检查实际发送的字节数。
虽然msdn中没有说明,但是实际上在EndSend中返回的字节数总是等于发送的字节数,或者抛出异常。

BeginSend的发送速度,可以占用全部的网络带宽。而Send的速度受网络延时的影响很大。在网络延迟高的网络中,可能每秒只有几K的速度。实际上,这个速度大致等于系统发送时的每个ip包的大小除以网络延迟的秒数。

完成端口使用的要点,就是减少系统中线程的数量。所以,不要以为用了BeginSend就一定可以提高系统的负载能力。主要还是在于减少系统中工作的线程数量。所以,尽量不要在线程中使用阻塞的方法。

Socket.BeginReceive方法和Socket.Receive方法,对接受的速度没有影响。因为根据tcp协议,既然这个包已经到了应用层,那么肯定是已经实际收到了。用BeginReceive的优势是在于可以不阻塞线程,从而减少系统中工作的线程。对于负载量不大的系统,用 Receive就可以了。Receive的逻辑比较简单。但是需要记住一点,Receive时返回的字节数,不一定等于要求读取的字节数。系统只是在数据包到达时,尽可能的读取要求的字节数。

.netSocket Api其实是对系统Socket Api的封装。所以以上的说法同样适用于winsock。但是由于BeginSend方法封装了完成端口,所以可以在获得高性能的同时,少了很多麻烦。对于网络的编程,是非常美妙的一件事情。

BeginSend需要注意的是,一定要控制发送的速度。否则,这个连接一定会因为系统缓冲区满而抛出异常。控制速度的办法就是在发送时计算发送的包数量,在EndSend的时候计算发送成功的包数量。在发送之前,检查未发送的包数量,如果小于预设值则发送,否则暂停。这个地方会阻塞线程,所以也不是最好的方法,比较好的办法,是自己做个发送缓冲队列。然后用一个专门的线程来处理发送。这样,只要用很少的线程,就可以处理发送。虽然BeginSend用了完成端口,但是如果在系统中有大量被阻塞的线程,那就违背了完成端口的本意。

beginSend是不阻塞的。beginsend的时候,指定了一个回调函数,beginSend将数据放入系统缓冲区就立即返回/但是数据并没有真正发出去。只是系统把数据缓存起来了。这个缓存很大,一般有几十M.BeginSend并不需要很多现成,即使你用100个线程beginSend,系统也只会用很少的线程来send。系统开始send时,会调用回调函数来通知调用者。
这个工作线程的个数,无需太多,不超过cpu的线程数就可以了。实际上,一个就够了。.net具体怎么实现的,我也不清楚。我自己做缓冲发送的时候,就是用一个线程。因为多了也不能提高发送速度。

的确iocp对于客户端没有太大意义。完成端口就是使用较少线程数的异步发送。不过,c++用完成端口很麻烦。逻辑比较复杂。但是客户端同样也有发送速度的问题。我一般不用mfc,所以对Casyncsocket不熟。但是CAsyncSocketwindows的消息循环来实现回调的方法,是令人非常纳闷的。看不出这样能带来什么好处,但是程序不得不耦合windows窗体。这种设计,真的是非常糟糕。

很多时候,客户端也是服务器。比如bt,emule,客户端都是要开很多连接的。但是并不需要开与连接数量相等的线程数。一个pc上开几百个线程,也是压力相当大的。bt的服务器,逻辑反而相对简单一些。

 

注:使用C#异步发送数据量较大时,程序会在应用层自动并包。这一点可以从任务管理器联网一栏数据骤降前后,抓包情况得出结论。怀疑是.net加入的自动保护措施,目前无真凭实据……