.net下完成端口(IOCP)的实现
代码是MSDN上的,其中缺少BufferManager类,现在将其加上并添加了一些自己的注释,其中有不对的地方还望大家多多指正
class SocketAsyncEventArgsPool { /// <summary> /// SocketAsyncEventArgs栈 /// </summary> Stack<SocketAsyncEventArgs> m_pool; /// <summary> /// 初始化SocketAsyncArg对象池 /// </summary> /// <param name="capacity">所能保持的最大连接数</param> public SocketAsyncEventArgsPool(int capacity) { m_pool = new Stack<SocketAsyncEventArgs>(capacity); } /// <summary> /// 将SocketAsyncEventArgs对象入栈 /// </summary> /// <param name="item">入栈的SocketAsyncEventArgs对象</param> public void Push(SocketAsyncEventArgs item) { if (item == null) { throw new ArgumentNullException("Items added to a SocketAsyncEventArgsPool cannot be null"); } lock (m_pool) { m_pool.Push(item); } } /// <summary> /// 将SocketAsyncEventArgs对象出栈 /// </summary> /// <returns>SocketAsyncEventArgs</returns> public SocketAsyncEventArgs Pop() { lock (m_pool) { return m_pool.Pop(); } } /// <summary> /// 已经存在的SocketAsyncEventArgs对象数量 /// </summary> public int Count { get { return m_pool.Count; } } }
缓冲区类
//该类用来创建一个可以被分给所有SocketAsyncEventArgs对象进行socket i/o操作的大缓冲区 //这样使得缓冲区可以被重复利用,防止内存碎片 //该类非线程安全 class BufferManager { /// <summary> /// 全部数量的缓冲池大小 /// </summary> int m_numBytes; /// <summary> /// 缓冲区管理维护基础字节数组 /// </summary> byte[] m_buffer; Stack<int> m_freeIndexPool; /// <summary> /// 当前传输的序号 /// </summary> int m_currentIndex; /// <summary> /// 缓冲大小 /// </summary> int m_bufferSize; /// <summary> /// 初始化缓冲区 /// </summary> /// <param name="totalBytes">设置最大缓冲区的大小</param> /// <param name="bufferSize">缓冲区大小</param> public BufferManager(int totalBytes, int bufferSize) { m_numBytes = totalBytes; m_currentIndex = 0; m_bufferSize = bufferSize; m_freeIndexPool = new Stack<int>(); } /// <summary> /// 初始化总缓冲区大小 /// </summary> public void InitBuffer() { //创建一个大的缓冲池,每个SocketAsyncEventArgs对象划分区域 m_buffer = new byte[m_numBytes]; } /// <summary> /// 从缓冲池分配一个缓冲区到指定的SocketAsyncEventArgs对象 /// </summary> /// <param name="args"></param> /// <returns></returns> public bool SetBuffer(SocketAsyncEventArgs args) { if (m_freeIndexPool.Count > 0) { args.SetBuffer(m_buffer, m_freeIndexPool.Pop(), m_bufferSize); } else { if ((m_numBytes - m_bufferSize) < m_currentIndex) { return false; } args.SetBuffer(m_buffer, m_currentIndex, m_bufferSize);//为某个SocketAsyncEventArgs分配相应缓冲区, m_currentIndex += m_bufferSize; } return true; } /// <summary> /// 将SocketAsyncEventArgs对象出栈,并释放其所占用的内存 /// </summary> /// <param name="args">需要释放的SocketAsyncEventArgs对象</param> public void FreeBuffer(SocketAsyncEventArgs args) { m_freeIndexPool.Push(args.Offset); args.SetBuffer(null, 0, 0); } }
自定义AsyncUserToken类
public class AsyncUserToken { public Socket Socket; private StringBuilder _sb; public AsyncUserToken() { _sb = new StringBuilder(); } public StringBuilder sb { get { return _sb; } set { _sb = value; } } }
具体的实现
//实现socket服务器的连接逻辑。 //接受一个连接后,从客户端读取所有数据,然后回发给客户端 //继续,直到客户端断开连接。 class Server { #region 变量定义 /// <summary> /// 同时连接的句柄最大数 /// </summary> private int m_numConnections; /// <summary> /// 每个I/O套接字的缓冲区最大字节数 /// </summary> private int m_receiveBufferSize; /// <summary> /// 所有socket共用的缓冲区 /// </summary> BufferManager m_bufferManager; const int opsToPreAlloc = 2; /// <summary> /// 用来侦听的socket /// </summary> Socket listenSocket; /// <summary> /// 写可重用的SocketAsyncEventArgs对象池,读取并接受socket操作 /// </summary> SocketAsyncEventArgsPool m_readWritePool; /// <summary> /// 由服务器接收的字节计数器 /// </summary> int m_totalBytesRead; /// <summary> /// 连接到服务器的客户端总数 /// </summary> int m_numConnectedSockets; /// <summary> /// 信号量,用于限制最大并发量 /// </summary> Semaphore m_maxNumberAcceptedClients; #endregion /// <summary> /// 创建一个server实例 /// </summary> /// <param name="numConnections">最大并发数量</param> /// <param name="receiveBufferSize">某个连接的缓冲区大小</param> public Server(int numConnections, int receiveBufferSize) { m_totalBytesRead = 0; m_numConnectedSockets = 0; m_numConnections = numConnections;//设置同时并发的最大数 m_receiveBufferSize = receiveBufferSize;//设置每个套接字的缓冲区最大值 m_bufferManager = new BufferManager(receiveBufferSize * numConnections * opsToPreAlloc, receiveBufferSize);//将最大缓冲区大小设置为最大连接数*某个缓冲区大小*2(用于接受和读取的缓冲区) m_readWritePool = new SocketAsyncEventArgsPool(numConnections);//设置SocketAsyncEventArgs池大小 m_maxNumberAcceptedClients = new Semaphore(numConnections, numConnections);//设置可同时并发的socket连接数 } /// <summary> /// 初始化SocketAsyncEventArgs对象,这些对象可重复利用 /// </summary> public void Init() { //初始化缓冲区,所有的io操作都在这里 m_bufferManager.InitBuffer(); //声明SocketAsyncEventArgs对象池 SocketAsyncEventArgs readWriteEventArg; for (int i = 0; i < m_numConnections; i++) { readWriteEventArg = new SocketAsyncEventArgs(); readWriteEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);//绑定IO完成事件 readWriteEventArg.UserToken = new AsyncUserToken(); m_bufferManager.SetBuffer(readWriteEventArg);//为每个SocketAsyncEventArg分配一个缓冲区空间 m_readWritePool.Push(readWriteEventArg);//将SocketAsyncEventArg入栈 } } /// <summary> /// 启动侦听 /// </summary> /// <param name="localEndPoint">终结点</param> public void Start(IPEndPoint localEndPoint) { //创建一个socket用于侦听 listenSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); listenSocket.Bind(localEndPoint); //设置侦听数 listenSocket.Listen(100); StartAccept(null);//开始接受socket Console.WriteLine("Press any key to terminate the server process...."); Console.ReadKey(); } //开始接受链接 public void StartAccept(SocketAsyncEventArgs acceptEventArg) { if (acceptEventArg == null)//判断是否为第一次运行,acceptEventArg该事件只是用来进行侦听连接 { acceptEventArg = new SocketAsyncEventArgs(); acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(AcceptEventArg_Completed);//绑定AcceptEventArg_Completed方法 } else { // 因为要重复利用,所以对象会被清空 acceptEventArg.AcceptSocket = null;//清空SocketAsyncEventArgs所对应的socket连接 } m_maxNumberAcceptedClients.WaitOne();//对线程进行阻塞,若超过允许连接的最大数则阻塞,直到接受到Release信号 bool willRaiseEvent = listenSocket.AcceptAsync(acceptEventArg); if (!willRaiseEvent)//有可能同步完成而不触发SocketAsyncEventArgs的completed事件,所以需要手动触发一次 { ProcessAccept(acceptEventArg); } } /// <summary> /// 该回调方法是用来在接受链接之后执行的 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void AcceptEventArg_Completed(object sender, SocketAsyncEventArgs e) { ProcessAccept(e); } /// <summary> /// 处理接受进来的socket连接 /// </summary> /// <param name="e"></param> private void ProcessAccept(SocketAsyncEventArgs e) { Interlocked.Increment(ref m_numConnectedSockets);//当前连接数+1(原子操作) Console.WriteLine("Client connection accepted. There are {0} clients connected to the server", m_numConnectedSockets); SocketAsyncEventArgs readEventArgs = m_readWritePool.Pop();//从SocketAsyncEventArgs池中出栈一个SocketAsyncEventArgs类用以进行I/O操作 ((AsyncUserToken)readEventArgs.UserToken).Socket = e.AcceptSocket;//接受链接 bool willRaiseEvent = e.AcceptSocket.ReceiveAsync(readEventArgs);//开始接受数据,并会触发readEventArgs的Completed事件 if (!willRaiseEvent)//原因同上 { ProcessReceive(readEventArgs); } StartAccept(e);//开始接受新的连接 } /// <summary> /// I/O完成事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void IO_Completed(object sender, SocketAsyncEventArgs e) { // 确定完成的操作类型(发送/接受) switch (e.LastOperation) { case SocketAsyncOperation.Receive: ProcessReceive(e); break; case SocketAsyncOperation.Send: ProcessSend(e); break; default: throw new ArgumentException("The last operation completed on the socket was not a receive or send"); } } // 当一个异步操作完成时,会调用该方法 // 若远程主机关闭了连接,则socket也同样会被关闭 // 若收到数据,则会返回给客户端 /// <summary> /// 处理接受过程. /// </summary> /// <param name="e">I/OSocketAsyncEventArgs而非Accpet</param> private void ProcessReceive(SocketAsyncEventArgs e) { AsyncUserToken token = (AsyncUserToken)e.UserToken; //判断socket中是否有数据在传输&&判断是否有异常 if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success) { Interlocked.Add(ref m_totalBytesRead, e.BytesTransferred);//接受总字节数+=本次传输字节数 Console.WriteLine("The server has read a total of {0} bytes", m_totalBytesRead); Console.WriteLine("Receive buff:" + Encoding.Default.GetString(e.Buffer, e.Offset, e.BytesTransferred)); token.sb.Append(Encoding.Default.GetString(e.Buffer, e.Offset, e.BytesTransferred)); byte[] b = Encoding.Default.GetBytes("this is send message"); e.SetBuffer(e.Offset, e.BytesTransferred);//将接受到的数据原样会送回client bool willRaiseEvent = token.Socket.SendAsync(e);//执行发送(会调用completed事件) if (!willRaiseEvent)//原因同上 { ProcessSend(e); } } else { CloseClientSocket(e);//关闭连接 } } // 该方法会在一个异步发送完成后调用 /// <summary> /// 发送后处理方法 /// </summary> /// <param name="e"></param> private void ProcessSend(SocketAsyncEventArgs e) { if (e.SocketError == SocketError.Success) { AsyncUserToken token = (AsyncUserToken)e.UserToken; // 继续读取数据 bool willRaiseEvent = token.Socket.ReceiveAsync(e);// if (!willRaiseEvent) { ProcessReceive(e); } } else { CloseClientSocket(e); } } /// <summary> /// 连接关闭 /// </summary> /// <param name="e">需要关闭的SocketAsyncEventArgs对象</param> private void CloseClientSocket(SocketAsyncEventArgs e) { AsyncUserToken token = e.UserToken as AsyncUserToken; //关闭与客户端连接的套接字 try { token.Socket.Shutdown(SocketShutdown.Send); } catch (Exception) { } token.Socket.Close(); //对连接数-1(原子操作) Interlocked.Decrement(ref m_numConnectedSockets); //允许接入新的连接 m_maxNumberAcceptedClients.Release(); Console.WriteLine("A client has been disconnected from the server. There are {0} clients connected to the server", m_numConnectedSockets); Console.WriteLine("here is total receive buffer:{0}", ((AsyncUserToken)e.UserToken).sb.ToString()); //将SocketAsyncEventArgs入栈,表明该资源可被继续使用 m_readWritePool.Push(e); } }
调用方式如下
public static void Main(String[] args) { IPAddress[] addressList = Dns.GetHostEntry(Environment.MachineName).AddressList; Server s = new Server(1, 1); s.Init(); s.Start(new IPEndPoint(addressList[addressList.Length - 1], 9900)); Console.ReadKey(); }