引言
我一直在探寻一个高性能的Socket客户端代码。以前,我使用Socket类写了一些基于传统异步编程模型的代码(BeginSend、BeginReceive,等等)也看过很多博客的知识,在linux中有poll和epoll来实现,在windows下面
微软MSDN中也提供了SocketAsyncEventArgs这个类来实现IOCP 地址:https://msdn.microsoft.com/zh-cn/library/system.net.sockets.socketasynceventargs.aspx
NET Framework中的APM也称为Begin/End模式。这是因为会调用Begin方法来启动异步操作,然后返回一个IAsyncResult 对象。可以选择将一个代理作为参数提供给Begin方法,异步操作完成时会调用该方法。或者,一个线程可以等待 IAsyncResult.AsyncWaitHandle。当回调被调用或发出等待信号时,就会调用End方法来获取异步操作的结果。这种模式很灵活,使用相对简单,在 .NET Framework 中非常常见。
但是,您必须注意,如果进行大量异步套接字操作,是要付出代价的。针对每次操作,都必须创建一个IAsyncResult对象,而且该对象不能被重复使用。由于大量使用对象分配和垃圾收集,这会影响性能。为了解决这个问题,新版本提供了另一个使用套接字上执行异步I/O的方法模式。这种新模式并不要求为每个套接字操作分配操作上下文对象。

代码下载:http://download.csdn.net/detail/zhujunxxxxx/8431289 这里的代码优化了的
目标
在上面微软提供的例子我觉得不是很完整,没有具体一个流程,只是受到客户端消息后发送相同内容给客户端,初学者不容易看懂流程,因为我花了一天的时间来实现一个功能齐全的IOCP服务器,

效果如下

代码

