有关.net异步套接字底层使用IOCP的一些佐证

http://hi.baidu.com/__%B6%C0%B9%C2%B2%D0%D4%C6__/blog/item/93ce141390c1900c5aaf53a1.html


2008-10-15 15:01

无论什么平台,编写支持高并发性的网络服务器,瓶颈往往出在I/O上,目前最高效的是采用Asynchronous
I/O模型,Linux平台提供了epoll,Windows平台提供了I/O Completion
Port(IO完成端口,即IOCP)。

Windows自winsock2开始就提供了IOCP支持,可以通过C++直接调用API,但对于基于.Net的C#开发,是在.Net
Framework2.0开始才引入的,在2.0版本下,最高效的网络服务器是通过异步Socket的一些列Beginxxx,Endxxx方法实现的,底层就是基于IOCP的。

当.Net
Framework升级到2.0 sp1之后,.Net Socket又提供了一种更高效的一些列xxxAsync方法,对底层IOCP实现性能有不少改进,.Net
Framework升级到3.5之后更是成熟稳定,微软也开始大力推广。

在实际应用中,证明C#编写基于.Net
IOCP的高性能服务器可以支持10000个以上的TCP长连接。但在具体实现过程中需要注意几个问题:
1.SocketAsyncEventArgs和Buffer最好预先分配,并能回收重复利用。
2.一个Socket的Send和Receive最好分别对应一个SocketAsyncEventArgs,因为当一个SocketAsyncEventArgs被ReceiveAsync调用挂起后,在调用SendAsync时就会出异常。同样不要对一个SocketAsyncEventArgs在一个异步操作被挂起时再次调用。


参考文章:
http://msdn.microsoft.com/zh-cn/magazine/cc163356.aspx
http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socketasynceventargs.aspx


==========================================================================


.NET开发异步SOCKET
(IOCP)
(博客园 天空)


在网上很多人讨论.NET异步SOCKET通信的问题。


很多人都有误解,都以为非要用系统底层的IOCP,实际上没那么麻烦,要相信.NET封装了所有系统底层操作。


实际上.NET的IOCP微软早有例子,下面是该例子:


class Server
{
    private int
m_numConnections;   // the maximum number of connections the sample is designed
to handle simultaneously
    private int m_receiveBufferSize;// buffer size to use for
each socket I/O operation
    BufferManager m_bufferManager; // represents a
large reusable set of buffers for all socket operations
    const int
opsToPreAlloc = 2;    // read, write (don't alloc buffer space for
accepts)
    Socket listenSocket;            // the socket used to listen for
incoming connection requests
    // pool of
reusable SocketAsyncEventArgs objects for write, read and accept socket
operations

    SocketAsyncEventArgsPool m_readWritePool;
    int m_totalBytesRead;           // counter of the
total # bytes received by the server
    int
m_numConnectedSockets;      // the total number of clients connected to the
server
    Semaphore m_maxNumberAcceptedClients;
  
...略(参考MSDN,百度查询关键字SocketAsyncEventArgs可查到此例子)
}
也就是微软为SOCKET定义的那些BeginXXX(.NET2.0)或ReceiveAsync(.NET3.5)等等那些方法。


这是.NET完成端口的实现。


笔者开发了基于此的TCP服务器,可以支持10万人同时在线。


IOCP和.net
Socket Class
NCindy


众所周知,IOCP是在Windows平台实现高性能、高扩展性的Socket服务器的一个重要手段。对于.net这个平台,是否还能使用IOCP呢?答案是肯定的。
那如何使用IOCP呢?本文虽然没有给出具体的实现,但是给出的一些参考文档,做为实现的指引。
.net的Socket类在使用异步IO模型时会自动使用Windows的完成端口内核对象,在Windows网络编程第二版中是这样描述的:


The asynchronous model in .NET sockets is the best way to manage I/O from one
or more sockets. It is the most efficient model of the three because its design
is similar to the I/O completion ports model (described in Chapter 5) and on
Windows NT–based systems it will use the completion port I/O model internally.
Because of this, you can potentially develop a high-performance, scalable
Winsock server in C# and even possibly in Visual Basic. For a complete
discussion of how to develop a high-performance, scalable Winsock application,
see Chapter 6.


Socket类型在.net 2.0版本中更加增强了,一个显著的区别是,.net
2.0中的Socket可以使用DuplicateAndClose来对Socket进行持久化。这个方法的Remarks是这么写的:


If the process creating the socket uses asynchronous methods (BeginReceive or
BeginSend), the process must first set the UseOnlyOverlappedIO property to true;
otherwise, the socket is bound to the completion port of the creating process,
which may cause an ArgumentNullException to be thrown on the target process.


因为持久化IOCP内核对象是没有意义的,或者说无法通过持久化信息恢复完成端口的状态,所以Socket类型拥有了一个属性,UseOnlyOverlappedIO
,这个属性的Remarks是这么写的:


