linux可用的跨平台C# .net standard2.0 写的高性能socket框架
能在window(IOCP)/linux(epoll)运行,基于C# .net standard2.0 写的socket框架,可使用于.net Framework/dotnet core程序集,.使用异步连接,异步发送,异步接收,性能爆表,并且通过压力测试。
源码下载地址:
https://download.csdn.net/download/guosa542129/11980602
通过并发测试,多线程测试程序地址:
https://download.csdn.net/download/guosa542129/11980605
操作过程:
安装NuGet: https://www.nuget.org/packages/socket.core/
Package Manager: Install-Package socket.core
.Net CLI :dotnet add package socket.core
Paket CLI:paket add socket.core
一:TCP模块介绍
服务端所在socket.core.Server命名空间下,分别为三种模式 push/pull/pack
客户端所在socket.core.Client命名空间下,分别为三种模式 push/pull/pack
主要流程与对应的方法和事件介绍.
注:connectId(int)代表着一个连接对象,data(byte[]),success(bool)
- 1.初始化TCP实现类(对应的三种模式)
实例化服务端类 TcpPushServer/TcpPullServer/TcpPackServer
实例化客户端类 TcpPushClient/TcpPullClient/TcpPackClient
参数介绍int numConnections同时处理的最大连接数,int receiveBufferSize用于每个套接字I/O操作的缓冲区大小(接收端), int overtime超时时长,单位秒.(每10秒检查一次),当值为0时,不设置超时,uint headerFlag包头标记范围0~1023(0x3FF),当包头标识等于0时,不校验包头 - 2.启动监听/连接服务器
服务端 server.Start(port);
客户端 client.Connect(ip,port); - 3.触发连接事件
服务端 server.OnAccept(connectId); 接收到一个连接id,可用他来发送,接收,关闭的标记
客户端 client.OnConnect(success); 接收是否成功连接到服务器 - 4.发送消息
服务端 server.Send(connectId,data,offset,length);
客户端 client.Send(data,offset,length); - 5.触发已发送事件
服务端 server.OnSend(connectId,length);
客户端 client.OnSend(length); - 6.触发接收事件
服务端 server.OnReceive(connectId, data);
客户端 client.OnReceive(data); - 7.关闭连接
服务端 server.Close(connectId);
客户端 client.Close(); - 8.触发关闭连接事件
服务端 server.OnClose(connectId);
客户端 client.OnClose();
三种模型简介
- 一:push
当接收到数据时会触发监听事件OnReceive(connectId,data);把数据立马“推”给应用程序
- 二:pull
当接收到数据时会触发监听事件OnReceive(connectId,length),告诉应用程序当前已经接收到了多少数据长度,应用程序可使用GetLength(connectId)方法检查已接收的数据的长度,如果满足则调用组件的Fetch(connectId,length)方法,把需要的数据“拉”出来
- 三:pack
pack模型组件是push和pull模型的结合体,应用程序不必要处理分包/合包,组件保证每个server.OnReceive(connectId,data)/client.OnReceive(data)事件都向应用程序提供一个完整的数据包
注:pack模型组件会对应用程序发送的每个数据包自动加上4个字节(32bit)的包头,组件接收到数据时,根据包头信息自动分包,每个完整的数据包通过OnReceive(connectId, data)事件发送给应用程序
PACK包头格式(4字节)4*8=32
XXXXXXXXXXYYYYYYYYYYYYYYYYYYYYYY
前10位X为包头标识位,用于数据包校验,有效包头标识取值范围0~1023(0x3FF),当包头标识等于0时,不校验包头,后22位Y为长度位,记录包体长度。有效数据包最大长度不能超过4194303(0x3FFFFF)字节(byte),应用程序可以通过TcpPackServer/TcpPackClient构造函数参数headerFlag设置
服务端其它方法介绍
- bool SetAttached(int connectId, object data)
服务端为每个客户端设置附加数据,避免用户自己再建立用户映射表
- T GetAttached(int connectId)
获取指定客户端的附加数据
- 属性:ConcurrentDictionary<int, string> ClientList
获取正在连接的客户端信息<connectId,ip和端口>
二:核心源码
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Linq;
using System.Collections.Concurrent;
using socket.core.Common; namespace socket.core.Server
{
/// <summary>
/// tcp Socket监听基库
/// </summary>
internal class TcpServer
{
/// <summary>
/// 连接标示 自增长
/// </summary>
private int connectId;
/// <summary>
/// 同时处理的最大连接数
/// </summary>
private int m_numConnections;
/// <summary>
/// 用于每个套接字I/O操作的缓冲区大小
/// </summary>
private int m_receiveBufferSize;
/// <summary>
/// 所有套接字接收操作的一个可重用的大型缓冲区集合。
/// </summary>
private BufferManager m_bufferManager;
/// <summary>
/// 用于监听传入连接请求的套接字
/// </summary>
private Socket listenSocket;
/// <summary>
/// 接受端SocketAsyncEventArgs对象重用池,接受套接字操作
/// </summary>
private SocketAsyncEventArgsPool m_receivePool;
/// <summary>
/// 发送端SocketAsyncEventArgs对象重用池,发送套接字操作
/// </summary>
private SocketAsyncEventArgsPool m_sendPool;
/// <summary>
/// 超时,如果超时,服务端断开连接,客户端需要重连操作
/// </summary>
private int overtime;
/// <summary>
/// 超时检查间隔时间(秒)
/// </summary>
private int overtimecheck = ;
/// <summary>
/// 能接到最多客户端个数的原子操作
/// </summary>
private Semaphore m_maxNumberAcceptedClients;
/// <summary>
/// 已经连接的对象池
/// </summary>
internal ConcurrentDictionary<int, ConnectClient> connectClient;
/// <summary>
/// 发送线程数
/// </summary>
private int sendthread = ;
/// <summary>
/// 需要发送的数据
/// </summary>
private ConcurrentQueue<SendingQueue>[] sendQueues;
/// <summary>
/// 锁
/// </summary>
private Mutex mutex = new Mutex();
/// <summary>
/// 连接成功事件
/// </summary>
internal event Action<int> OnAccept;
/// <summary>
/// 接收通知事件
/// </summary>
internal event Action<int, byte[], int, int> OnReceive;
/// <summary>
/// 已送通知事件
/// </summary>
internal event Action<int, int> OnSend;
/// <summary>
/// 断开连接通知事件
/// </summary>
internal event Action<int> OnClose; /// <summary>
/// 设置基本配置
/// </summary>
/// <param name="numConnections">同时处理的最大连接数</param>
/// <param name="receiveBufferSize">用于每个套接字I/O操作的缓冲区大小(接收端)</param>
/// <param name="overTime">超时时长,单位秒.(每10秒检查一次),当值为0时,不设置超时</param>
internal TcpServer(int numConnections, int receiveBufferSize, int overTime)
{
overtime = overTime;
m_numConnections = numConnections;
m_receiveBufferSize = receiveBufferSize;
m_bufferManager = new BufferManager(receiveBufferSize * m_numConnections, receiveBufferSize);
m_receivePool = new SocketAsyncEventArgsPool(m_numConnections);
m_sendPool = new SocketAsyncEventArgsPool(m_numConnections);
m_maxNumberAcceptedClients = new Semaphore(m_numConnections, m_numConnections);
Init();
} /// <summary>
/// 初始化服务器通过预先分配的可重复使用的缓冲区和上下文对象。这些对象不需要预先分配或重用,但这样做是为了说明API如何可以易于用于创建可重用对象以提高服务器性能。
/// </summary>
private void Init()
{
connectClient = new ConcurrentDictionary<int, ConnectClient>();
sendQueues = new ConcurrentQueue<SendingQueue>[sendthread];
for (int i = ; i < sendthread; i++)
{
sendQueues[i] = new ConcurrentQueue<SendingQueue>();
}
//分配一个大字节缓冲区,所有I/O操作都使用一个。这个侍卫对内存碎片
m_bufferManager.InitBuffer();
//预分配的接受对象池socketasynceventargs,并分配缓存
SocketAsyncEventArgs saea_receive;
//分配的发送对象池socketasynceventargs,但是不分配缓存
SocketAsyncEventArgs saea_send;
for (int i = ; i < m_numConnections; i++)
{
//预先接受端分配一组可重用的消息
saea_receive = new SocketAsyncEventArgs();
saea_receive.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);
//分配缓冲池中的字节缓冲区的socketasynceventarg对象
m_bufferManager.SetBuffer(saea_receive);
m_receivePool.Push(saea_receive);
//预先发送端分配一组可重用的消息
saea_send = new SocketAsyncEventArgs();
saea_send.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);
m_sendPool.Push(saea_send);
}
} /// <summary>
/// 启动tcp服务侦听
/// </summary>
/// <param name="port">监听端口</param>
internal void Start(int port)
{
IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, port);
//创建listens是传入的套接字。
listenSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
listenSocket.NoDelay = true;
//绑定端口
listenSocket.Bind(localEndPoint);
//挂起的连接队列的最大长度。
listenSocket.Listen();
//在监听套接字上接受
StartAccept(null);
//发送线程
for (int i = ; i < sendthread; i++)
{
Thread thread = new Thread(StartSend);
thread.IsBackground = true;
thread.Priority = ThreadPriority.AboveNormal;
thread.Start(i);
}
//超时机制
if (overtime > )
{
Thread heartbeat = new Thread(new ThreadStart(() =>
{
Heartbeat();
}));
heartbeat.IsBackground = true;
heartbeat.Priority = ThreadPriority.Lowest;
heartbeat.Start();
}
} /// <summary>
/// 超时机制
/// </summary>
private void Heartbeat()
{
//计算超时次数 ,超过count就当客户端断开连接。服务端清除该连接资源
int count = overtime / overtimecheck;
while (true)
{
foreach (var item in connectClient.Values)
{
if (item.keep_alive >= count)
{
item.keep_alive = ;
CloseClientSocket(item.saea_receive);
}
}
foreach (var item in connectClient.Values)
{
item.keep_alive++;
}
Thread.Sleep(overtimecheck * );
}
} #region Accept /// <summary>
/// 开始接受客户端的连接请求的操作。
/// </summary>
/// <param name="acceptEventArg">发布时要使用的上下文对象服务器侦听套接字上的接受操作</param>
private void StartAccept(SocketAsyncEventArgs acceptEventArg)
{
if (acceptEventArg == null)
{
acceptEventArg = new SocketAsyncEventArgs();
acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);
}
else
{
// 套接字必须被清除,因为上下文对象正在被重用。
acceptEventArg.AcceptSocket = null;
}
m_maxNumberAcceptedClients.WaitOne();
//准备一个客户端接入
if (!listenSocket.AcceptAsync(acceptEventArg))
{
ProcessAccept(acceptEventArg);
}
} /// <summary>
/// 当异步连接完成时调用此方法
/// </summary>
/// <param name="e">操作对象</param>
private void ProcessAccept(SocketAsyncEventArgs e)
{
connectId++;
//把连接到的客户端信息添加到集合中
ConnectClient connecttoken = new ConnectClient();
connecttoken.socket = e.AcceptSocket;
//从接受端重用池获取一个新的SocketAsyncEventArgs对象
connecttoken.saea_receive = m_receivePool.Pop();
connecttoken.saea_receive.UserToken = connectId;
connecttoken.saea_receive.AcceptSocket = e.AcceptSocket;
connectClient.TryAdd(connectId, connecttoken);
//一旦客户机连接,就准备接收。
if (!e.AcceptSocket.ReceiveAsync(connecttoken.saea_receive))
{
ProcessReceive(connecttoken.saea_receive);
}
//事件回调
if (OnAccept != null)
{
OnAccept(connectId);
}
//接受第二连接的请求
StartAccept(e);
} #endregion #region 接受处理 receive /// <summary>
/// 接受处理回调
/// </summary>
/// <param name="e">操作对象</param>
private void ProcessReceive(SocketAsyncEventArgs e)
{
//检查远程主机是否关闭连接
if (e.BytesTransferred > && e.SocketError == SocketError.Success)
{
int connectId = (int)e.UserToken;
ConnectClient client;
if (!connectClient.TryGetValue(connectId, out client))
{
return;
}
//如果接收到数据,超时记录设置为0
if (overtime > )
{
if (client != null)
{
client.keep_alive = ;
}
}
//回调
if (OnReceive != null)
{
if (client != null)
{
OnReceive(connectId, e.Buffer, e.Offset, e.BytesTransferred);
}
}
//准备下次接收数据
try
{
if (!e.AcceptSocket.ReceiveAsync(e))
{
ProcessReceive(e);
}
}
catch (ObjectDisposedException ex)
{
if (OnClose != null)
{
OnClose(connectId);
}
}
}
else
{
CloseClientSocket(e);
}
} #endregion #region 发送处理 send /// <summary>
/// 开始启用发送
/// </summary>
private void StartSend(object thread)
{
while (true)
{
SendingQueue sending;
if (sendQueues[(int)thread].TryDequeue(out sending))
{
Send(sending);
}
else
{
Thread.Sleep();
}
}
} /// <summary>
/// 异步发送消息
/// </summary>
/// <param name="connectId">连接ID</param>
/// <param name="data">数据</param>
/// <param name="offset">偏移位</param>
/// <param name="length">长度</param>
internal void Send(int connectId, byte[] data, int offset, int length)
{
sendQueues[connectId % sendthread].Enqueue(new SendingQueue() { connectId = connectId, data = data, offset = offset, length = length });
} /// <summary>
/// 异步发送消息
/// </summary>
/// <param name="sendQuere">发送消息体</param>
private void Send(SendingQueue sendQuere)
{
ConnectClient client;
if (!connectClient.TryGetValue(sendQuere.connectId, out client))
{
return;
}
//如果发送池为空时,临时新建一个放入池中
mutex.WaitOne();
if (m_sendPool.Count == )
{
SocketAsyncEventArgs saea_send = new SocketAsyncEventArgs();
saea_send.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);
m_sendPool.Push(saea_send);
}
SocketAsyncEventArgs sendEventArgs = m_sendPool.Pop();
mutex.ReleaseMutex();
sendEventArgs.UserToken = sendQuere.connectId;
sendEventArgs.SetBuffer(sendQuere.data, sendQuere.offset, sendQuere.length);
try
{
if (!client.socket.SendAsync(sendEventArgs))
{
ProcessSend(sendEventArgs);
}
}
catch (ObjectDisposedException ex)
{
if (OnClose != null)
{
OnClose(sendQuere.connectId);
}
}
sendQuere = null;
} /// <summary>
/// 发送回调
/// </summary>
/// <param name="e">操作对象</param>
private void ProcessSend(SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
m_sendPool.Push(e);
if (OnSend != null)
{
OnSend((int)e.UserToken, e.BytesTransferred);
}
}
else
{
CloseClientSocket(e);
}
} #endregion /// <summary>
/// 每当套接字上完成接收或发送操作时,都会调用此方法。
/// </summary>
/// <param name="sender"></param>
/// <param name="e">与完成的接收操作关联的SocketAsyncEventArg</param>
private void IO_Completed(object sender, SocketAsyncEventArgs e)
{
//确定刚刚完成哪种类型的操作并调用相关的处理程序
switch (e.LastOperation)
{
case SocketAsyncOperation.Receive:
ProcessReceive(e);
break;
case SocketAsyncOperation.Send:
ProcessSend(e);
break;
case SocketAsyncOperation.Accept:
ProcessAccept(e);
break;
default:
break;
}
} #region 断开连接处理 /// <summary>
/// 客户端断开一个连接
/// </summary>
/// <param name="connectId">连接标记</param>
internal void Close(int connectId)
{
ConnectClient client;
if (!connectClient.TryGetValue(connectId, out client))
{
return;
}
CloseClientSocket(client.saea_receive);
} /// <summary>
/// 断开一个连接
/// </summary>
/// <param name="e">操作对象</param>
private void CloseClientSocket(SocketAsyncEventArgs e)
{
if (e.LastOperation == SocketAsyncOperation.Receive)
{
int connectId = (int)e.UserToken;
ConnectClient client;
if (!connectClient.TryGetValue(connectId, out client))
{
return;
}
if (client.socket.Connected == false)
{
return;
}
try
{
client.socket.Shutdown(SocketShutdown.Both);
}
// 抛出客户端进程已经关闭
catch (Exception) { }
client.socket.Close();
m_receivePool.Push(e);
m_maxNumberAcceptedClients.Release();
if (OnClose != null)
{
OnClose(connectId);
}
connectClient.TryRemove((int)e.UserToken, out client);
client = null;
}
} #endregion #region 附加数据 /// <summary>
/// 给连接对象设置附加数据
/// </summary>
/// <param name="connectId">连接标识</param>
/// <param name="data">附加数据</param>
/// <returns>true:设置成功,false:设置失败</returns>
internal bool SetAttached(int connectId, object data)
{
ConnectClient client;
if (!connectClient.TryGetValue(connectId, out client))
{
return false;
}
client.attached = data;
return true;
} /// <summary>
/// 获取连接对象的附加数据
/// </summary>
/// <param name="connectId">连接标识</param>
/// <returns>附加数据,如果没有找到则返回null</returns>
internal T GetAttached<T>(int connectId)
{
ConnectClient client;
if (!connectClient.TryGetValue(connectId, out client))
{
return default(T);
}
else
{
return (T)client.attached;
}
}
#endregion
} } socket核心类1.初始化UDP实现类UdpServer/UdpClients
服务端socket.core.Server.UdpServer
客户端socket.core.Client.UdpClients
参数int receiveBufferSize用于每个套接字I/O操作的缓冲区大小(接收端)2.发送数据
服务端 server.Send(remoteEndPoint,data,offset,length)
客户端 client.Send(data,offset,length)
客户端 client.Send(remoteEndPoint,data,offset,length)3.触发已发送事件
服务端 server.OnSend(remoteEndPoint,length)
客户端 client.OnSend(length)3.触发接收事件
服务端 server.OnReceive(remoteEndPoint,data,offset,length)
客户端 client.OnReceive(data,offset,length)
linux可用的跨平台C# .net standard2.0 写的高性能socket框架的更多相关文章
- Linux平台 Oracle 10gR2(10.2.0.5)RAC安装 Part3:db安装和升级
Linux平台 Oracle 10gR2(10.2.0.5)RAC安装 Part3:db安装和升级 环境:OEL 5.7 + Oracle 10.2.0.5 RAC 5.安装Database软件 5. ...
- Linux平台 Oracle 10gR2(10.2.0.5)RAC安装 Part1:准备工作
Linux平台 Oracle 10gR2(10.2.0.5)RAC安装 Part1:准备工作 环境:OEL 5.7 + Oracle 10.2.0.5 RAC 1.实施前准备工作 1.1 服务器安装操 ...
- Linux平台 Oracle 10gR2(10.2.0.5)RAC安装 Part2:clusterware安装和升级
Linux平台 Oracle 10gR2(10.2.0.5)RAC安装 Part2:clusterware安装和升级 环境:OEL 5.7 + Oracle 10.2.0.5 RAC 3.安装Clus ...
- Linux 6.5(oracle 11.2.0.4)单实例ASM安装
Linux 6.5(oracle 11.2.0.4) 1.解析主机.配置网络等 /etc/hosts /etc/sysconfig/network /etc/init.d/NetworkManager ...
- 在Linux上rpm安装运行Redis 3.0.4
http://www.rpmfind.net搜索redis,找到redis3.0.4的rpm源选做 wget ftp://fr2.rpmfind.net/linux/remi/enterprise/6 ...
- #npm install# MSBUILD : error MSB4132: 无法识别工具版本“2.0”。可用的工具版本为 "4.0"。
0.问题描述 Windows 10 最近使用npm install安装项目依赖包,当自动执行至node-gyp rebuild时报错: C:\Users\dsl\Desktop\Pros\ant-de ...
- 基于.NetCore2.1。服务类库采用.Net Standard2.0,兼容.net 4.6.1消息推送服务
基于.NetCore2.1.服务类库采用.Net Standard2.0,兼容.net 4.6.1消息推送服务 https://www.cnblogs.com/ibeisha/p/weixinServ ...
- centos / Linux 服务环境下安装 Redis 5.0.3
原文:centos / Linux 服务环境下安装 Redis 5.0.3 1.首先进入你要安装的目录 cd /usr/local 2.下载目前最新稳定版本 Redis 5.0.3 wget http ...
- (8)Linux(客户端)和Windows(服务端)下socket通信实例
Linux(客户端)和Windows(服务端)下socket通信实例: (1)首先是Windows做客户端,Linux做服务端的程序 Windows Client端 #include <st ...
随机推荐
- 使用filebeat收集日志传输到redis的各种效果展示
0 环境 Linux主机,cengtos7系统 安装有openresty软件,用来访问生成日志信息 1.15.8版本 安装有filebeat软件,用来收集openresty的日志 7.3版本 安装有r ...
- MySQL数据库入门多实例配置
MySQL数据库入门——多实例配置 前面介绍了相关的基础命令操作,所有的操作都是基于单实例的,mysql多实例在实际生产环境也是非常实用的,因为必须要掌握 1.什么是多实例 多实例就是一台服务器上开启 ...
- vue使用Vuex, IE浏览器报错
错误: [vuex] vuex requires a Promise polyfill in this browser. 原因:因为使用了 ES6 中用来传递异步消息的的Promise,而IE低版本 ...
- 2019-11-29-win10-uwp-如何开始写-uwp-程序
title author date CreateTime categories win10 uwp 如何开始写 uwp 程序 lindexi 2019-11-29 10:12:42 +0800 201 ...
- Django中数据库的增删改查
本随笔使用的是pycharm专业版2019.1.3.Django==1.9.8.Python2.7 这里的Django后台使用了ORM(Object Relational Mapping),全称对象关 ...
- 用C#实现DES加密解密封装
主要用到C#提供的以下三个类:MemoryStream 内存流DESCryptoServiceProvider 加密服务提供者类CryptoStream 讲数据流连接到加密转换的流 using Sys ...
- HDU5840 Problem This world need more Zhu 分块 树剖
给一颗n个点的有点权的树,有m个询问,对于每个询问u,v,k,首先将点u到点v的最短路径上的所有点按顺序编号,u的编号为1,求树链上所有点的新编号cnt满足cnt%k==0的点的权值的最大值.n,m, ...
- SSH开发环境整合
第一步:Spring开发环境搭建 1.1: 添加配置文件和相应spring-3.2-core.Jar 核心包 配置文件 <?xml version="1.0" encodin ...
- SQL复杂筛选
SELECT A.MATERIALID,A.MATERIALNAME,ISNULL(A.COMPIDSEQ,'') COMPIDSEQ,ISNULL(A.SUPPLYID,'') SUPPLYID,S ...
- 9. ClustrixDB主从复制
一.在线添加从库 主集群: 10.1.1.23:5306 从集群: 10.1.3.88:5306 主库开启binlog MySQL [(none)]> CREATE BINLOG 'clustr ...