Socket 异步
摘要:
System.Net.Sockets.Sockte 类有一组增强功能,提供可供专用的高性能套接字应用程序使用的可选异步模式,SocketAsyncEventArgs 类就是这一组增强功能的一部分。 该类专为需要高性能的网络服务器应用程序而设计。 应用程序可以完全使用增强的异步模式,也可以仅仅在目标热点区域(例如,在接收大量数据时)使用此模式。
这些增强功能的主要特点是可以避免在异步套接字 I/O 量非常大时发生重复的对象分配和同步。
在新的 System.Net.Sockets.Sockte 类增强功能中,异步套接字操作由分配的可重用 SocketAsyncEventArgs 对象描述并由应用程序维护。 高性能套接字应用程序非常清楚地知道必须保持的重叠的套接字操作的量。 应用程序可以根据自身需要创建任意多的SocketAsyncEventArgs 对象。 例如,如果服务器应用程序需要总是有 15 个未完成的套接字接收操作来支持传入客户端连接率,那么可以为此分配 15 个可重用的 SocketAsyncEventArgs 对象。
使用此类执行异步套接字操作的模式包含以下步骤:
分配一个新的 SocketAsyncEventArgs 上下文对象,或者从应用程序池中获取一个空闲的此类对象。
将该上下文对象的属性设置为要执行的操作(例如,完成回调方法、数据缓冲区、缓冲区偏移量以及要传输的最大数据量)。
调用适当的套接字方法 (xxxAsync) 以启动异步操作。
如果异步套接字方法 (xxxAsync) 返回 true,则在回调中查询上下文属性来获取完成状态。
如果异步套接字方法 (xxxAsync) 返回 false,则说明操作是同步完成的。 可以查询上下文属性来获取操作结果。
将该上下文重用于另一个操作,将它放回到应用程序池中,或者将它丢弃。
新的异步套接字操作上下文对象的生命周期由应用程序代码引用和异步 I/O 引用决定。 在对异步套接字操作上下文对象的引用作为一个参数提交给某个异步套接字操作方法之后,应用程序不必保留该引用。 在完成回调返回之前将一直引用该对象。 但是,应用程序保留对上下文的引用是有好处的,这样该引用就可以重用于将来的异步套接字操作。
下面的代码示例实现使用 SocketAsyncEventArgs 类的套接字服务器的连接逻辑。 接受连接之后,从客户端读取的所有数据都将发回客户端。 客户端模式的读取和回送会一直继续到客户端断开连接
一、服务端
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading; namespace ConsoleTest
{
public class Server
{
/// <summary>
/// 同时处理的最大连接数
/// </summary>
private int m_numConnections; /// <summary>
/// 每个Socket IO可使用的缓冲区大小
/// </summary>
private int m_receiveBufferSize; BufferManager m_bufferManager;
const int opsToPreAlloc = 2;
Socket listenSocket;
SocketAsyncEventArgsPool m_readWritePool; /// <summary>
/// 服务端接受的总数据大小
/// </summary>
int m_totalBytesRead; /// <summary>
/// 连接到服务端的客户端数量
/// </summary>
int m_numConnectedSockets;
Semaphore m_maxNumberAcceptedClients; 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);
m_readWritePool = new SocketAsyncEventArgsPool(numConnections);
m_maxNumberAcceptedClients = new Semaphore(numConnections, numConnections);
} public void Init()
{
m_bufferManager.InitBuffer();
SocketAsyncEventArgs readWriteEventArg;
for (int i = 0; i < m_numConnections; i++)
{
readWriteEventArg = new SocketAsyncEventArgs();
readWriteEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);
readWriteEventArg.UserToken = new AsyncUserToken();
m_bufferManager.SetBuffer(readWriteEventArg);
m_readWritePool.Push(readWriteEventArg);
}
} /// <summary>
/// 开始监听
/// </summary>
public void Start(IPEndPoint localEndPoint)
{
listenSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
listenSocket.Bind(localEndPoint);
listenSocket.Listen(100); //开始监听客户端连接
StartAccept(null); Console.WriteLine("按任意键终止服务器...");
Console.ReadKey();
} /// <summary>
/// 处理来自客户机的连接请求
/// </summary>
/// <param name="acceeptEventArg"></param>
public void StartAccept(SocketAsyncEventArgs acceptEventArg)
{
if (acceptEventArg == null)
{
acceptEventArg = new SocketAsyncEventArgs();
acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(AcceptEventArg_Completed);
}
else
{
acceptEventArg.AcceptSocket = null;
} m_maxNumberAcceptedClients.WaitOne();
//异步接收客户端连接,操作完成后触发AcceptEventArg_Completed事件
bool willRaiseEvent = listenSocket.AcceptAsync(acceptEventArg);
//如果是同步完成
if (!willRaiseEvent)
{
ProcessAccept(acceptEventArg);
}
} /// <summary>
/// 接收到数据后触发
/// </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");
}
} /// <summary>
/// 侦听到客户端连接时触发
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void AcceptEventArg_Completed(object sender, SocketAsyncEventArgs e)
{
ProcessAccept(e);
} /// <summary>
///
/// </summary>
/// <param name="e"></param>
private void ProcessAccept(SocketAsyncEventArgs e)
{
Interlocked.Increment(ref m_numConnectedSockets);
Console.WriteLine("已接受客户端连接. There are {0} clients connected to the server",
m_numConnectedSockets); SocketAsyncEventArgs readEventArgs = m_readWritePool.Pop(); ((AsyncUserToken)readEventArgs.UserToken).Socket = e.AcceptSocket; //异步接受数据,接受完成则触发AcceptEventArg_Completed事件
bool willRaiseEvent = e.AcceptSocket.ReceiveAsync(readEventArgs); if (!willRaiseEvent)
{
ProcessReceive(readEventArgs);
}
//循环接受下一个客户端连接
StartAccept(e);
} private void ProcessReceive(SocketAsyncEventArgs e)
{
AsyncUserToken token = e.UserToken as AsyncUserToken;
if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
{
Interlocked.Add(ref m_totalBytesRead, e.BytesTransferred);
Console.WriteLine("服务器已经接受 {0} 字节数据", m_totalBytesRead); string received = Encoding.Unicode.GetString(e.Buffer, e.Offset, e.BytesTransferred); for (int i = 73728; i < e.Buffer.Length; i++)
{
byte o = e.Buffer[i];
} Console.WriteLine(received); //异步发送是将Buffer空间的所有数据拷贝到基础系统发送缓冲区
e.SetBuffer(e.Offset, e.BytesTransferred);
string str1 = Encoding.Unicode.GetString(e.Buffer);
//发送数据到客户端
bool willRaiseEvent = token.Socket.SendAsync(e);
if (!willRaiseEvent)
{
ProcessSend(e);
}
}
else
{
CloseClientSocket(e);
}
} 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);
}
} private void CloseClientSocket(SocketAsyncEventArgs e)
{
AsyncUserToken token = e.UserToken as AsyncUserToken; try
{
token.Socket.Shutdown(SocketShutdown.Send);
}
catch (Exception) { }
token.Socket.Close(); 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); m_readWritePool.Push(e);
} }
}
using System.Net.Sockets; namespace ConsoleTest
{
class AsyncUserToken
{
Socket m_socket; public AsyncUserToken() : this(null) { } public AsyncUserToken(Socket socket)
{
m_socket = socket;
} public Socket Socket
{
get { return m_socket; }
set { m_socket = value; }
} }
}
using System.Collections.Generic;
using System.Net.Sockets; namespace ConsoleTest
{
/// <summary>
/// 创建一个大型缓冲区,缓冲区可以进行分割并指定给 SocketAsyncEventArgs 对象以便用在每个套接字 I/O 操作中
/// </summary>
public class BufferManager
{
/// <summary>
/// 缓冲区大小
/// </summary>
int m_numBytes; /// <summary>
/// BufferManager维护的底层字节数组缓冲区
/// </summary>
byte[] m_buffer; /// <summary>
/// 记录偏移量
/// </summary>
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()
{
m_buffer = new byte[m_numBytes];
} /// <summary>
/// 设置要用于异步套接字方法的数据缓冲区。
/// </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);
m_currentIndex += m_bufferSize;
}
return true;
} /// <summary>
/// 释放缓冲区
/// </summary>
/// <param name="args"></param>
public void FreeBuffer(SocketAsyncEventArgs args)
{
m_freeIndexPool.Push(args.Offset);
args.SetBuffer(null, 0, 0);
}
}
}
using System;
using System.Collections.Generic;
using System.Net.Sockets; namespace ConsoleTest
{
/// <summary>
/// 代表一个可重用的SocketAsyncEventArgs对象的集合。
/// </summary>
public class SocketAsyncEventArgsPool
{
Stack<SocketAsyncEventArgs> m_pool; public SocketAsyncEventArgsPool(int capacity)
{
m_pool = new Stack<SocketAsyncEventArgs>(capacity);
} /// <summary>
/// 添加SocketAsyncEventArg实例到池
/// </summary>
/// <param name="item"></param>
public void Push(SocketAsyncEventArgs item)
{
if (item == null) throw new ArgumentNullException("参数不能为空!"); lock (m_pool)
{
m_pool.Push(item);
}
} /// <summary>
/// 从池中删除SocketAsyncEventArgs实例
/// </summary>
/// <returns></returns>
public SocketAsyncEventArgs Pop()
{
lock (m_pool)
{
return m_pool.Pop();
}
} /// <summary>
/// 缓冲池中的实例个数
/// </summary>
public int Count
{
get { return m_pool.Count; }
}
}
}
二、客户端调用:
using System.Collections.Generic;
using System.Net.Sockets; namespace ConsoleTest
{
/// <summary>
/// 创建一个大型缓冲区,缓冲区可以进行分割并指定给 SocketAsyncEventArgs 对象以便用在每个套接字 I/O 操作中
/// </summary>
public class BufferManager
{
/// <summary>
/// 缓冲区大小
/// </summary>
int m_numBytes; /// <summary>
/// BufferManager维护的底层字节数组缓冲区
/// </summary>
byte[] m_buffer; /// <summary>
/// 记录偏移量
/// </summary>
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()
{
m_buffer = new byte[m_numBytes];
} /// <summary>
/// 设置要用于异步套接字方法的数据缓冲区。
/// </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);
m_currentIndex += m_bufferSize;
}
return true;
} /// <summary>
/// 释放缓冲区
/// </summary>
/// <param name="args"></param>
public void FreeBuffer(SocketAsyncEventArgs args)
{
m_freeIndexPool.Push(args.Offset);
args.SetBuffer(null, 0, 0);
}
}
}
Socket 异步的更多相关文章
- socket异步编程--libevent的使用
使用 libevent 和 libev 提高网络应用性能 http://www.ibm.com/developerworks/cn/aix/library/au-libev/ libevent实现ht ...
- Socket异步发送的同步控制
在网络通信中,我们使用Socket异步发送数据,但在客户端,往往是需要等待服务器的返回结果后(握手过程)再往下执行,这就涉及到同步控制了,在多次的实现中,使用AutoResetEvent,实现不,即有 ...
- [转] socket异步编程--libevent的使用
这篇文章介绍下libevent在socket异步编程中的应用.在一些对性能要求较高的网络应用程序中,为了防止程序阻塞在socket I/O操作上造成程序性能的下降,需要使用异步编程,即程序准备好读写的 ...
- .net平台下socket异步通讯(代码实例)
你应该知道的.net平台下socket异步通讯(代码实例) 1,首先添加两个windows窗体项目,一个作为服务端server,一个作为客户端Client 2,然后添加服务端代码,添加命名空间,界面上 ...
- 基于.net的Socket异步编程总结
最近在为公司的分布式服务框架做支持异步调用的开发,这种新特性的上线需要进行各种严格的测试.在并发性能测试时,性能一直非常差,而且非常的不稳定.经过不断的分析调优,发现Socket通信和多线程异步回调存 ...
- 你应该知道的.net平台下socket异步通讯(代码实例)
1,首先添加两个windows窗体项目,一个作为服务端server,一个作为客户端Client 2,然后添加服务端代码,添加命名空间,界面上添加TextBox控件 using System.Net; ...
- C# Socket异步聊天例子
最近在配合游戏服务器端搞一个客户端通信,客户端是unity搞的,理所当然就高C#了,上手之前先看了一下C# Socket通信这一块,基本不考虑同步方式,而异步方式,微软也提供了两套API,一套是Beg ...
- Socket异步通讯
1.可以通过多线程来解决(一会补上) 2.Socket在tcp/udp两种通信协议下的异步通信: 基于TCP的异步通信: BeginAccept方法和endeaccept方法 包含在System.Ne ...
- Socket异步存储示例
异步客户端存储示例: using System; using System.Net; using System.Net.Sockets; using System.Threading; using S ...
- socket 异步选择 WSAAsyncSelect 用法
WSAAsyncSelect 实现给异步socket给了另一种实现方式,就是通过窗口消息的方式来提醒对socket接收还是发送 msdn有非常全面的解释:https://msdn.microsoft. ...
随机推荐
- 阿里云对象存储OSS使用 HTTPS
一.前言 阿里云对象存储oss本身也是可以用HTTPS直接访问的,但是它本身的地址是http://***.oss-cn-hangzhou.aliyuncs.com这样的,那么如果我们想使用自己的域名, ...
- Ngingx--location匹配顺序
location = / 精确匹配 /,后面不能带任何字符 location / 所有地址都是以 / 开头,所以这条规则将会匹配到所有请求.但优先级最低. location /docum ...
- APP的数据采集与埋点方式分析
前言: 神策数据写过几篇分析APP前后端埋点的文章,原文在此: https://sensorsdata.cn/blog/shu-ju-jie-ru-yu-mai-dian/ http://www.wo ...
- libcurl在mingw下编译
通过命令提示符进入 curl-7.27.0 文件夹输入 mingw32-make mingw32 进行生成(这里我只需要普通的功能,于是没有加附加的选项)编译完成后,在 lib 文件夹中会有我们需要的 ...
- CSS选择器-常用搜集
标签选择器: div{ font-size=10px; color=red; background-color=yello; width=200px; height=200px; } <div& ...
- 【机器学习】K-邻近算法的python 实现
#!/usr/bin/python # -*- coding: utf-8 -*- from numpy import * import operator def createDataSet(): ' ...
- 【BZOJ】4033: [HAOI2015]树上染色 树上背包
[题目]#2124. 「HAOI2015」树上染色 [题意]给定n个点的带边权树,要求将k个点染成黑色,使得 [ 黑点的两两距离和+白点的两两距离和 ] 最大.n<=2000. [算法]树上背包 ...
- 【BZOJ】1415 [Noi2005]聪聪和可可 期望DP+记忆化搜索
[题意]给定无向图,聪聪和可可各自位于一点,可可每单位时间随机向周围走一步或停留,聪聪每单位时间追两步(先走),问追到可可的期望时间.n<=1000. [算法]期望DP+记忆化搜索 [题解]首先 ...
- Linux必备工具Tmux
之前介绍了Linux的Screen命令,今天介绍一个更为强大的终端工具Tmux. Tmux 是一个用于在一个终端窗口中运行多个终端会话的工具.它基本能替代nohup以及screen,甚至比它们更为强大 ...
- uboot1.1.6 start.s分析
.Stage1 start.S代码结构 u-boot的stage1代码通常放在start.S文件中,他用汇编语言写成,其主要代码部分如下:(1)定义入口.由于一个可执行的Image必须有一个入口点,并 ...