首先是ICOPServer.cs 这个类是IOCP服务器的核心类,目前这个类是网络上比较全的代码,MSDN上面的例子都没有我的全

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading; namespace ServerTest
{
/// <summary>
/// IOCP SOCKET服务器
/// </summary>
public class IOCPServer : IDisposable
{
const int opsToPreAlloc = ;
#region Fields
/// <summary>
/// 服务器程序允许的最大客户端连接数
/// </summary>
private int _maxClient; /// <summary>
/// 监听Socket,用于接受客户端的连接请求
/// </summary>
private Socket _serverSock; /// <summary>
/// 当前的连接的客户端数
/// </summary>
private int _clientCount; /// <summary>
/// 用于每个I/O Socket操作的缓冲区大小
/// </summary>
private int _bufferSize = ; /// <summary>
/// 信号量
/// </summary>
Semaphore _maxAcceptedClients; /// <summary>
/// 缓冲区管理
/// </summary>
BufferManager _bufferManager; /// <summary>
/// 对象池
/// </summary>
SocketAsyncEventArgsPool _objectPool; private bool disposed = false; #endregion #region Properties /// <summary>
/// 服务器是否正在运行
/// </summary>
public bool IsRunning { get; private set; }
/// <summary>
/// 监听的IP地址
/// </summary>
public IPAddress Address { get; private set; }
/// <summary>
/// 监听的端口
/// </summary>
public int Port { get; private set; }
/// <summary>
/// 通信使用的编码
/// </summary>
public Encoding Encoding { get; set; } #endregion #region Ctors /// <summary>
/// 异步IOCP SOCKET服务器
/// </summary>
/// <param name="listenPort">监听的端口</param>
/// <param name="maxClient">最大的客户端数量</param>
public IOCPServer(int listenPort, int maxClient)
: this(IPAddress.Any, listenPort, maxClient)
{
} /// <summary>
/// 异步Socket TCP服务器
/// </summary>
/// <param name="localEP">监听的终结点</param>
/// <param name="maxClient">最大客户端数量</param>
public IOCPServer(IPEndPoint localEP, int maxClient)
: this(localEP.Address, localEP.Port, maxClient)
{
} /// <summary>
/// 异步Socket TCP服务器
/// </summary>
/// <param name="localIPAddress">监听的IP地址</param>
/// <param name="listenPort">监听的端口</param>
/// <param name="maxClient">最大客户端数量</param>
public IOCPServer(IPAddress localIPAddress, int listenPort, int maxClient)
{
this.Address = localIPAddress;
this.Port = listenPort;
this.Encoding = Encoding.Default; _maxClient = maxClient; _serverSock = new Socket(localIPAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp); _bufferManager = new BufferManager(_bufferSize * _maxClient * opsToPreAlloc, _bufferSize); _objectPool = new SocketAsyncEventArgsPool(_maxClient); _maxAcceptedClients = new Semaphore(_maxClient, _maxClient);
} #endregion #region 初始化 /// <summary>
/// 初始化函数
/// </summary>
public void Init()
{
// Allocates one large byte buffer which all I/O operations use a piece of. This gaurds
// against memory fragmentation
_bufferManager.InitBuffer(); // preallocate pool of SocketAsyncEventArgs objects
SocketAsyncEventArgs readWriteEventArg; for (int i = ; i < _maxClient; i++)
{
//Pre-allocate a set of reusable SocketAsyncEventArgs
readWriteEventArg = new SocketAsyncEventArgs();
readWriteEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(OnIOCompleted);
readWriteEventArg.UserToken = null; // assign a byte buffer from the buffer pool to the SocketAsyncEventArg object
_bufferManager.SetBuffer(readWriteEventArg); // add SocketAsyncEventArg to the pool
_objectPool.Push(readWriteEventArg);
} } #endregion #region Start
/// <summary>
/// 启动
/// </summary>
public void Start()
{
if (!IsRunning)
{
Init();
IsRunning = true;
IPEndPoint localEndPoint = new IPEndPoint(Address, Port);
// 创建监听socket
_serverSock = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
//_serverSock.ReceiveBufferSize = _bufferSize;
//_serverSock.SendBufferSize = _bufferSize;
if (localEndPoint.AddressFamily == AddressFamily.InterNetworkV6)
{
// 配置监听socket为 dual-mode (IPv4 & IPv6)
// 27 is equivalent to IPV6_V6ONLY socket option in the winsock snippet below,
_serverSock.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName), false);
_serverSock.Bind(new IPEndPoint(IPAddress.IPv6Any, localEndPoint.Port));
}
else
{
_serverSock.Bind(localEndPoint);
}
// 开始监听
_serverSock.Listen(this._maxClient);
// 在监听Socket上投递一个接受请求。
StartAccept(null);
}
}
#endregion #region Stop /// <summary>
/// 停止服务
/// </summary>
public void Stop()
{
if (IsRunning)
{
IsRunning = false;
_serverSock.Close();
//TODO 关闭对所有客户端的连接 }
} #endregion #region Accept /// <summary>
/// 从客户端开始接受一个连接操作
/// </summary>
private void StartAccept(SocketAsyncEventArgs asyniar)
{
if (asyniar == null)
{
asyniar = new SocketAsyncEventArgs();
asyniar.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
}
else
{
//socket must be cleared since the context object is being reused
asyniar.AcceptSocket = null;
}
_maxAcceptedClients.WaitOne();
if (!_serverSock.AcceptAsync(asyniar))
{
ProcessAccept(asyniar);
//如果I/O挂起等待异步则触发AcceptAsyn_Asyn_Completed事件
//此时I/O操作同步完成,不会触发Asyn_Completed事件,所以指定BeginAccept()方法
}
} /// <summary>
/// accept 操作完成时回调函数
/// </summary>
/// <param name="sender">Object who raised the event.</param>
/// <param name="e">SocketAsyncEventArg associated with the completed accept operation.</param>
private void OnAcceptCompleted(object sender, SocketAsyncEventArgs e)
{
ProcessAccept(e);
} /// <summary>
/// 监听Socket接受处理
/// </summary>
/// <param name="e">SocketAsyncEventArg associated with the completed accept operation.</param>
private void ProcessAccept(SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
Socket s = e.AcceptSocket;//和客户端关联的socket
if (s.Connected)
{
try
{ Interlocked.Increment(ref _clientCount);//原子操作加1
SocketAsyncEventArgs asyniar = _objectPool.Pop();
asyniar.UserToken = s; Log4Debug(String.Format("客户 {0} 连入, 共有 {1} 个连接。", s.RemoteEndPoint.ToString(), _clientCount)); if (!s.ReceiveAsync(asyniar))//投递接收请求
{
ProcessReceive(asyniar);
}
}
catch (SocketException ex)
{
Log4Debug(String.Format("接收客户 {0} 数据出错, 异常信息: {1} 。", s.RemoteEndPoint, ex.ToString()));
//TODO 异常处理
}
//投递下一个接受请求
StartAccept(e);
}
}
} #endregion #region 发送数据 /// <summary>
/// 异步的发送数据
/// </summary>
/// <param name="e"></param>
/// <param name="data"></param>
public void Send(SocketAsyncEventArgs e, byte[] data)
{
if (e.SocketError == SocketError.Success)
{
Socket s = e.AcceptSocket;//和客户端关联的socket
if (s.Connected)
{
Array.Copy(data, , e.Buffer, , data.Length);//设置发送数据 //e.SetBuffer(data, 0, data.Length); //设置发送数据
if (!s.SendAsync(e))//投递发送请求,这个函数有可能同步发送出去,这时返回false,并且不会引发SocketAsyncEventArgs.Completed事件
{
// 同步发送时处理发送完成事件
ProcessSend(e);
}
else
{
CloseClientSocket(e);
}
}
}
} /// <summary>
/// 同步的使用socket发送数据
/// </summary>
/// <param name="socket"></param>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <param name="size"></param>
/// <param name="timeout"></param>
public void Send(Socket socket, byte[] buffer, int offset, int size, int timeout)
{
socket.SendTimeout = ;
int startTickCount = Environment.TickCount;
int sent = ; // how many bytes is already sent
do
{
if (Environment.TickCount > startTickCount + timeout)
{
//throw new Exception("Timeout.");
}
try
{
sent += socket.Send(buffer, offset + sent, size - sent, SocketFlags.None);
}
catch (SocketException ex)
{
if (ex.SocketErrorCode == SocketError.WouldBlock ||
ex.SocketErrorCode == SocketError.IOPending ||
ex.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
{
// socket buffer is probably full, wait and try again
Thread.Sleep();
}
else
{
throw ex; // any serious error occurr
}
}
} while (sent < size);
} /// <summary>
/// 发送完成时处理函数
/// </summary>
/// <param name="e">与发送完成操作相关联的SocketAsyncEventArg对象</param>
private void ProcessSend(SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
Socket s = (Socket)e.UserToken; //TODO
}
else
{
CloseClientSocket(e);
}
} #endregion #region 接收数据 /// <summary>
///接收完成时处理函数
/// </summary>
/// <param name="e">与接收完成操作相关联的SocketAsyncEventArg对象</param>
private void ProcessReceive(SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)//if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
{
// 检查远程主机是否关闭连接
if (e.BytesTransferred > )
{
Socket s = (Socket)e.UserToken;
//判断所有需接收的数据是否已经完成
if (s.Available == )
{
//从侦听者获取接收到的消息。
//String received = Encoding.ASCII.GetString(e.Buffer, e.Offset, e.BytesTransferred);
//echo the data received back to the client
//e.SetBuffer(e.Offset, e.BytesTransferred); byte[] data = new byte[e.BytesTransferred];
Array.Copy(e.Buffer, e.Offset, data, , data.Length);//从e.Buffer块中复制数据出来,保证它可重用 string info = Encoding.Default.GetString(data);
Log4Debug(String.Format("收到 {0} 数据为 {1}", s.RemoteEndPoint.ToString(), info));
//TODO 处理数据 //增加服务器接收的总字节数。
} if (!s.ReceiveAsync(e))//为接收下一段数据,投递接收请求,这个函数有可能同步完成,这时返回false,并且不会引发SocketAsyncEventArgs.Completed事件
{
//同步接收时处理接收完成事件
ProcessReceive(e);
}
}
}
else
{
CloseClientSocket(e);
}
} #endregion #region 回调函数 /// <summary>
/// 当Socket上的发送或接收请求被完成时,调用此函数
/// </summary>
/// <param name="sender">激发事件的对象</param>
/// <param name="e">与发送或接收完成操作相关联的SocketAsyncEventArg对象</param>
private void OnIOCompleted(object sender, SocketAsyncEventArgs e)
{
// Determine which type of operation just completed and call the associated handler.
switch (e.LastOperation)
{
case SocketAsyncOperation.Accept:
ProcessAccept(e);
break;
case SocketAsyncOperation.Receive:
ProcessReceive(e);
break;
default:
throw new ArgumentException("The last operation completed on the socket was not a receive or send");
}
} #endregion #region Close
/// <summary>
/// 关闭socket连接
/// </summary>
/// <param name="e">SocketAsyncEventArg associated with the completed send/receive operation.</param>
private void CloseClientSocket(SocketAsyncEventArgs e)
{
Log4Debug(String.Format("客户 {0} 断开连接!", ((Socket)e.UserToken).RemoteEndPoint.ToString()));
Socket s = e.UserToken as Socket;
CloseClientSocket(s, e);
} /// <summary>
/// 关闭socket连接
/// </summary>
/// <param name="s"></param>
/// <param name="e"></param>
private void CloseClientSocket(Socket s, SocketAsyncEventArgs e)
{
try
{
s.Shutdown(SocketShutdown.Send);
}
catch (Exception)
{
// Throw if client has closed, so it is not necessary to catch.
}
finally
{
s.Close();
}
Interlocked.Decrement(ref _clientCount);
_maxAcceptedClients.Release();
_objectPool.Push(e);//SocketAsyncEventArg 对象被释放,压入可重用队列。
}
#endregion #region Dispose
/// <summary>
/// Performs application-defined tasks associated with freeing,
/// releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
} /// <summary>
/// Releases unmanaged and - optionally - managed resources
/// </summary>
/// <param name="disposing"><c>true</c> to release
/// both managed and unmanaged resources; <c>false</c>
/// to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
try
{
Stop();
if (_serverSock != null)
{
_serverSock = null;
}
}
catch (SocketException ex)
{
//TODO 事件
}
}
disposed = true;
}
}
#endregion public void Log4Debug(string msg)
{
Console.WriteLine("notice:" + msg);
} }
}

