Server
Program
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
namespace XGCServerSocketAsyncEventArgs
{
class Program
{
static void Main(string[] args)
{
string ip = "127.0.0.1";
int port = 3000;
IPAddress ipAdress = IPAddress.Parse(ip);
IPEndPoint ipe = new IPEndPoint(ipAdress, port);
Server server = new Server(1024, 64);
server.Init();
server.Start(ipe);
}
}
}
Server
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// https://msdn.microsoft.com/zh-cn/library/system.net.sockets.socketasynceventargs.aspx
/// </summary>
namespace XGCServerSocketAsyncEventArgs
{
/// <summary>
/// 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.
///
/// 实现套接字服务器的连接逻辑。
/// 接受连接后,从客户端读取的所有数据都将发送回客户端。
/// 读取和回传到客户端模式将继续,直到客户端断开连接。
/// </summary>
class Server
{
/// <summary>
/// the maximum number of connections the sample is designed to handle simultaneously 例子中设计的同时处理的最大连接数
/// </summary>
private int m_numConnections;
/// <summary>
/// buffer size to use for each socket I/O operation
/// 用于每个Socket I/O 操作的缓冲区大小
/// </summary>
private int m_receiveBufferSize;
/// <summary>
/// represents a large reusable set of buffers for all socket operations
/// 表示用于所有套接字操作的大量可重用的缓冲区
/// </summary>
private BufferManager m_bufferManager;
/// <summary>
/// read, write (don't alloc buffer space for accepts)
/// 读,写(不为接受连接分配缓冲区空间)
/// </summary>
const int opsToPreAlloc = 2;
/// <summary>
/// the socket used to listen for incoming connection requests
/// </summary>
Socket listenSocket;
/// <summary>
/// pool of reusable SocketAsyncEventArgs objects for write, read and accept socket operations
/// 可重用SocketAsyncEventArgs对象的池,用于写入,读取和接受套接字操作
/// </summary>
SocketAsyncEventArgsPool m_readWritePool;
/// <summary>
/// counter of the total # bytes received by the server
/// 由服务器接收的总共#个字节的计数器
/// </summary>
private int m_totalBytesRead;
/// <summary>
/// the total number of clients connected to the server
/// </summary>
private int m_numConnectedSockets;
/// <summary>
/// 信号量
/// </summary>
Semaphore m_maxNumberAcceptedClients;
/// <summary>
/// Create an uninitialized server instance.
/// To start the server listening for connection requests call the Init method followed by Start method
/// 创建一个未初始化的服务器实例。
/// 要开始监听连接请求的服务器,请调用Init方法,然后调用Start方法
/// </summary>
/// <param name="numConnections">the maximum number of connections the sample is designed to handle simultaneously</param>
/// <param name="receivesBufferSize">buffer size to use for each socket I/O operation</param>
public Server(int numConnections, int receivesBufferSize)
{
m_totalBytesRead = 0;
m_numConnectedSockets = 0;
m_numConnections = numConnections;
m_receiveBufferSize = receivesBufferSize;
// 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(receivesBufferSize * numConnections * opsToPreAlloc, receivesBufferSize);
m_readWritePool = new SocketAsyncEventArgsPool(numConnections);
m_maxNumberAcceptedClients = new Semaphore(numConnections, numConnections);
}
/// <summary>
/// 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.
/// 通过预先分配可重用的缓冲区和上下文对象来初始化服务器。
/// 这些对象不需要预先分配或重用,但是这样做是为了说明如何轻松地使用API来创建可重用的对象来提高服务器性能。
/// </summary>
public void Init()
{
// Allocates one large byte buffer which all I/O operations use a piece of. This gaurds against memory fragmentation
// 分配一个大字节缓冲区, 所有的 I/O 操作都使用一个。 这样对抗内存分裂
m_bufferManager.InitBuffer();
// preallocate pool of SocketAsyncEventArgs objects
// 预先分配SocketAsyncEventArgs对象的池
SocketAsyncEventArgs readWriteEventArg;
for (int i = 0; i < m_numConnections; i++)
{
// Pre-allocate a set of reusable SocketAsyncEventArgs
// 预先分配一组可重用的SocketAsyncEventArgs
readWriteEventArg = new SocketAsyncEventArgs();
readWriteEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);
readWriteEventArg.UserToken = new AsyncUserToken();
// assign a byte buffer from the buffer pool to the SocketAsyncEventArg object
// 给SocketAsyncEventArg对象从缓冲池分配一个字节缓冲区
m_bufferManager.SetBuffer(readWriteEventArg);
// add SocketAsyncEventArg to the pool
m_readWritePool.Push(readWriteEventArg);
}
}
/// <summary>
/// Starts the server such that it is listening for incoming connection requests.
/// </summary>
/// <param name="localEndPoint">The endpoint which the server will listening for connection requests on</param>
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 在接收端口上接收
StartAccept(null);
//按任意键终止服务器进程....
Console.WriteLine("Server Started : Press any key to terminate the server process....");
Console.ReadKey();
}
/// <summary>
/// Begins an operation to accept a connection request from the client
/// </summary>
/// <param name="p">The context object to use when issuing the accept operation on the server's listening socket</param>
private void StartAccept(SocketAsyncEventArgs acceptEventArg)
{
if (acceptEventArg == null)
{
acceptEventArg = new SocketAsyncEventArgs();
acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(AcceptEventArgs_Completed);
}
else
{
// socket must be cleared since the context object is being reused
acceptEventArg.AcceptSocket = null;
}
m_maxNumberAcceptedClients.WaitOne();
bool willRaiseEvent = listenSocket.AcceptAsync(acceptEventArg);
if (!willRaiseEvent)
{
ProcessAccept(acceptEventArg);
}
}
/// <summary>
/// This method is the callback method associated with Socket.AcceptAsync operations and is invoked when an accept operation is complete
/// </summary>
private void AcceptEventArgs_Completed(object sender, SocketAsyncEventArgs e)
{
ProcessAccept(e);
}
private void ProcessAccept(SocketAsyncEventArgs e)
{
// 为多个线程共享的变量提供原子操作。以原子操作的形式递增指定变量的值并存储结果。
Interlocked.Increment(ref m_numConnectedSockets);
string ipClient = e.AcceptSocket.RemoteEndPoint.ToString();
Console.WriteLine("Server : Client [ {0} ] connected. There are {1} clients connected to server", ipClient, m_numConnectedSockets);
// Get the socket for the accepted client connection and put it into the
// ReadEventArg object user token
SocketAsyncEventArgs readEventArgs = m_readWritePool.Pop();
((AsyncUserToken)readEventArgs.UserToken).UserSocket = 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);
}
// Accept the next connection request
StartAccept(e);
}
/// <summary>
/// This method is called whenever a receive or send operation is completed on a socket
/// </summary>
/// <param name="e">SocketAsyncEventArg associated with the completed receive operation</param>
private 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");
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="e"></param>
private void ProcessReceive(SocketAsyncEventArgs e)
{
AsyncUserToken token = (AsyncUserToken)e.UserToken;
// e.BytesTransferred获取套接字操作中传输的字节数。
if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
{
// 对两个 32 位整数进行求和并用和替换第一个整数,上述操作作为一个原子操作完成。
Interlocked.Add(ref m_totalBytesRead, e.BytesTransferred);
Console.WriteLine("Server : 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);
bool willRaiseEvent = token.UserSocket.SendAsync(e);
if (!willRaiseEvent)
{
ProcessSend(e);
}
}
else
{
CloseClientSocket(e);
}
}
/// <summary>
/// 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
/// </summary>
private void ProcessSend(SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
// done echoing data back to the client 完成回传数据回到客户端
AsyncUserToken token = (AsyncUserToken)e.UserToken;
// read the next block of data send from the client 读取从客户端发送的下一个数据块
bool willRaiseEvent = token.UserSocket.ReceiveAsync(e);
if (!willRaiseEvent)
{
ProcessReceive(e);
}
}
else
{
CloseClientSocket(e);
}
}
private void CloseClientSocket(SocketAsyncEventArgs e)
{
AsyncUserToken token = e.UserToken as AsyncUserToken;
// close the socket associated with the client
try
{
token.UserSocket.Shutdown(SocketShutdown.Send);
}
// throws if client process has already closed
catch (Exception) { }
token.UserSocket.Close();
// Free the SocketAsyncEventArg so they can be reused by another client
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);
// Free the SocketAsyncEventArg so they can be reused by another client
m_readWritePool.Push(e);
}
}
}
AsyncUserToken
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace XGCServerSocketAsyncEventArgs
{
public class AsyncUserToken
{
public Socket UserSocket { get; set; }
public String ID { get; set; }
}
}
BufferManager
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
/// <summary>
/// https://msdn.microsoft.com/zh-cn/library/bb517542.aspx
/// </summary>
namespace XGCServerSocketAsyncEventArgs
{
/// <summary>
/// This class creates a single large buffer which can be divided up
/// and assigned to SocketAsyncEventArgs objects for use with each
/// socket I/O operation.
/// This enables bufffers to be easily reused and guards against
/// fragmenting heap memory.
/// The operations exposed on the BufferManager class are not thread safe.
///
/// 该类创建一个可以分割并分配给SocketAsyncEventArgs对象的单个大型缓冲区,用于每个套接字 I/O 操作。
/// 这样可以轻松地重用缓冲区,并防止破坏堆内存。
/// BufferManager类上暴露的操作不是线程安全的。
/// </summary>
public class BufferManager
{
private int m_numBytes; // the total number of bytes controlled by the buffer pool 由缓冲池控制的总字节数
private byte[] m_buffer; // the underlying byte array maintained by the Buffer Manager 缓冲区管理器维护的底层字节数组
private Stack<int> m_freeIndexPool; // 用来存储每个 释放的SocketAsyncEventArgs对象的数据缓冲区的偏移 的栈结构
private int m_currentIndex; // 相当于一个游标,判断缓冲区最后存储字节的位置
private int m_bufferSize; // 传入字节的大小
public BufferManager(int totalBytes, int bufferSize)
{
m_numBytes = totalBytes;
m_currentIndex = 0;
m_bufferSize = bufferSize;
m_freeIndexPool = new Stack<int>();
}
/// <summary>
/// Allocates buffer space used by the buffer pool 分配缓冲池使用的缓冲区
/// </summary>
public void InitBuffer()
{
// create one big large buffer and divide that out to each SocketAsyncEventArg object
// 创建一个大的大缓冲区,并将其分隔给每个SocketAsyncEventArg对象
m_buffer = new byte[m_numBytes];
}
/// <summary>
/// Assigns a buffer from the buffer pool to the specified SocketAsyncEventArgs object
/// <returns>true if the buffer was successfully set, else false</returns>
/// 将缓冲池的缓冲区分配给指定的SocketAsyncEventArgs对象
/// 如果缓冲区成功设置,则返回true,否则返回false
/// </summary>
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);
m_currentIndex += m_bufferSize;
}
return true;
}
/// <summary>
/// Removes the buffer from a SocketAsyncEventArg object.
/// This frees the buffer back to the buffer pool
/// 从SocketAsyncEventArg对象中删除缓冲区。
/// 将缓冲区释放回缓冲池
/// </summary>
public void FreeBugffer(SocketAsyncEventArgs args)
{
m_freeIndexPool.Push(args.Offset);
args.SetBuffer(null, 0, 0);
}
}
}
SocketAsyncEventArgsPool
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
/// <summary>
/// https://msdn.microsoft.com/zh-cn/library/system.net.sockets.socketasynceventargs.socketasynceventargs.aspx
/// </summary>
namespace XGCServerSocketAsyncEventArgs
{
/// <summary>
/// Represents a collection of reusable SocketAsyncEventArgs objects.
/// 表示可重用的SocketAsyncEventArgs对象的集合。
/// </summary>
public class SocketAsyncEventArgsPool
{
private Stack<SocketAsyncEventArgs> m_pool;
public SocketAsyncEventArgsPool(int capacity)
{
m_pool = new Stack<SocketAsyncEventArgs>(capacity);
}
public void Push(SocketAsyncEventArgs item)
{
if (item == null) { throw new ArgumentException("Items added to a SocketAsyncEventArgsPool cannot be null"); }
lock (m_pool)
{
m_pool.Push(item);
}
}
public SocketAsyncEventArgs Pop()
{
lock (m_pool)
{
return m_pool.Pop();
}
}
public int Count
{
get { return m_pool.Count; }
}
}
}
Client
Program
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace XGCClientSocketAsyncEventArgs
{
class Program
{
static void Main(string[] args)
{
string ip = "127.0.0.1";
int port = 3000;
Client m_client = new Client(ip, port);
m_client.Start();
}
}
}
Client
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace XGCClientSocketAsyncEventArgs
{
class Client
{
public Socket m_socket;
IPEndPoint m_endPoint;
private SocketAsyncEventArgs m_connectSAEA;
private SocketAsyncEventArgs m_sendSAEA;
public Client(string ip, int port)
{
m_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress iPAddress = IPAddress.Parse(ip);
m_endPoint = new IPEndPoint(iPAddress, port);
m_connectSAEA = new SocketAsyncEventArgs { RemoteEndPoint = m_endPoint };
}
public void Start()
{
m_connectSAEA.Completed += OnConnectedCompleted;
m_socket.ConnectAsync(m_connectSAEA);
Thread.Sleep(5000);
Send("Hello");
//按任意键终止服务器进程....
//Console.WriteLine("Client : Press any key to terminate the server process....");
Console.ReadKey();
}
public void OnConnectedCompleted(object sender, SocketAsyncEventArgs e)
{
if (e.SocketError != SocketError.Success) return;
Socket socket = sender as Socket;
string iPRemote = socket.RemoteEndPoint.ToString();
Console.WriteLine("Client : 连接服务器 [ {0} ] 成功",iPRemote);
SocketAsyncEventArgs receiveSAEA = new SocketAsyncEventArgs();
byte[] receiveBuffer = new byte[1024 * 4];
receiveSAEA.SetBuffer(receiveBuffer, 0, receiveBuffer.Length);
receiveSAEA.Completed += OnReceiveCompleted;
receiveSAEA.RemoteEndPoint = m_endPoint;
socket.ReceiveAsync(receiveSAEA);
}
private void OnReceiveCompleted(object sender, SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.OperationAborted) return;
Socket socket = sender as Socket;
if (e.SocketError == SocketError.Success && e.BytesTransferred > 0)
{
string ipAdress = socket.RemoteEndPoint.ToString();
int lengthBuffer = e.BytesTransferred;
byte[] receiveBuffer = e.Buffer;
byte[] buffer = new byte[lengthBuffer];
Buffer.BlockCopy(receiveBuffer, 0, buffer, 0, lengthBuffer);
string msg = Encoding.Unicode.GetString(buffer);
Console.WriteLine("Client : receive message[ {0} ],from Server[ {1} ]", msg, ipAdress);
socket.ReceiveAsync(e);
}
else if (e.SocketError == SocketError.ConnectionReset && e.BytesTransferred == 0)
{
Console.WriteLine("Client: 服务器断开连接 ");
}
else
{
return;
}
}
public void Send(string msg)
{
byte[] sendBuffer = Encoding.Unicode.GetBytes(msg);
if (m_sendSAEA == null)
{
m_sendSAEA = new SocketAsyncEventArgs();
m_sendSAEA.Completed += OnSendCompleted;
}
m_sendSAEA.SetBuffer(sendBuffer, 0, sendBuffer.Length);
if (m_socket != null)
{
m_socket.SendAsync(m_sendSAEA);
}
}
private void OnSendCompleted(object sender, SocketAsyncEventArgs e)
{
if (e.SocketError != SocketError.Success) return;
Socket socket = sender as Socket;
byte[] sendBuffer = e.Buffer;
string sendMsg = Encoding.Unicode.GetString(sendBuffer);
Console.WriteLine("Client : Send message [ {0} ] to Serer[ {1} ]", sendMsg, socket.RemoteEndPoint.ToString());
}
public void DisConnect()
{
if (m_socket != null)
{
try
{
m_socket.Shutdown(SocketShutdown.Both);
}
catch (SocketException se)
{
}
finally
{
m_socket.Close();
}
}
}
}
}
运行
Server
Client
源码下载:http://download.youkuaiyun.com/download/fuemocheng/10022537