#unity/日常积累 # SocketAsyncEventArgs 类 - 参考 反馈 [](https://learn.microsoft.com/zh-cn/dotnet/api/system.net.sockets.socketasynceventargs?view=net-8.0#definition) ## 定义 命名空间: [System.Net.Sockets](https://learn.microsoft.com/zh-cn/dotnet/api/system.net.sockets?view=net-8.0) 程序集: System.Net.Sockets.dll Source: [SocketAsyncEventArgs.cs](https://github.com/dotnet/runtime/blob/5535e31a712343a63f5d7d796cd874e563e5ac14/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.cs) 表示异步套接字操作。 ``` cs public class SocketAsyncEventArgs : EventArgs, IDisposable ``` 继承 [Object](https://learn.microsoft.com/zh-cn/dotnet/api/system.object?view=net-8.0) [EventArgs](https://learn.microsoft.com/zh-cn/dotnet/api/system.eventargs?view=net-8.0) SocketAsyncEventArgs 实现 [IDisposable](https://learn.microsoft.com/zh-cn/dotnet/api/system.idisposable?view=net-8.0) [](https://learn.microsoft.com/zh-cn/dotnet/api/system.net.sockets.socketasynceventargs?view=net-8.0#examples) ## 示例 下面的代码示例为使用 [SocketAsyncEventArgs](https://learn.microsoft.com/zh-cn/dotnet/api/system.net.sockets.socketasynceventargs?view=net-8.0) 类的套接字服务器实现连接逻辑。 接受连接后,从客户端读取的所有数据都发送回客户端。 继续读取并回显到客户端模式,直到客户端断开连接。 此示例使用的 BufferManager 类显示在 方法的代码示例 [SetBuffer(Byte[], Int32, Int32)](https://learn.microsoft.com/zh-cn/dotnet/api/system.net.sockets.socketasynceventargs.setbuffer?view=net-8.0#system-net-sockets-socketasynceventargs-setbuffer(system-byte()-system-int32-system-int32)) 中。 此示例中使用的 SocketAsyncEventArgsPool 类显示在构造函数的代码示例 [SocketAsyncEventArgs](https://learn.microsoft.com/zh-cn/dotnet/api/system.net.sockets.socketasynceventargs.-ctor?view=net-8.0) 中。 ``` cs // Implements the connection logic for the socket server. // After accepting a connection, all data read from the client // is sent back to the client. The read and echo back to the client pattern // is continued until the client disconnects. 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; // Create an uninitialized server instance. // To start the server listening for connection requests // call the Init method followed by Start method // // the maximum number of connections the sample is designed to handle simultaneously // buffer size to use for each socket I/O operation public Server(int numConnections, int receiveBufferSize) { m_totalBytesRead = 0; m_numConnectedSockets = 0; m_numConnections = numConnections; m_receiveBufferSize = receiveBufferSize; // allocate buffers such that the maximum number of sockets can have one outstanding read and //write posted to the socket simultaneously m_bufferManager = new BufferManager(receiveBufferSize * numConnections * opsToPreAlloc, receiveBufferSize); m_readWritePool = new SocketAsyncEventArgsPool(numConnections); m_maxNumberAcceptedClients = new Semaphore(numConnections, numConnections); } // Initializes the server by preallocating reusable buffers and // context objects. These objects do not need to be preallocated // or reused, but it is done this way to illustrate how the API can // easily be used to create reusable objects to increase server performance. // public void Init() { // Allocates one large byte buffer which all I/O operations use a piece of. This gaurds // against memory fragmentation m_bufferManager.InitBuffer(); // preallocate pool of SocketAsyncEventArgs objects SocketAsyncEventArgs readWriteEventArg; for (int i = 0; i < m_numConnections; i++) { //Pre-allocate a set of reusable SocketAsyncEventArgs readWriteEventArg = new SocketAsyncEventArgs(); readWriteEventArg.Completed += new EventHandler(IO_Completed); // assign a byte buffer from the buffer pool to the SocketAsyncEventArg object m_bufferManager.SetBuffer(readWriteEventArg); // add SocketAsyncEventArg to the pool m_readWritePool.Push(readWriteEventArg); } } // Starts the server such that it is listening for // incoming connection requests. // // The endpoint which the server will listening // for connection requests on public void Start(IPEndPoint localEndPoint) { // create the socket which listens for incoming connections listenSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); listenSocket.Bind(localEndPoint); // start the server with a listen backlog of 100 connections listenSocket.Listen(100); // post accepts on the listening socket SocketAsyncEventArgs acceptEventArg = new SocketAsyncEventArgs(); acceptEventArg.Completed += new EventHandler(AcceptEventArg_Completed); StartAccept(acceptEventArg); //Console.WriteLine("{0} connected sockets with one outstanding receive posted to each....press any key", m_outstandingReadCount); Console.WriteLine("Press any key to terminate the server process...."); Console.ReadKey(); } // Begins an operation to accept a connection request from the client // // The context object to use when issuing // the accept operation on the server's listening socket public void StartAccept(SocketAsyncEventArgs acceptEventArg) { // loop while the method completes synchronously bool willRaiseEvent = false; while (!willRaiseEvent) { m_maxNumberAcceptedClients.WaitOne(); // socket must be cleared since the context object is being reused acceptEventArg.AcceptSocket = null; willRaiseEvent = listenSocket.AcceptAsync(acceptEventArg); if (!willRaiseEvent) { ProcessAccept(acceptEventArg); } } } // This method is the callback method associated with Socket.AcceptAsync // operations and is invoked when an accept operation is complete // void AcceptEventArg_Completed(object sender, SocketAsyncEventArgs e) { ProcessAccept(e); // Accept the next connection request StartAccept(e); } private void ProcessAccept(SocketAsyncEventArgs e) { Interlocked.Increment(ref m_numConnectedSockets); Console.WriteLine("Client connection accepted. There are {0} clients connected to the server", m_numConnectedSockets); // Get the socket for the accepted client connection and put it into the //ReadEventArg object user token SocketAsyncEventArgs readEventArgs = m_readWritePool.Pop(); readEventArgs.UserToken = e.AcceptSocket; // As soon as the client is connected, post a receive to the connection bool willRaiseEvent = e.AcceptSocket.ReceiveAsync(readEventArgs); if (!willRaiseEvent) { ProcessReceive(readEventArgs); } } // This method is called whenever a receive or send operation is completed on a socket // // SocketAsyncEventArg associated with the completed receive operation void IO_Completed(object sender, SocketAsyncEventArgs e) { // determine which type of operation just completed and call the associated handler 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"); } } // This method is invoked when an asynchronous receive operation completes. // If the remote host closed the connection, then the socket is closed. // If data was received then the data is echoed back to the client. // private void ProcessReceive(SocketAsyncEventArgs e) { // check if the remote host closed the connection if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success) { //increment the count of the total bytes receive by the server Interlocked.Add(ref m_totalBytesRead, e.BytesTransferred); Console.WriteLine("The server has read a total of {0} bytes", m_totalBytesRead); //echo the data received back to the client e.SetBuffer(e.Offset, e.BytesTransferred); Socket socket = (Socket)e.UserToken; bool willRaiseEvent = socket.SendAsync(e); if (!willRaiseEvent) { ProcessSend(e); } } else { CloseClientSocket(e); } } // This method is invoked when an asynchronous send operation completes. // The method issues another receive on the socket to read any additional // data sent from the client // // private void ProcessSend(SocketAsyncEventArgs e) { if (e.SocketError == SocketError.Success) { // done echoing data back to the client Socket socket = (Socket)e.UserToken; // read the next block of data send from the client bool willRaiseEvent = socket.ReceiveAsync(e); if (!willRaiseEvent) { ProcessReceive(e); } } else { CloseClientSocket(e); } } private void CloseClientSocket(SocketAsyncEventArgs e) { Socket socket = (Socket)e.UserToken; // close the socket associated with the client try { socket.Shutdown(SocketShutdown.Send); } // throws if client process has already closed catch (Exception) { } socket.Close(); // decrement the counter keeping track of the total number of clients connected to the server Interlocked.Decrement(ref m_numConnectedSockets); // Free the SocketAsyncEventArg so they can be reused by another client m_readWritePool.Push(e); m_maxNumberAcceptedClients.Release(); Console.WriteLine("A client has been disconnected from the server. There are {0} clients connected to the server", m_numConnectedSockets); } } ``` ## 注解 类 [SocketAsyncEventArgs](https://learn.microsoft.com/zh-cn/dotnet/api/system.net.sockets.socketasynceventargs?view=net-8.0) 是 类的一组增强的一部分,这些增强 [System.Net.Sockets.Socket](https://learn.microsoft.com/zh-cn/dotnet/api/system.net.sockets.socket?view=net-8.0) 功能提供可由专用的高性能套接字应用程序使用的替代异步模式。 此类专为需要高性能的网络服务器应用程序而设计。 例如,当接收大量数据) 时,应用程序可以独占或仅在目标热区域使用增强型异步模式 (。 这些增强功能的主要功能是避免在大容量异步套接字 I/O 期间重复分配和同步对象。 类当前实现的 [System.Net.Sockets.Socket](https://learn.microsoft.com/zh-cn/dotnet/api/system.net.sockets.socket?view=net-8.0) Begin/End 设计模式要求为每个异步套接字操作分配对象 [System.IAsyncResult](https://learn.microsoft.com/zh-cn/dotnet/api/system.iasyncresult?view=net-8.0) 。 在新的 [System.Net.Sockets.Socket](https://learn.microsoft.com/zh-cn/dotnet/api/system.net.sockets.socket?view=net-8.0) 类增强中,异步套接字操作由应用程序分配和维护的可重用 [SocketAsyncEventArgs](https://learn.microsoft.com/zh-cn/dotnet/api/system.net.sockets.socketasynceventargs?view=net-8.0) 对象描述。 高性能套接字应用程序非常清楚必须维持的重叠套接字操作的数量。 该应用程序可创建所需的 [SocketAsyncEventArgs](https://learn.microsoft.com/zh-cn/dotnet/api/system.net.sockets.socketasynceventargs?view=net-8.0) 对象数量。 例如,如果服务器应用程序需要随时有 15 个未完成的套接字接受操作以支持传入客户端连接速率,则它可以为此分配 15 个可重用 [SocketAsyncEventArgs](https://learn.microsoft.com/zh-cn/dotnet/api/system.net.sockets.socketasynceventargs?view=net-8.0) 对象。 使用此类执行异步套接字操作的模式包括以下步骤: 1. 分配一个新的 [SocketAsyncEventArgs](https://learn.microsoft.com/zh-cn/dotnet/api/system.net.sockets.socketasynceventargs?view=net-8.0) 上下文对象,或从应用程序池中获取一个空闲对象。 2. 将上下文对象的属性设置为即将 (完成回调方法执行的操作、数据缓冲区、缓冲区中的偏移量以及要传输的最大数据量,例如) 。 3. 调用适当的套接字方法 (xxxAsync) 以启动异步操作。 4. 如果异步套接字方法 (xxxAsync) 返回 true,请在回调中查询完成状态的上下文属性。 5. 如果异步套接字方法 (xxxAsync) 返回 false,则操作同步完成。 可查询上下文属性获取操作结果。 6. 重新使用上下文进行另一项操作,将其放回池中,或放弃它。 新的异步套接字操作上下文对象的生存期由应用程序代码和异步 I/O 引用的引用决定。 作为参数提交给异步套接字操作方法之一后,应用程序不必保留对异步套接字操作上下文对象的引用。 完成回调返回之前,应用程序会继续引用它。 但是,应用程序最好保留对上下文的引用,以便将来的异步套接字操作可以重复使用该引用。