BufferManager.cs 这个类是缓存管理类,是采用MSDN上面的例子一样的 地址: https://msdn.microsoft.com/zh-cn/library/bb517542.aspx

SocketAsyncEventArgsPool.cs 这个类也是来自MSDN的 地址:https://msdn.microsoft.com/zh-cn/library/system.net.sockets.socketasynceventargs.aspx

需要的话自己到MSDN网站上去取,我就不贴出来了

服务器端

static void Main(string[] args)
{ IOCPServer server = new IOCPServer(, );
server.Start();
Console.WriteLine("服务器已启动....");
System.Console.ReadLine();
}

客户端

客户端代码也是很简单

static void Main(string[] args)
{
IPAddress remote = IPAddress.Parse("192.168.3.4");
client c = new client(, remote); c.connect();
Console.WriteLine("服务器连接成功!");
while (true)
{
Console.Write("send>");
string msg = Console.ReadLine();
if (msg == "exit")
break;
c.send(msg);
}
c.disconnect();
Console.ReadLine();
}

client.cs

public class client
{ public TcpClient _client; public int port; public IPAddress remote; public client(int port, IPAddress remote)
{ this.port = port;
this.remote = remote;
} public void connect()
{
this._client = new TcpClient();
_client.Connect(remote, port);
}
public void disconnect()
{
_client.Close();
}
public void send(string msg)
{
byte[] data = Encoding.Default.GetBytes(msg);
_client.GetStream().Write(data, , data.Length);
}
}

