无论什么平台,编写支持高并发性的网络服务器,瓶颈往往出在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封装了所有系统底层操作。
实际上.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是在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%...不是很理想啊
|