兔八哥极品软件园    运行: 2755天 | 文章:583 篇 | 评论:3 条 | 碎语:1条

c# socket与完成端口、异步发送

作者:admin 发布于:2013-1-18 16:49 Friday 分类:.NET


经过一番研究,终于可以确认,.net socket的beginSend和beginReceive用的是完成端口。(windows 98上不是,因为98没有这样的机制)。如果微软没有撒谎的话。
发送大量数据时,Socket.BeginSend和Socket.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时返回的字节数,不一定等于要求读取的字节数。系统只是在数据 包到达时,尽可能的读取要求的字节数。

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

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

beginSend是不阻塞的。beginsend的时候,指定了一个回调函数,beginSend将数据放入系统缓冲区就立即返回/但是数据并没有真正 发出去。只是系统把数据缓存起来了。这个缓存很大,一般有几十M.BeginSend并不需要很多现成,即使你用100个线程beginSend,系统也 只会用很少的线程来send。系统开始send时,会调用回调函数来通知调用者。
这个工作线程的个数,无需太多,不超过cpu的线程数就可以了。实际上,一个就够了。.net具体怎么实现的,我也不清楚。我自己做缓冲发送的时候,就是用一个线程。因为多了也不能提高发送速度。
的确iocp对于客户端没有太大意义。完成端口就是使用较少线程数的异步发送。不过,c++用完成端口很麻烦。逻辑比较复杂。
但是客户端同样也有发送速度的问题。我一般不用mfc,所以对Casyncsocket不熟。但是CAsyncSocket用windows的消息循环来 实现回调的方法,是令人非常纳闷的。看不出这样能带来什么好处,但是程序不得不耦合windows窗体。这种设计,真的是非常糟糕。
很多时候,客户端也是服务器。比如bt,emule,客户端都是要开很多连接的。但是并不需要开与连接数量相等的线程数。一个pc上开几百个线程,也是压力相当大的。bt的服务器,逻辑反而相对简单一些。


Powered by 兔八哥极品软件 苏ICP备12049267号 sitemap