IOCPClient类,使用SocketAsyncEventArgs类建立一个Socket客户端。虽然MSDN说这个类特别设计给网络服务器应用,但也没有限制在客户端代码中使用APM。下面给出了IOCPClient类的样例代码:

public class IOCPClient
{
/// <summary>
/// 连接服务器的socket
/// </summary>
private Socket _clientSock; /// <summary>
/// 用于服务器执行的互斥同步对象
/// </summary>
private static Mutex mutex = new Mutex();
/// <summary>
/// Socket连接标志
/// </summary>
private Boolean _connected = false; private const int ReceiveOperation = , SendOperation = ; private static AutoResetEvent[]
autoSendReceiveEvents = new AutoResetEvent[]
{
new AutoResetEvent(false),
new AutoResetEvent(false)
}; /// <summary>
/// 服务器监听端点
/// </summary>
private IPEndPoint _remoteEndPoint; public IOCPClient(IPEndPoint local, IPEndPoint remote)
{
_clientSock = new Socket(local.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
_remoteEndPoint = remote;
} #region 连接服务器 /// <summary>
/// 连接远程服务器
/// </summary>
public void Connect()
{
SocketAsyncEventArgs connectArgs = new SocketAsyncEventArgs(); connectArgs.UserToken = _clientSock;
connectArgs.RemoteEndPoint = _remoteEndPoint;
connectArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnConnected);
mutex.WaitOne();
if (!_clientSock.ConnectAsync(connectArgs))//异步连接
{
ProcessConnected(connectArgs);
} }
/// <summary>
/// 连接上的事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void OnConnected(object sender, SocketAsyncEventArgs e)
{
mutex.ReleaseMutex();
//设置Socket已连接标志。
_connected = (e.SocketError == SocketError.Success);
}
/// <summary>
/// 处理连接服务器
/// </summary>
/// <param name="e"></param>
private void ProcessConnected(SocketAsyncEventArgs e)
{
//TODO
} #endregion #region 发送消息
/// <summary>
/// 向服务器发送消息
/// </summary>
/// <param name="data"></param>
public void Send(byte[] data)
{
SocketAsyncEventArgs asyniar = new SocketAsyncEventArgs();
asyniar.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendComplete);
asyniar.SetBuffer(data, , data.Length);
asyniar.UserToken = _clientSock;
asyniar.RemoteEndPoint = _remoteEndPoint;
autoSendReceiveEvents[SendOperation].WaitOne();
if (!_clientSock.SendAsync(asyniar))//投递发送请求,这个函数有可能同步发送出去,这时返回false,并且不会引发SocketAsyncEventArgs.Completed事件
{
// 同步发送时处理发送完成事件
ProcessSend(asyniar);
}
} /// <summary>
/// 发送操作的回调方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnSendComplete(object sender, SocketAsyncEventArgs e)
{
//发出发送完成信号。
autoSendReceiveEvents[SendOperation].Set();
ProcessSend(e);
} /// <summary>
/// 发送完成时处理函数
/// </summary>
/// <param name="e">与发送完成操作相关联的SocketAsyncEventArg对象</param>
private void ProcessSend(SocketAsyncEventArgs e)
{
//TODO
}
#endregion #region 接收消息
/// <summary>
/// 开始监听服务端数据
/// </summary>
/// <param name="e"></param>
public void StartRecive(SocketAsyncEventArgs e)
{
//准备接收。
Socket s = e.UserToken as Socket;
byte[] receiveBuffer = new byte[];
e.SetBuffer(receiveBuffer, , receiveBuffer.Length);
e.Completed += new EventHandler<SocketAsyncEventArgs>(OnReceiveComplete);
autoSendReceiveEvents[ReceiveOperation].WaitOne();
if (!s.ReceiveAsync(e))
{
ProcessReceive(e);
}
} /// <summary>
/// 接收操作的回调方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnReceiveComplete(object sender, SocketAsyncEventArgs e)
{
//发出接收完成信号。
autoSendReceiveEvents[ReceiveOperation].Set();
ProcessReceive(e);
} /// <summary>
///接收完成时处理函数
/// </summary>
/// <param name="e">与接收完成操作相关联的SocketAsyncEventArg对象</param>
private void ProcessReceive(SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
// 检查远程主机是否关闭连接
if (e.BytesTransferred > )
{
Socket s = (Socket)e.UserToken;
//判断所有需接收的数据是否已经完成
if (s.Available == )
{
byte[] data = new byte[e.BytesTransferred];
Array.Copy(e.Buffer, e.Offset, data, , data.Length);//从e.Buffer块中复制数据出来,保证它可重用 //TODO 处理数据
} if (!s.ReceiveAsync(e))//为接收下一段数据,投递接收请求,这个函数有可能同步完成,这时返回false,并且不会引发SocketAsyncEventArgs.Completed事件
{
//同步接收时处理接收完成事件
ProcessReceive(e);
}
}
}
} #endregion public void Close()
{
_clientSock.Disconnect(false);
} /// <summary>
/// 失败时关闭Socket,根据SocketError抛出异常。
/// </summary>
/// <param name="e"></param> private void ProcessError(SocketAsyncEventArgs e)
{
Socket s = e.UserToken as Socket;
if (s.Connected)
{
//关闭与客户端关联的Socket
try
{
s.Shutdown(SocketShutdown.Both);
}
catch (Exception)
{
//如果客户端处理已经关闭,抛出异常
}
finally
{
if (s.Connected)
{
s.Close();
}
}
}
//抛出SocketException
throw new SocketException((Int32)e.SocketError);
} /// <summary>
/// 释放SocketClient实例
/// </summary>
public void Dispose()
{
mutex.Close();
autoSendReceiveEvents[SendOperation].Close();
autoSendReceiveEvents[ReceiveOperation].Close();
if (_clientSock.Connected)
{
_clientSock.Close();
}
} }

