写自己的socket框架(二)
1、开始正常监听以后,就要开始接受数据了,整体流程图如下:
2、上一节看到我们在程序初始化的时候,初始化了很多个SocketConnection,用于管理客户端的链接,那应用层如何来操作,又什么时候来接受数据?于是我们便有了SocketSession,用于给应用层来管理整个会话过程,代码如下:
public class SocketSession : IDisposable
{
public string SessionId { get; private set; } private System.Net.Sockets.Socket _connectSocket; private IProtocol _protocol; private SocketConnection _connect;
public SocketConnection Connection { get { return _connect; } } private MemoryStream _memStream; private delegate void ReceiveDataHandler(SocketAsyncEventArgs e); private ReceiveDataHandler ReceiveHandler;
private delegate void ReceiveReadPackageHandler(byte[] b, int offset, SocketAsyncEventArgs e);
private ReceiveReadPackageHandler ReadPackageHandler; public System.Net.Sockets.Socket ConnectSocket
{
get
{
return _connectSocket;
}
private set { }
} public SocketSession(string sessionId)
{
this.SessionId = sessionId;
} public SocketSession(System.Net.Sockets.Socket client, SocketConnection connect)
: this(Guid.NewGuid().ToString())
{
this._connectSocket = client;
this._connect = connect;
this._protocol = connect.Pool.AppServer.AppProtocol;
_memStream = new MemoryStream(); ReceiveHandler = ReceiveData;
ReadPackageHandler = this.ReadPackage;
} internal void ReceiveData(SocketAsyncEventArgs e)
{
if (e.SocketError != SocketError.Success)
{
this.Close();
return;
} if (e.BytesTransferred <= )
{
this.Close();
return;
} try
{
if (this.Connection.Flag == SocketFlag.Busy)
{
byte[] buffer = new byte[e.BytesTransferred];
Array.Copy(e.Buffer, , buffer, , e.BytesTransferred);
ReadPackage(buffer, , e);
buffer = null;
}
}
catch (Exception ex)
{
this.Close();
return;
}
} internal void ReceiveAsync(SocketAsyncEventArgs e)
{
if (e == null)
{
return;
}
bool isCompleted = true;
try
{
isCompleted = this._connectSocket.ReceiveAsync(e);
}
catch (Exception ex)
{
LogHelper.Debug(this.SessionId + ex.ToString());
this.Close();
}
if (!isCompleted)
{
this.ReceiveHandler.BeginInvoke(e, ReceiveHandlerCallBack, ReceiveHandler);
}
} void ReceiveHandlerCallBack(IAsyncResult result)
{
try
{
(result.AsyncState as ReceiveDataHandler).EndInvoke(result);
}
catch (Exception e)
{
LogHelper.Debug(e.Message);
}
} internal void OnDataRecevied(SessionEventArgs arg)
{
if (DataRecevied != null)
{
this._memStream.SetLength();
DataRecevied.Invoke(this, arg);
}
} internal void Close()
{
try
{
this._connectSocket.Close();
}
catch (Exception ex)
{
LogHelper.Debug("关闭socket异常" + ex.ToString());
} if (this.Closed != null)
{
this.Closed();
}
} internal Action Closed;
internal Action<SocketSession, SessionEventArgs> DataRecevied; public void Dispose()
{
if (_memStream != null)
{
_memStream.Close();
_memStream.Dispose();
_memStream = null;
}
} public void Send(byte[] data)
{
try
{
if (this.Connection.Flag == SocketFlag.Busy)
{
this._connectSocket.Send(data);
}
}
catch (Exception ex)
{
this.Close();
}
} private void ReadPackage(byte[] data, int offset, SocketAsyncEventArgs e)
{
if (data == null || data.Length == )
{
return;
}
if (offset >= data.Length)
{
return;
}
if (offset == )
{
if (_memStream.Length > )
{
_memStream.Write(data, , data.Length);
data = _memStream.ToArray();
}
}
//粘包处理
OnReceivedCallBack(data, offset, e); data = null;
} private void OnReceivedCallBack(byte[] buffer, int offset, SocketAsyncEventArgs e)
{
byte[] data = this._protocol.OnDataReceivedCallBack(buffer, ref offset); if (offset == -)
{
this.Close();
return;
}
if (data == null || data.Length == )
{
this._memStream.Write(buffer, offset, buffer.Length - offset);
this.ReceiveAsync(e);
return;
}
SessionEventArgs session_args = new SessionEventArgs();
session_args.Data = data;
this.OnDataRecevied(session_args);
if (offset < buffer.Length)
{
this.ReadPackageHandler.BeginInvoke(buffer, offset, e, ReadPackageCallBack, ReadPackageHandler);
}
else
{
this.ReceiveAsync(e);
} data = null;
} void ReadPackageCallBack(IAsyncResult result)
{
try
{
(result.AsyncState as ReceiveReadPackageHandler).EndInvoke(result);
}
catch (Exception ex)
{
LogHelper.Debug(ex.Message);
}
}
}
细心的童鞋可以发现,在ReceiveAsync方法里面,接收数据的地方,当同步接收完成的时候,我们调用了一个异步委托ReceiveHandler.BeginInvoke。
在解析出一个独立的包,并且缓冲区的数据里面还有多余的包的时候,我们也调用了一个异步的委托ReadPackageHandler.BeginInvoke。
如果缓冲区比较大,比如我现在是8K,而单个包很小,客户端又发送比较频繁的时候。会导致在解析包的时候,形成一个短暂的递归。递归就会不停的压堆,资源得不到释放。
运行一段时间后,有可能导致OutOfMemoryException,如果一直是同步接收数据,在Receive的地方,也有可能形成一个递归。于是便采用了异步调用的方式。
3、因为socket属于无边界的,代码层面的每一次Send,并不是真正意义上的直接发送给服务器,而只是写到了缓冲区,由系统来决定什么时候发。如果客户 端发送非常频繁的情况下,就可能导致服务器从缓冲区取出来的包,是由多个包一起组成的。从缓冲区取出来的包,并不能保证是一个独立的应用层的包,需要按既定的协议来解析包。
我们先假定一个简单的协议,一个包的前4个字节,表明这个包内容的长度。代码如下:
public class DefaultProtocol : IProtocol
{
public byte[] OnDataReceivedCallBack(byte[] data, ref int offset)
{
int length = BitConverter.ToInt32(data, offset);
int package_head = ;
int package_length = length + package_head;
byte[] buffer = null;
if (length > )
{
if (offset + package_length <= data.Length)
{
buffer = new byte[length];
Array.Copy(data, offset + package_head, buffer, , length);
offset += package_length;
}
}
else
{
offset = -;
}
return buffer;
}
}
如果协议无法正常解析,则offset=-1,并关闭掉该链接。如果在解析完一个包以后,还有剩余的包, 于是在抛给应用层以后,便继续解析。如果单个包比较大,缓冲区一次放不下的时候,我们将数据暂时写入到内存流里面,然后将下一次接收到的数据,一并拿出来解析。
4、接收数据已经准备完毕以后,就需要将SocketConnection和SocketSession关联起来,代码如下:
public class AppServer : IAppServer
{
public delegate void DataRecevieHandler(SocketSession o, SessionEventArgs e);
public delegate void NewConnectionHandler(SocketSession o, EventArgs e);
public delegate void OnErrorHandler(Exception e); public event DataRecevieHandler DataRecevied;
public event NewConnectionHandler NewConnected;
public event OnErrorHandler OnError; private ISocketListener _listener;
private SocketConnectionPool _connectPool; public AppServer(ServerConfig serverConfig)
{
this.AppConfig = serverConfig;
if (this.AppProtocol == null)
{
this.AppProtocol = new DefaultProtocol();
} _connectPool = new SocketConnectionPool(this);
_connectPool.Connected = OnConnected; _listener = new SocketListener(this.AppConfig);
_listener.NewClientAccepted += new NewClientAcceptHandler(listener_NewClientAccepted);
_listener.Error += new ErrorHandler(_listener_Error);
} void OnDataRecevied(SocketSession session, SessionEventArgs e)
{
if (this.DataRecevied != null)
{
DataRecevied.BeginInvoke(session, e, DataReceviedCallBack, DataRecevied);
}
} public bool Start()
{
_connectPool.Init(); return _listener.Start();
} public void Stop()
{
_listener.Stop();
} void _listener_Error(ISocketListener listener, Exception e)
{
if (this.OnError != null)
{
this.OnError.Invoke(e);
}
} void listener_NewClientAccepted(ISocketListener listener, System.Net.Sockets.Socket client, object state)
{
_connectPool.Push(client);
} public void OnConnected(System.Net.Sockets.Socket client, SocketConnection connect)
{
var session = new SocketSession(client, connect);
session.DataRecevied = OnDataRecevied;
connect.Initialise(session);
if (NewConnected != null)
{
NewConnected.BeginInvoke(session, EventArgs.Empty, NewConnectedCallBack, NewConnected);
}
if (connect.RecevieEventArgs != null)
{
session.ReceiveAsync(connect.RecevieEventArgs);
}
} void DataReceviedCallBack(IAsyncResult result)
{
try
{
(result.AsyncState as DataRecevieHandler).EndInvoke(result);
}
catch (Exception e)
{
LogHelper.Debug(e.Message);
}
} void NewConnectedCallBack(IAsyncResult result)
{
try
{
(result.AsyncState as NewConnectionHandler).EndInvoke(result);
}
catch (Exception e)
{
LogHelper.Debug(e.Message);
}
} public ServerConfig AppConfig
{
get;
set;
} public IProtocol AppProtocol
{
get;
set;
}
}
到这里,整个接收包的流程就结束了,但是发送的地方,我们发现是同步在发送,如果有特别需要的可以考虑写成异步方式,但我个人更倾向于,这一块留给应用层处理,在应用层写一个发送队列,然后有独立的线程来管理这个发送队列。
写自己的socket框架(二)的更多相关文章
- linux可用的跨平台C# .net standard2.0 写的高性能socket框架
能在window(IOCP)/linux(epoll)运行,基于C# .net standard2.0 写的socket框架,可使用于.net Framework/dotnet core程序集,.使用 ...
- 写自己的Socket框架(一)
本系列仅介绍可用于生产环境的C#异步Socket框架,如果您在其他地方看到类似的代码,不要惊讶,那可能就是我在参考开源代码时,直接“剽窃”过来的. 1.在脑海里思考一下整个socket的链接的处理流程 ...
- 写自己的Socket框架(三)
在通信写完了以后,应用层接收到Socket抛上来的byte[],这个时候对于实际的写逻辑的开发者来说,这样的数据并不友好,我们就需要在应用层统一一个包的规则(应用层协议),处理完以后,然后再传给实际的 ...
- 看过《大湿教我写.net通用权限框架(1)之菜单导航篇》之后发生的事(续)——主界面
引言 在UML系列学习中的小插曲:看过<大湿教我写.net通用权限框架(1)之菜单导航篇>之后发生的事 在上篇中只拿登录界面练练手,不把主界面抠出来,实在难受,严重的强迫症啊.之前一直在总 ...
- ZYSocket 4.3.5 SOCKET框架组 发布[NEW]
最新代码请到 github: https://github.com/luyikk/ZYSOCKET 更新 4.3.5更新说明: 修复各种BUG. 重写了一份 protobuf-net 有什么用呢,不需 ...
- 搭建Extjs框架(二)
搭建Extjs 框架 二.编写入口文件 app.js,配置extjs 组件\视图文件路径 并将app.js引入index.html 在app.js中指定一些文件的路径,Extjs页面的起始 ...
- 从零开始学习 asp.net core 2.1 web api 后端api基础框架(二)-创建项目
原文:从零开始学习 asp.net core 2.1 web api 后端api基础框架(二)-创建项目 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.ne ...
- [LINQ2Dapper]最完整Dapper To Linq框架(二)---动态化查询
目录 [LINQ2Dapper]最完整Dapper To Linq框架(一)---基础查询 [LINQ2Dapper]最完整Dapper To Linq框架(二)---动态化查询 [LINQ2Dapp ...
- Workerman:PHP的socket框架
hi,我们今天来讲讲Workerman,什么是Workerman呢? 看看官网上的介绍 Workerman是一款开源高性能异步PHP socket框架.支持高并发,超高稳定性,被广泛的用于手机app. ...
随机推荐
- In-Memory:在内存中创建临时表和表变量
在Disk-Base数据库中,由于临时表和表变量的数据存储在tempdb中,如果系统频繁地创建和更新临时表和表变量,大量的IO操作集中在tempdb中,tempdb很可能成为系统性能的瓶颈.在SQL ...
- 虾扯蛋:Android View动画 Animation不完全解析
本文结合一些周知的概念和源码片段,对View动画的工作原理进行挖掘和分析.以下不是对源码一丝不苟的分析过程,只是以搞清楚Animation的执行过程.如何被周期性调用为目标粗略分析下相关方法的执行细节 ...
- 神马玩意,EntityFramework Core 1.1又更新了?走,赶紧去围观
前言 哦,不搞SQL了么,当然会继续,周末会继续更新,估计写完还得几十篇,但是我会坚持把SQL更新完毕,绝不会烂尾,后续很长一段时间没更新的话,不要想我,那说明我是学习新的技能去了,那就是学习英语,本 ...
- Photoshop将普通照片快速制作二次元漫画风格效果
今天为大家分享Photoshop将普通照片快速制作二次元漫画风格效果,教程很不错,对于喜欢漫画的朋友可以参考本文,希望能对大家有所帮助! 一提到日本动画电影,大家第一印象肯定是宫崎骏,但是日本除了宫崎 ...
- 深入浅出Redis-redis哨兵集群
1.Sentinel 哨兵 Sentinel(哨兵)是Redis 的高可用性解决方案:由一个或多个Sentinel 实例 组成的Sentinel 系统可以监视任意多个主服务器,以及这些主服务器属下的所 ...
- .Net语言 APP开发平台——Smobiler学习日志:如何快速在手机上实现ContextMenu
最前面的话:Smobiler是一个在VS环境中使用.Net语言来开发APP的开发平台,也许比Xamarin更方便 样式一 一.目标样式 我们要实现上图中的效果,需要如下的操作: 1.从工具栏上的&qu ...
- Win10提示没有权限使用网络资源问题解决
借鉴链接:http://www.cr173.com/html/67361_1.html Win10提示没有权限使用网络资源解决方法 1.打开控制面板; 2.在所有控制面板项中找到凭据管理器; 3.添加 ...
- Android中Activity处理返回结果的实现方式
大家在网上购物时都有这样一个体验,在确认订单选择收货人以及地址时,会跳转页面到我们存入网站内的所有收货信息(包含收货地址,收货人)的界面供我们选择,一旦我们点击其中某一条信息,则会自动跳转到订单提交界 ...
- 微信小程序之用户数据解密(七)
[未经作者本人同意,请勿以任何形式转载] 经常看到有点的小伙伴在群里问小程序用户数据解密流程,所以打算写一篇关于小程序用户敏感数据解密教程: 加密过程微信服务器完成,解密过程在小程序和自身服务器完成, ...
- Spring Security OAuth2 开发指南
官方原文:http://projects.spring.io/spring-security-oauth/docs/oauth2.html 翻译及修改补充:Alex Liao. 转载请注明来源:htt ...