Set this property to true for a Socket you intend to call DuplicateAndClose.
Otherwise, the Framework may assign a completion port to the socket, which would
prohibit the use of DuplicateAndClose.



============================================================================


关于C#下直接调用Win32实现IOCP失败的讨论:





sonic.net 模拟的IOCP,4500
个客户端,可以正常工作.


测试了一下sonic.net 模拟的IOCP库(c#),
客户端:3台机子,每台模拟1500客户,共4500 个客户端,
服务器:P
D双核2.0G,2G内存,
局域网测试

客户端连接 -> 发一条信息 -> 服务器收到 -> 再send
back一条信息.
-> 客户端Sleep 2秒.-> 重复.

测试结果为服务器可以正常运行,cpu利用率经常去到
100%,内存90多M,非页面缓存1M多.

如果再增加客户连接,还可以连接,不会出现繁忙状态.同时,日志记录到并没有客户出现掉线的情况.


.........


这种效果可以吗?
我这边的应用,大概客户并发1000到2000.



如果换成其它的vc作的iocp服务器,客户支持数可以到4000或更多,同时,cpu利用率是非常低的.这个比sonic.net的好多了...........



还有一个问题就是:在看vc作的iocp服务器源代码时,发现有些做法是预先建立多个socket来
accept的,即预先投递多个acceptex,当发现socket不够用时,再建.

那么,在用c#来作iocp的网络应用时,是否是要作这样的[预先投递多个acceptex]的做法?通过这样来提高并发连接?

(我现在用sonic.net的库时,只是作了一个循环accept,有连接就把得到的socketid放入队列.).



d
up
没有人用过sonic.net的iocp吗?.可以交流一下吗?
d
ding
dddd
看了标题,我确实十分的兴奋。。
最快的速度进来了。。、、

看玩后、、我的心情十分复杂。。

1.4500个不出错。基本满足了99%的需求了。。让人很高兴。

2.CPU经常100%让人很郁闷,因为你测试的只是返回,做服务器,必定不是简单的返回。于是4500看来只能打对折了。

3.楼主没有提供相关测试代码,也是很遗憾。

顺便回答一下楼主,1000-2000的应用,感觉还是挺悬的。。。你可以考虑吧服务器的send
back过程做的复杂点多浪费点资源再来测试,。。
总的来说还是有希望的。。。不过既然楼主会VC,为什么不服务端用VC写呢。。。
终于有个可以交流一下的人啦~.
关健代码.


C# code
             
               
private ManagedIOCP MIOCP; private ThreadPool WorkerPool; public IOCP_Main( int NumThreads) { MIOCP = new ManagedIOCP(NumThreads); buffer = new byte [ 500 ]; ITask IOCPDataHandler = new HandleIOCPData(MIOCP); ITask ThreadListener = new ThreadListen(MIOCP); WorkerPool = new ThreadPool( 20 , 20 ); WorkerPool.Dispatch(ThreadListener); WorkerPool.Dispatch(IOCPDataHandler); }




C# code
             
               
public class ThreadListen : ITask { private ManagedIOCP MIOCP; #region ITask Members public ThreadListen(ManagedIOCP IOCP_HANDLE) { MIOCP = IOCP_HANDLE; } public void Execute(Sonic.Net.ThreadPool tp) { try { Socket Listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); System.Net.IPAddress ipa = System.Net.IPAddress.Any; // System.Net.IPAddress ipa = IPAddress.Parse("127.0.0.1"); IPEndPoint LocalEP = new IPEndPoint(ipa, 10001 ); Listener.Bind(LocalEP); Listener.Listen( 50 ); // Listener.Blocking = false; while ( true ) { System.Net.Sockets.Socket clientsocket = Listener.Accept(); MIOCP.Dispatch(clientsocket); System.Threading.Interlocked.Increment( ref iAcceptClientCount); string sLog = string .Format( " Client {0} Connected..ip:{1}. " , iAcceptClientCount.ToString(), clientsocket.RemoteEndPoint.ToString()); WindowsApplication7.Form1.loginfo.Info(sLog); } // Listener.BeginAccept(new AsyncCallback(AcceptCallback), Listener); } catch (Exception err) { // throw err; string sLog = " server error at Accept: " ; WindowsApplication7.Form1.loginfo.Info(sLog,err); } } }




C# code
             
               
public class HandleIOCPData : ITask { #region ITask Members private byte [] buffer; private ManagedIOCP MIOCP; public HandleIOCPData(ManagedIOCP IOCP_HANDLE) { MIOCP = IOCP_HANDLE; } public void Execute(Sonic.Net.ThreadPool tp) { buffer = new byte [ 500 ]; Socket ClientSocket; IOCPHandle hIOCP = MIOCP.Register(); while ( true ) { try { object obj = hIOCP.Wait(); // System.Windows.Forms.MessageBox.Show("Object recieved!"); // Process the object ClientSocket = (Socket)obj; if (ClientSocket.Connected == true ) { AsyncCallback Callback = new AsyncCallback(ReadCallback); ClientSocket.BeginReceive(buffer, 0 , buffer.Length, SocketFlags.None, Callback, ClientSocket); } } catch (ManagedIOCPException e) { // Console.WriteLine("ManagedIOCPException!\t{0}",e.ToString()); string sLog = " HandleIOCPData error " ; WindowsApplication7.Form1.loginfo.Info(sLog, e); break ; } catch (Exception e) { string sLog = " HandleIOCPData error " ; WindowsApplication7.Form1.loginfo.Info(sLog, e); // Console.WriteLine(e.ToString()); break ; } } } private void ReadCallback(System.IAsyncResult ar) { Socket ClientSocket = (Socket)ar.AsyncState; int nBytesRec = 0 ; try { nBytesRec = ClientSocket.EndReceive(ar); // if (ClientSocket != null && ClientSocket.Connected == true) // nBytesRec = ClientSocket.EndReceive(ar); // else // { // // disconnected... // string sLog = "ClientSocket is null"; // WindowsApplication7.Form1.loginfo.Info(sLog); // return; // } } catch (Exception err1) { string sLog = string .Format( " {0} ClientSocket disconnected... " , ClientSocket.RemoteEndPoint.ToString()); WindowsApplication7.Form1.loginfo.Info(sLog,err1); return ; } // System.Windows.Forms.MessageBox.Show("In callback!"); if (nBytesRec > 0 ) { // string Recieved = Encoding.ASCII.GetString(buffer, 0, nBytesRec); string Recieved = WindowsApplication7.Form1.DefaultEncoding.GetString(buffer).TrimEnd( ' \0 ' ); string sLog = " Server Receive: " + Recieved; WindowsApplication7.Form1.loginfo.Info(sLog); string sss1 = string .Format( " server send back message:u a {0} client,receive msg from u:{1},ticks:{2} " , ClientSocket.RemoteEndPoint.ToString(), Recieved,System.DateTime.Now.Ticks.ToString()); // ClientSocket.Send(System.Text.Encoding.UTF8.GetBytes(sss1)); ClientSocket.Send(WindowsApplication7.Form1.DefaultEncoding.GetBytes(sss1)); // Console.Write("\n"); // Console.Write(Recieved); // MessageBox.Show(Recieved); MIOCP.Dispatch(ClientSocket); } else { MIOCP.Dispatch(ClientSocket); } string sTest = " ssssss " ; } } 代码有点长咯,不知我讲明白了没 ?

测试client的send


C# code
             
               
public void Send() { System.String pString = " 11111111 " ; pString = ClientID + pString; byte [] b = WindowsApplication7.Form1.DefaultEncoding.GetBytes(pString); while ( true ) { if ( ! _IsConnect) { Console.WriteLine( " {0}未连接服务器,不可发送 " , ClientID); return ; } try { int i = sock.Send(b); if (i > 0 ) { byte [] bufer = new byte [ 500 ]; sock.Receive(bufer); // Console.Write("\n"); // Console.Write(System.Text.Encoding.UTF8.GetString(bufer).TrimEnd('\0')+"\n"); string sLog = WindowsApplication7.Form1.DefaultEncoding.GetString(bufer).TrimEnd( ' \0 ' ); WindowsApplication7.Form1.loginfo.Info(sLog); } } catch (Exception err) { // Console.WriteLine("{0}发送失败.error message:{1}", ClientID,err.Message); string sLog = " client Send error clientid:[ " + ClientID + " ] " ; WindowsApplication7.Form1.loginfo.Info(sLog, err); return ; } Thread.Sleep( 100 ); } }


主程序中调用client.send


C# code
             
               
private void Doit() { // System.Net.IPAddress ipa = System.Net.IPAddress.Parse("127.0.0.1"); System.Net.IPAddress ipa = System.Net.IPAddress.Parse( " 192.168.2.51 " ); System.Net.IPEndPoint ipe = new System.Net.IPEndPoint(ipa, 10001 ); for ( int i = 0 ; i < 1500 ; i ++ ) { Client c = new Client(); c.ClientID = string .Format( " ClientID {0}: " , i.ToString()); c.Connect(ipe); // Console.Write("\n"); // Console.Write("clientid {0} connect...", i.ToString()); Thread th = new Thread( new ThreadStart(c.Send)); th.IsBackground = true ; th.Start(); // c.Close(); // ChangeLabel(ToString()); iClientCount ++ ; Thread.Sleep( 100 ); } }

d
...........
可能我调用的方法错了.
不是这样用的.....
4500个连接,而且测试的只是服务端返回一条简单的确认信息,CPU占用率已经经常100%...不是很理想啊

你可能感兴趣的:(.net)