最新文章

  1. Firemonkey 载入 Style 皮肤 (*.fsf 二进制文件) 速度测试
  2. SimpleDateFormate的使用
  3. Codeforces Round #209 (Div. 2) B. Permutation
  4. struts2-通配符映射(基本没啥卵用)和动态调用
  5. 实现Asp.Net MVC无刷新分页
  6. sqlserver 2000事务复制问题
  7. java.util.ConcurrentModificationException 解决办法(转)
  8. (3)选择元素——(5)为项目列表加样式(Styling list-item levels)
  9. kettle查询出来的真实值被识别为null
  10. Android布局优化之层级优化
  11. 易理解java代码8皇后问题
  12. Java --Annotation学习心得体会及笔记
  13. 通过数据分析告诉你北京Python开发的现状
  14. java 网络通信传输层协议——UDP和TCP
  15. 第53章 结束会话端点(End Session Endpoint) - Identity Server 4 中文文档(v1.0.0)
  16. 微信小程序底部tabbar
  17. Java学习笔记之——接口
  18. C# 中那些常用的工具类(Utility Class)(二)
  19. DBMS_METADATA.set_transform_param格式化输出
  20. leecode第二百一十五题(数组中的第K个最大元素)

热门文章

  1. 【转】webservice
  2. .Net Core 扩展使用Refit
  3. iscsi使用教程(下)
  4. Codeforces Round #518 (Div. 2) D(计数DP)
  5. SQL数据库操作整理
  6. 关于CSRF攻击详解
  7. Python中使用Type hinting 和 annotations
  8. Mysql的子查询与连接查询
  9. 浅谈关于SRAM与DRAM的区别
  10. BZOJ 4892 [Tjoi2017]dna 哈希+二分