├── ConsoleTestServer ├── App.config ├── Properties │ └── AssemblyInfo.cs ├── Program.cs └── ConsoleTestServer.csproj ├── ConsoleTestClient ├── App.config ├── Properties │ └── AssemblyInfo.cs ├── Program.cs └── ConsoleTestClient.csproj ├── README.md ├── IOCPUtils ├── Handler.cs ├── SocketResult.cs ├── Properties │ └── AssemblyInfo.cs ├── PlatformHelper.cs ├── UserTokenPool.cs ├── IOCPUtils.csproj ├── BufferManager.cs ├── UserToken.cs ├── SocketExtensions.cs ├── IOCPBase.cs └── TaskCompletionSource.cs ├── IOCPClient ├── Properties │ └── AssemblyInfo.cs ├── IOCPClient.csproj └── Client.cs ├── IOCPServer ├── Properties │ └── AssemblyInfo.cs ├── IOCPServer.csproj └── Server.cs ├── LICENSE ├── SimpleIOCPServer.sln └── .gitignore /ConsoleTestServer/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ConsoleTestClient/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## AsyncSocket 2 | A simple asynchronous Client/Server Socket that uses I/O Completion Port for high performance, and implements the Task-based Asynchronous Pattern with Async and Await. 3 | 4 | ## Reference 5 | 1. [Cowboy](https://github.com/gaochundong/Cowboy "Cowboy") 6 | 2. [MSDN](https://msdn.microsoft.com/en-us/library/system.net.sockets.socketasynceventargs.aspx "MSDN") 7 | -------------------------------------------------------------------------------- /IOCPUtils/Handler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Sockets; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace IOCPUtils 9 | { 10 | public delegate Task OnAcceptedHandler(IOCPBase server, int num, Socket socket); 11 | public delegate Task OnServerErrorHandler(Exception ex, params object[] args); 12 | } 13 | -------------------------------------------------------------------------------- /IOCPUtils/SocketResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Sockets; 3 | 4 | namespace IOCPUtils 5 | { 6 | public sealed class SocketResult 7 | { 8 | public static SocketResult NotSocket = new SocketResult { SocketError=SocketError.NotSocket }; 9 | public byte[] Data { get; set; } 10 | public int Length { get; set; } 11 | public SocketError SocketError { get; set; } 12 | public bool ReceiveSuccess 13 | { 14 | get 15 | { 16 | return this.Length > 0 && this.SocketError == SocketError.Success; 17 | } 18 | } 19 | public bool ReceiveAvailable 20 | { 21 | get; 22 | set; 23 | } 24 | public SocketAsyncEventArgs Args { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /IOCPClient/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // 有关程序集的一般信息由以下 6 | // 控制。更改这些特性值可修改 7 | // 与程序集关联的信息。 8 | [assembly: AssemblyTitle("IOCPClient")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("IOCPClient")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | //将 ComVisible 设置为 false 将使此程序集中的类型 18 | //对 COM 组件不可见。 如果需要从 COM 访问此程序集中的类型, 19 | //请将此类型的 ComVisible 特性设置为 true。 20 | [assembly: ComVisible(false)] 21 | 22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID 23 | [assembly: Guid("9af83a6d-72c3-4a0c-9c5a-750f889e88dd")] 24 | 25 | // 程序集的版本信息由下列四个值组成: 26 | // 27 | // 主版本 28 | // 次版本 29 | // 生成号 30 | // 修订号 31 | // 32 | //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值, 33 | // 方法是按如下所示使用“*”: : 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /IOCPServer/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // 有关程序集的一般信息由以下 6 | // 控制。更改这些特性值可修改 7 | // 与程序集关联的信息。 8 | [assembly: AssemblyTitle("IOCPServer")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("IOCPServer")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | //将 ComVisible 设置为 false 将使此程序集中的类型 18 | //对 COM 组件不可见。 如果需要从 COM 访问此程序集中的类型, 19 | //请将此类型的 ComVisible 特性设置为 true。 20 | [assembly: ComVisible(false)] 21 | 22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID 23 | [assembly: Guid("e12451f6-875b-44a6-8810-a9e396e9a1c1")] 24 | 25 | // 程序集的版本信息由下列四个值组成: 26 | // 27 | // 主版本 28 | // 次版本 29 | // 生成号 30 | // 修订号 31 | // 32 | //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值, 33 | // 方法是按如下所示使用“*”: : 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /IOCPUtils/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // 有关程序集的一般信息由以下 6 | // 控制。更改这些特性值可修改 7 | // 与程序集关联的信息。 8 | [assembly: AssemblyTitle("IOCPUtils")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("IOCPUtils")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | //将 ComVisible 设置为 false 将使此程序集中的类型 18 | //对 COM 组件不可见。 如果需要从 COM 访问此程序集中的类型, 19 | //请将此类型的 ComVisible 特性设置为 true。 20 | [assembly: ComVisible(false)] 21 | 22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID 23 | [assembly: Guid("6cffe40e-4ad0-40d1-a7c5-3080bf43317d")] 24 | 25 | // 程序集的版本信息由下列四个值组成: 26 | // 27 | // 主版本 28 | // 次版本 29 | // 生成号 30 | // 修订号 31 | // 32 | //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值, 33 | // 方法是按如下所示使用“*”: : 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ConsoleTestClient/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // 有关程序集的一般信息由以下 6 | // 控制。更改这些特性值可修改 7 | // 与程序集关联的信息。 8 | [assembly: AssemblyTitle("ConsoleTestClient")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("ConsoleTestClient")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | //将 ComVisible 设置为 false 将使此程序集中的类型 18 | //对 COM 组件不可见。 如果需要从 COM 访问此程序集中的类型, 19 | //请将此类型的 ComVisible 特性设置为 true。 20 | [assembly: ComVisible(false)] 21 | 22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID 23 | [assembly: Guid("ab37cc50-23d7-4113-bf3d-3e0bdf430e01")] 24 | 25 | // 程序集的版本信息由下列四个值组成: 26 | // 27 | // 主版本 28 | // 次版本 29 | // 生成号 30 | // 修订号 31 | // 32 | //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值, 33 | // 方法是按如下所示使用“*”: : 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /ConsoleTestServer/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // 有关程序集的一般信息由以下 6 | // 控制。更改这些特性值可修改 7 | // 与程序集关联的信息。 8 | [assembly: AssemblyTitle("ConsoleTestServer")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("ConsoleTestServer")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | //将 ComVisible 设置为 false 将使此程序集中的类型 18 | //对 COM 组件不可见。 如果需要从 COM 访问此程序集中的类型, 19 | //请将此类型的 ComVisible 特性设置为 true。 20 | [assembly: ComVisible(false)] 21 | 22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID 23 | [assembly: Guid("0c6e94dd-3131-47f7-8fc8-44d0e4125c5e")] 24 | 25 | // 程序集的版本信息由下列四个值组成: 26 | // 27 | // 主版本 28 | // 次版本 29 | // 生成号 30 | // 修订号 31 | // 32 | //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值, 33 | // 方法是按如下所示使用“*”: : 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /IOCPUtils/PlatformHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace IOCPUtils 4 | { 5 | internal static class PlatformHelper 6 | { 7 | private const int PROCESSOR_COUNT_REFRESH_INTERVAL_MS = 30000; 8 | 9 | private static volatile int s_processorCount; 10 | 11 | private static volatile int s_lastProcessorCountRefreshTicks; 12 | 13 | internal static int ProcessorCount 14 | { 15 | get 16 | { 17 | int tickCount = Environment.TickCount; 18 | int num = PlatformHelper.s_processorCount; 19 | if (num == 0 || tickCount - PlatformHelper.s_lastProcessorCountRefreshTicks >= 30000) 20 | { 21 | num = (PlatformHelper.s_processorCount = Environment.ProcessorCount); 22 | PlatformHelper.s_lastProcessorCountRefreshTicks = tickCount; 23 | } 24 | return num; 25 | } 26 | } 27 | 28 | internal static bool IsSingleProcessor 29 | { 30 | get 31 | { 32 | return PlatformHelper.ProcessorCount == 1; 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /IOCPUtils/UserTokenPool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Net.Sockets; 4 | 5 | namespace IOCPUtils 6 | { 7 | public sealed class UserTokenPool 8 | { 9 | ConcurrentStack m_pool; 10 | public UserTokenPool() 11 | { 12 | m_pool = new ConcurrentStack(); 13 | } 14 | public void Push(UserToken item) 15 | { 16 | if (item == null) { throw new ArgumentNullException("Items added to a UserTokenPool cannot be null"); } 17 | m_pool.Push(item); 18 | } 19 | public void Reture(UserToken token) 20 | { 21 | token.TimeOut = -1; 22 | token.Id = Guid.Empty.ToString(); 23 | token.ConnectSocket = null; 24 | token.ReceiveArgs.RemoteEndPoint = null; 25 | Push(token); 26 | } 27 | private void InitItem(UserToken token) 28 | { 29 | token.Id = Guid.NewGuid().ToString(); 30 | } 31 | public UserToken Pop() 32 | { 33 | UserToken item = null; 34 | if (m_pool.TryPop(out item)) 35 | { 36 | InitItem(item); 37 | return item; 38 | } 39 | return item; 40 | } 41 | public int Count 42 | { 43 | get 44 | { 45 | return m_pool.Count; 46 | } 47 | } 48 | public void Clear() 49 | { 50 | m_pool.Clear(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ConsoleTestClient/Program.cs: -------------------------------------------------------------------------------- 1 | using IOCPClient; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Configuration; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Net.Sockets; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace ConsoleTestClient 12 | { 13 | class Program 14 | { 15 | static void Main(string[] args) 16 | { 17 | Test().Wait(); 18 | Console.ReadKey(); 19 | } 20 | static async Task Test() 21 | { 22 | IPAddress addr = IPAddress.Parse(ConfigurationManager.AppSettings["remoteAddr"]); 23 | int port = int.Parse(ConfigurationManager.AppSettings["remotePort"]); 24 | Client client = new Client(new IPEndPoint(addr, port)); 25 | while(true) 26 | { 27 | try 28 | { 29 | var result = await client.ConnectAsync(10000); 30 | if (result.SocketError == SocketError.Success) 31 | break; 32 | Console.WriteLine("无法连接,错误:" + result.SocketError); 33 | } 34 | catch(Exception ex) 35 | { 36 | Console.WriteLine(ex.Message); 37 | await Task.Delay(1000); 38 | } 39 | } 40 | while(true) 41 | { 42 | try 43 | { 44 | var result = await client.ReceiveAsync(client.ConnectSocket, 10000); 45 | if(!result.ReceiveSuccess) 46 | { 47 | Console.WriteLine("没有接收到数据"); 48 | break; 49 | } 50 | Console.WriteLine("接收到的数据为:" + client.Encoding.GetString(result.Data)); 51 | await client.SendAsync(client.ConnectSocket, client.Encoding.GetBytes(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"))); 52 | } 53 | catch(Exception ex) 54 | { 55 | Console.WriteLine("接收数据发生错误:" + ex.Message); 56 | } 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ConsoleTestServer/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using IOCPServer; 7 | using IOCPUtils; 8 | using System.Configuration; 9 | 10 | namespace ConsoleTestServer 11 | { 12 | class Program 13 | { 14 | static void Main(string[] args) 15 | { 16 | int port = int.Parse( ConfigurationManager.AppSettings["port"]); 17 | Server server = new Server(port, 50); 18 | server.OnAccpetErrored += Server_OnErrored; 19 | server.Start(Server_OnAccepted); 20 | Console.WriteLine("服务器已经启动..."); 21 | Console.ReadKey(); 22 | server.Stop(); 23 | Console.WriteLine("服务器已经关闭..."); 24 | } 25 | 26 | private static async Task Server_OnErrored(Exception ex, params object[] args) 27 | { 28 | await Console.Out.WriteLineAsync("服务器出现错误:" + ex.Message); 29 | } 30 | 31 | private static async Task Server_OnAccepted(IOCPBase server, int num, System.Net.Sockets.Socket socket) 32 | { 33 | while(true) 34 | { 35 | try 36 | { 37 | SocketResult result = await server.SendAsync(socket, Encoding.UTF8.GetBytes(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"))); 38 | 39 | Console.WriteLine("send result:"+result.SocketError); 40 | if(result.SocketError!=System.Net.Sockets.SocketError.Success) 41 | { 42 | server.CloseClientSocket(socket); 43 | return; 44 | } 45 | result = await server.ReceiveAsync(socket); 46 | Console.WriteLine("receive result:" + result.SocketError); 47 | if (result.SocketError != System.Net.Sockets.SocketError.Success) 48 | { 49 | server.CloseClientSocket(socket); 50 | return; 51 | } 52 | await Task.Delay(1000); 53 | } 54 | catch(Exception ex) 55 | { 56 | Console.WriteLine("error:" + ex.Message); 57 | server.CloseClientSocket(socket); 58 | return; 59 | } 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /IOCPClient/IOCPClient.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {9AF83A6D-72C3-4A0C-9C5A-750F889E88DD} 8 | Library 9 | Properties 10 | IOCPClient 11 | IOCPClient 12 | v4.5 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | {6CFFE40E-4AD0-40D1-A7C5-3080BF43317D} 49 | IOCPUtils 50 | 51 | 52 | 53 | 60 | -------------------------------------------------------------------------------- /IOCPServer/IOCPServer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {E12451F6-875B-44A6-8810-A9E396E9A1C1} 8 | Library 9 | Properties 10 | IOCPServer 11 | IOCPServer 12 | v4.5 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | {6cffe40e-4ad0-40d1-a7c5-3080bf43317d} 49 | IOCPUtils 50 | 51 | 52 | 53 | 60 | -------------------------------------------------------------------------------- /IOCPUtils/IOCPUtils.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {6CFFE40E-4AD0-40D1-A7C5-3080BF43317D} 8 | Library 9 | Properties 10 | IOCPUtils 11 | IOCPUtils 12 | v4.5 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 62 | -------------------------------------------------------------------------------- /SimpleIOCPServer.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IOCPServer", "IOCPServer\IOCPServer.csproj", "{E12451F6-875B-44A6-8810-A9E396E9A1C1}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IOCPClient", "IOCPClient\IOCPClient.csproj", "{9AF83A6D-72C3-4A0C-9C5A-750F889E88DD}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IOCPUtils", "IOCPUtils\IOCPUtils.csproj", "{6CFFE40E-4AD0-40D1-A7C5-3080BF43317D}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleTestServer", "ConsoleTestServer\ConsoleTestServer.csproj", "{0C6E94DD-3131-47F7-8FC8-44D0E4125C5E}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleTestClient", "ConsoleTestClient\ConsoleTestClient.csproj", "{AB37CC50-23D7-4113-BF3D-3E0BDF430E01}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {E12451F6-875B-44A6-8810-A9E396E9A1C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {E12451F6-875B-44A6-8810-A9E396E9A1C1}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {E12451F6-875B-44A6-8810-A9E396E9A1C1}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {E12451F6-875B-44A6-8810-A9E396E9A1C1}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {9AF83A6D-72C3-4A0C-9C5A-750F889E88DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {9AF83A6D-72C3-4A0C-9C5A-750F889E88DD}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {9AF83A6D-72C3-4A0C-9C5A-750F889E88DD}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {9AF83A6D-72C3-4A0C-9C5A-750F889E88DD}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {6CFFE40E-4AD0-40D1-A7C5-3080BF43317D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {6CFFE40E-4AD0-40D1-A7C5-3080BF43317D}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {6CFFE40E-4AD0-40D1-A7C5-3080BF43317D}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {6CFFE40E-4AD0-40D1-A7C5-3080BF43317D}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {0C6E94DD-3131-47F7-8FC8-44D0E4125C5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {0C6E94DD-3131-47F7-8FC8-44D0E4125C5E}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {0C6E94DD-3131-47F7-8FC8-44D0E4125C5E}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {0C6E94DD-3131-47F7-8FC8-44D0E4125C5E}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {AB37CC50-23D7-4113-BF3D-3E0BDF430E01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {AB37CC50-23D7-4113-BF3D-3E0BDF430E01}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {AB37CC50-23D7-4113-BF3D-3E0BDF430E01}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {AB37CC50-23D7-4113-BF3D-3E0BDF430E01}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | EndGlobal 47 | -------------------------------------------------------------------------------- /IOCPUtils/BufferManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.Sockets; 4 | 5 | namespace IOCPUtils 6 | { 7 | // This class creates a single large buffer which can be divided up 8 | // and assigned to SocketAsyncEventArgs objects for use with each 9 | // socket I/O operation. 10 | // This enables bufffers to be easily reused and guards against 11 | // fragmenting heap memory. 12 | // 13 | // The operations exposed on the BufferManager class are not thread safe. 14 | public sealed class BufferManager 15 | { 16 | int m_numBytes; // the total number of bytes controlled by the buffer pool 17 | byte[] m_buffer; // the underlying byte array maintained by the Buffer Manager 18 | Stack m_freeIndexPool; // 19 | int m_currentIndex; 20 | int m_bufferSize; 21 | 22 | public BufferManager(int totalBytes, int bufferSize) 23 | { 24 | m_numBytes = totalBytes; 25 | m_currentIndex = 0; 26 | m_bufferSize = bufferSize; 27 | m_freeIndexPool = new Stack(); 28 | } 29 | 30 | // Allocates buffer space used by the buffer pool 31 | public void InitBuffer() 32 | { 33 | // create one big large buffer and divide that 34 | // out to each SocketAsyncEventArg object 35 | m_buffer = new byte[m_numBytes]; 36 | } 37 | 38 | // Assigns a buffer from the buffer pool to the 39 | // specified SocketAsyncEventArgs object 40 | // 41 | // true if the buffer was successfully set, else false 42 | public bool SetBuffer(SocketAsyncEventArgs args) 43 | { 44 | 45 | if (m_freeIndexPool.Count > 0) 46 | { 47 | args.SetBuffer(m_buffer, m_freeIndexPool.Pop(), m_bufferSize); 48 | } 49 | else 50 | { 51 | if ((m_numBytes - m_bufferSize) < m_currentIndex) 52 | { 53 | return false; 54 | } 55 | args.SetBuffer(m_buffer, m_currentIndex, m_bufferSize); 56 | m_currentIndex += m_bufferSize; 57 | } 58 | return true; 59 | } 60 | 61 | // Removes the buffer from a SocketAsyncEventArg object. 62 | // This frees the buffer back to the buffer pool 63 | public void FreeBuffer(UserToken token) 64 | { 65 | lock (token) 66 | { 67 | if (token.ReceiveArgs.Buffer == this.m_buffer) 68 | { 69 | this.ReturnBuffer(token); 70 | } 71 | } 72 | } 73 | public void ReturnBuffer(UserToken token) 74 | { 75 | this.m_freeIndexPool.Push(token.ReceiveArgs.Offset); 76 | } 77 | public void ResetBuffer(UserToken token) 78 | { 79 | if (token.ReceiveArgs.Buffer == this.m_buffer) 80 | { 81 | Array.Clear(this.m_buffer, token.ReceiveArgs.Offset, m_bufferSize); 82 | token.ReceiveArgs.SetBuffer(token.ReceiveArgs.Offset, m_bufferSize); 83 | } 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /ConsoleTestClient/ConsoleTestClient.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {AB37CC50-23D7-4113-BF3D-3E0BDF430E01} 8 | Exe 9 | Properties 10 | ConsoleTestClient 11 | ConsoleTestClient 12 | v4.5 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | {9AF83A6D-72C3-4A0C-9C5A-750F889E88DD} 55 | IOCPClient 56 | 57 | 58 | {6cffe40e-4ad0-40d1-a7c5-3080bf43317d} 59 | IOCPUtils 60 | 61 | 62 | 63 | 70 | -------------------------------------------------------------------------------- /ConsoleTestServer/ConsoleTestServer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {0C6E94DD-3131-47F7-8FC8-44D0E4125C5E} 8 | Exe 9 | Properties 10 | ConsoleTestServer 11 | ConsoleTestServer 12 | v4.5 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | {e12451f6-875b-44a6-8810-a9e396e9a1c1} 55 | IOCPServer 56 | 57 | 58 | {6CFFE40E-4AD0-40D1-A7C5-3080BF43317D} 59 | IOCPUtils 60 | 61 | 62 | 63 | 70 | -------------------------------------------------------------------------------- /IOCPUtils/UserToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Sockets; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace IOCPUtils 7 | { 8 | public class UserToken 9 | { 10 | private Socket connectSocket; 11 | private TaskCompletionSource completionSource; 12 | private Timer timer = null; 13 | public UserToken() 14 | { 15 | completionSource = new TaskCompletionSource(); 16 | timer = new Timer(Timer_Elapsed, null, Timeout.Infinite, Timeout.Infinite); 17 | } 18 | 19 | private void Timer_Elapsed(object state) 20 | { 21 | #if DEBUG 22 | Console.WriteLine("Timer_Elapsed fired"); 23 | #endif 24 | SetCanceled(); 25 | 26 | } 27 | 28 | public string Id { get; set; } 29 | 30 | public SocketAsyncEventArgs ReceiveArgs 31 | { 32 | get; 33 | set; 34 | } 35 | 36 | public Socket ConnectSocket 37 | { 38 | get 39 | { 40 | return this.connectSocket; 41 | } 42 | set 43 | { 44 | this.connectSocket = value; 45 | this.ReceiveArgs.AcceptSocket = this.connectSocket; 46 | } 47 | } 48 | public Task CompletionSource 49 | { 50 | get 51 | { 52 | ResetTask(); 53 | if (TimeOut > 0) 54 | { 55 | timer.Change(TimeOut, Timeout.Infinite); 56 | } 57 | #if DEBUG 58 | Console.WriteLine($"return a UserToken: {Id}, TaskID: {completionSource.Task.Id}"); 59 | #endif 60 | return completionSource.Task; 61 | } 62 | } 63 | 64 | public int TimeOut { get; set; } = -1; 65 | public void SetResult(SocketResult result) 66 | { 67 | if (completionSource.Task.IsCompleted) 68 | return; 69 | completionSource.SetResult(result); 70 | timer.Change(Timeout.Infinite, Timeout.Infinite); 71 | #if DEBUG 72 | Console.WriteLine($"UserToken SetResult: {Id} TaskID: {completionSource.Task.Id}"); 73 | #endif 74 | } 75 | public void SetException(Exception exception) 76 | { 77 | if (completionSource.Task.IsCompleted) 78 | return; 79 | completionSource.SetResult(SocketResult.NotSocket); 80 | timer.Change(Timeout.Infinite, Timeout.Infinite); 81 | #if DEBUG 82 | Console.WriteLine($"UserToken SetException: {Id} TaskID: {completionSource.Task.Id}"); 83 | #endif 84 | } 85 | public void SetCanceled() 86 | { 87 | if (completionSource.Task.IsCompleted) 88 | return; 89 | completionSource.SetResult(SocketResult.NotSocket); 90 | timer.Change(Timeout.Infinite, Timeout.Infinite); 91 | #if DEBUG 92 | Console.WriteLine($"UserToken SetCanceled: {Id} TaskID: {completionSource.Task.Id}"); 93 | #endif 94 | } 95 | private void ResetTask() 96 | { 97 | TaskCompletionSource.Reset(ref completionSource); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /IOCPUtils/SocketExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Net.Sockets; 4 | using System.Threading.Tasks; 5 | 6 | namespace IOCPUtils 7 | { 8 | public static class SocketExtensions 9 | { 10 | private static Func checkIfDisposed; 11 | static SocketExtensions() 12 | { 13 | var type = typeof(Socket); 14 | var propertyInfo = type.GetProperty("CleanedUp", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); 15 | var p = Expression.Parameter(type, "socket"); 16 | var body=Expression.Property(p, propertyInfo); 17 | checkIfDisposed = Expression.Lambda>(body, p).Compile(); 18 | } 19 | public static bool IsDisposed(this Socket socket) 20 | { 21 | return checkIfDisposed(socket); 22 | } 23 | public static Task ConnectAsync(this Socket socket, IOCPBase iocpBase, UserToken userToken) 24 | { 25 | var task = userToken.CompletionSource; 26 | if(!socket.ConnectAsync(userToken.ReceiveArgs)) 27 | { 28 | SocketResult result = new SocketResult { SocketError = userToken.ReceiveArgs.SocketError, Args = userToken.ReceiveArgs }; 29 | iocpBase.ProcessConnect(userToken.ReceiveArgs, result); 30 | } 31 | return task; 32 | } 33 | public static Task DisconnectAsync(this Socket socket, IOCPBase iocpBase, UserToken userToken) 34 | { 35 | var task = userToken.CompletionSource; 36 | if(!socket.DisconnectAsync(userToken.ReceiveArgs)) 37 | { 38 | SocketResult result = new SocketResult { SocketError = userToken.ReceiveArgs.SocketError, Args = userToken.ReceiveArgs }; 39 | iocpBase.ProcessDisconnect(userToken.ReceiveArgs, result); 40 | } 41 | return task; 42 | } 43 | public static Task ReceiveAsync(this Socket socket,IOCPBase iocpBase, UserToken userToken) 44 | { 45 | var task = userToken.CompletionSource; 46 | if(!socket.ReceiveAsync(userToken.ReceiveArgs)) 47 | { 48 | SocketResult result = new SocketResult { SocketError = userToken.ReceiveArgs.SocketError, Args = userToken.ReceiveArgs }; 49 | iocpBase.ProcessReceive(userToken.ReceiveArgs, result); 50 | } 51 | return task; 52 | } 53 | public static Task SendAsync(this Socket socket, byte[] data, IOCPBase iocpBase, UserToken userToken) 54 | { 55 | if (data.Length>iocpBase.BufferSize) 56 | { 57 | SendPacketsElement[] elements = new SendPacketsElement[1]; 58 | elements[0] = new SendPacketsElement(data,0,data.Length,true); 59 | return SendDataAsync(socket, elements, iocpBase, userToken); 60 | } 61 | var task = userToken.CompletionSource; 62 | Array.Copy(data, 0, userToken.ReceiveArgs.Buffer, userToken.ReceiveArgs.Offset, data.Length); 63 | userToken.ReceiveArgs.SetBuffer(userToken.ReceiveArgs.Offset, data.Length); 64 | if(!socket.SendAsync(userToken.ReceiveArgs)) 65 | { 66 | SocketResult result = new SocketResult { SocketError = userToken.ReceiveArgs.SocketError, Args = userToken.ReceiveArgs }; 67 | iocpBase.ProcessSend(userToken.ReceiveArgs, result); 68 | } 69 | return task; 70 | } 71 | public static Task SendFileAsync(this Socket socket, string fileName, IOCPBase iocpBase, UserToken userToken) 72 | { 73 | SendPacketsElement[] elements = new SendPacketsElement[1]; 74 | elements[0] = new SendPacketsElement(fileName,0,0,true); 75 | return SendDataAsync(socket, elements, iocpBase, userToken); 76 | } 77 | static Task SendDataAsync(Socket socket, SendPacketsElement[] elements, IOCPBase iocpBase, UserToken userToken) 78 | { 79 | var task = userToken.CompletionSource; 80 | userToken.ReceiveArgs.SendPacketsElements = elements; 81 | userToken.ReceiveArgs.SendPacketsFlags = TransmitFileOptions.UseKernelApc; 82 | if(!socket.SendPacketsAsync(userToken.ReceiveArgs)) 83 | { 84 | SocketResult result = new SocketResult { SocketError = userToken.ReceiveArgs.SocketError, Args = userToken.ReceiveArgs }; 85 | iocpBase.ProcessSendPackets(userToken.ReceiveArgs, result); 86 | } 87 | return task; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | -------------------------------------------------------------------------------- /IOCPUtils/IOCPBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Sockets; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace IOCPUtils 9 | { 10 | public abstract class IOCPBase 11 | { 12 | #region Properties 13 | public abstract int BufferSize { get; protected set; } 14 | protected internal int DefaultConcurrencyLevel 15 | { 16 | get 17 | { 18 | return 4 * PlatformHelper.ProcessorCount; 19 | } 20 | } 21 | #endregion 22 | 23 | #region Set Socket Options 24 | 25 | protected void SetSocketOptions(Socket socket) 26 | { 27 | socket.ReceiveBufferSize = this.BufferSize; 28 | socket.SendBufferSize = this.BufferSize; 29 | } 30 | 31 | #endregion 32 | 33 | #region Connect 34 | 35 | protected internal void ProcessConnect(SocketAsyncEventArgs e, SocketResult result) 36 | { 37 | Socket s = e.AcceptSocket; 38 | var userToken = e.UserToken as UserToken; 39 | try 40 | { 41 | userToken.SetResult(result); 42 | } 43 | catch(Exception ex) 44 | { 45 | userToken.SetException(ex); 46 | } 47 | finally 48 | { 49 | RecycleToken(userToken); 50 | } 51 | } 52 | 53 | #endregion 54 | 55 | #region Disconnect 56 | 57 | protected internal void ProcessDisconnect(SocketAsyncEventArgs e, SocketResult result) 58 | { 59 | var userToken = e.UserToken as UserToken; 60 | try 61 | { 62 | userToken.SetResult(result); 63 | } 64 | catch (Exception ex) 65 | { 66 | userToken.SetException(ex); 67 | } 68 | finally 69 | { 70 | RecycleToken(userToken); 71 | } 72 | } 73 | 74 | #endregion 75 | 76 | #region Receive 77 | 78 | protected internal void ProcessReceive(SocketAsyncEventArgs e, SocketResult result) 79 | { 80 | var userToken = e.UserToken as UserToken; 81 | Socket s = userToken.ConnectSocket; 82 | try 83 | { 84 | result.Length = e.BytesTransferred; 85 | result.ReceiveAvailable = s.Available > 0; 86 | if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success) 87 | { 88 | result.Data = new byte[e.BytesTransferred]; 89 | Array.Copy(e.Buffer, e.Offset, result.Data, 0, result.Data.Length); 90 | Array.Clear(e.Buffer, e.Offset, result.Data.Length); 91 | } 92 | userToken.SetResult(result); 93 | } 94 | catch (Exception ex) 95 | { 96 | userToken.SetException(ex); 97 | } 98 | finally 99 | { 100 | RecycleToken(userToken); 101 | } 102 | } 103 | 104 | #endregion 105 | 106 | #region Send 107 | 108 | protected internal void ProcessSend(SocketAsyncEventArgs e, SocketResult result) 109 | { 110 | var userToken = e.UserToken as UserToken; 111 | try 112 | { 113 | userToken.SetResult(result); 114 | } 115 | catch (Exception ex) 116 | { 117 | userToken.SetException(ex); 118 | } 119 | finally 120 | { 121 | RecycleToken(userToken); 122 | } 123 | } 124 | 125 | protected internal void ProcessSendPackets(SocketAsyncEventArgs e, SocketResult result) 126 | { 127 | var userToken = e.UserToken as UserToken; 128 | try 129 | { 130 | userToken.SetResult(result); 131 | } 132 | catch (Exception ex) 133 | { 134 | userToken.SetException(ex); 135 | } 136 | finally 137 | { 138 | userToken.ReceiveArgs.SendPacketsElements = null; 139 | userToken.ReceiveArgs.SendPacketsFlags = TransmitFileOptions.UseDefaultWorkerThread; 140 | RecycleToken(userToken); 141 | } 142 | 143 | } 144 | 145 | #endregion 146 | 147 | #region Abstract 148 | 149 | #region Close 150 | public abstract void CloseClientSocket(Socket socket); 151 | 152 | protected abstract void RecycleToken(UserToken token); 153 | #endregion 154 | 155 | #region Async Receive 156 | 157 | public abstract Task ReceiveAsync(Socket socket, int timeOut = -1); 158 | 159 | #endregion 160 | 161 | #region Async Send 162 | 163 | public abstract Task SendAsync(Socket socket, byte[] data, int timeOut = -1); 164 | 165 | #endregion 166 | 167 | #region Async Send File 168 | 169 | public abstract Task SendFileAsync(Socket socket, string fileName, int timeOut = -1); 170 | 171 | #endregion 172 | 173 | #region Async Disconnect 174 | 175 | public abstract Task DisconnectAsync(Socket socket, int timeOut = -1); 176 | 177 | #endregion 178 | 179 | #endregion 180 | 181 | #region Callback 182 | protected void OnIOCompleted(object serder, SocketAsyncEventArgs e) 183 | { 184 | SocketResult result = new SocketResult { SocketError = e.SocketError, Args=e }; 185 | switch (e.LastOperation) 186 | { 187 | case SocketAsyncOperation.Connect: 188 | ProcessConnect(e, result); 189 | break; 190 | case SocketAsyncOperation.Receive: 191 | ProcessReceive(e, result); 192 | break; 193 | case SocketAsyncOperation.Send: 194 | ProcessSend(e, result); 195 | break; 196 | case SocketAsyncOperation.SendPackets: 197 | ProcessSendPackets(e, result); 198 | break; 199 | case SocketAsyncOperation.Disconnect: 200 | ProcessDisconnect(e, result); 201 | break; 202 | default: 203 | throw new ArgumentException("The last operation completed on the socket was not a receive or send"); 204 | } 205 | } 206 | #endregion 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /IOCPUtils/TaskCompletionSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | using System.Runtime.CompilerServices; 5 | using System.Threading.Tasks; 6 | 7 | namespace IOCPUtils 8 | { 9 | /// 10 | /// Utility methods for 11 | /// From https://github.com/tyrotoxin/AsyncEnumerable/blob/master/src/Internals/TaskCompletionSource.cs 12 | /// 13 | public static class TaskCompletionSource 14 | { 15 | private static Func _resetTaskFunc; 16 | static TaskCompletionSource() 17 | { 18 | var m_stateFlags = typeof(Task).GetField("m_stateFlags", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); 19 | var m_continuationObject = typeof(Task).GetField("m_continuationObject", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); 20 | var m_taskId = typeof(Task).GetField("m_taskId", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); 21 | var m_stateObject = typeof(Task).GetField("m_stateObject", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); 22 | if (m_stateFlags != null && m_continuationObject != null && m_taskId != null && m_stateObject != null) 23 | try 24 | { 25 | /* Using Linq Expressions compile a simple function: 26 | * 27 | * int ResetTask(Task task, object state) 28 | * { 29 | * task.m_stateFlags = ; 30 | * task.m_continuationObject = null; 31 | * task.m_taskId = 0; 32 | * m_stateObject = state; 33 | * return 0; 34 | * } 35 | */ 36 | 37 | var defaultStateFlags = (int)m_stateFlags.GetValue(new TaskCompletionSource().Task); 38 | 39 | var targetArg = Expression.Parameter(typeof(Task), "task"); 40 | var stateObjectArg = Expression.Parameter(typeof(object), "stateObject"); 41 | 42 | var body = Expression.Block( 43 | Expression.Assign(Expression.MakeMemberAccess(targetArg, m_stateFlags), Expression.Constant(defaultStateFlags, typeof(int))), 44 | Expression.Assign(Expression.MakeMemberAccess(targetArg, m_continuationObject), Expression.Constant(null, typeof(object))), 45 | Expression.Assign(Expression.MakeMemberAccess(targetArg, m_taskId), Expression.Constant(0, typeof(int))), 46 | Expression.Assign(Expression.MakeMemberAccess(targetArg, m_stateObject), stateObjectArg), 47 | Expression.Constant(0, typeof(int)) // this can be anything of any type - lambda expression allows to compile Func<> only, but not an Action<> 48 | ); 49 | 50 | var lambda = Expression.Lambda(body, targetArg, stateObjectArg); 51 | _resetTaskFunc = (Func)lambda.Compile(); 52 | 53 | // Do initial testing of the reset function 54 | TestResetFunction(); 55 | } 56 | catch 57 | { 58 | // If something goes wrong, the feature just won't be enabled. 59 | _resetTaskFunc = null; 60 | } 61 | } 62 | private static void TestResetFunction() 63 | { 64 | var stateObject1 = new object(); 65 | var stateObject2 = new object(); 66 | var tcs = new TaskCompletionSource(); 67 | 68 | // Test reset before SetResult 69 | _resetTaskFunc(tcs.Task, stateObject1); 70 | if (tcs.Task.IsCanceled || tcs.Task.IsCompleted || tcs.Task.IsFaulted || tcs.Task.AsyncState != stateObject1) 71 | { 72 | _resetTaskFunc = null; 73 | return; 74 | } 75 | 76 | // Test SetResult 77 | tcs.SetResult(123); 78 | if (tcs.Task.IsCanceled || !tcs.Task.IsCompleted || tcs.Task.IsFaulted) 79 | { 80 | _resetTaskFunc = null; 81 | return; 82 | } 83 | 84 | // Test reset before SetCanceled 85 | _resetTaskFunc(tcs.Task, stateObject2); 86 | if (tcs.Task.IsCanceled || tcs.Task.IsCompleted || tcs.Task.IsFaulted || tcs.Task.AsyncState != stateObject2) 87 | { 88 | _resetTaskFunc = null; 89 | return; 90 | } 91 | 92 | // Test SetCanceled 93 | tcs.SetCanceled(); 94 | if (!tcs.Task.IsCanceled || !tcs.Task.IsCompleted || tcs.Task.IsFaulted) 95 | { 96 | _resetTaskFunc = null; 97 | return; 98 | } 99 | 100 | // Test reset before SetException 101 | _resetTaskFunc(tcs.Task, stateObject1); 102 | if (tcs.Task.IsCanceled || tcs.Task.IsCompleted || tcs.Task.IsFaulted || tcs.Task.AsyncState != stateObject1) 103 | { 104 | _resetTaskFunc = null; 105 | return; 106 | } 107 | 108 | // Test SetException 109 | var ex = new Exception(); 110 | tcs.SetException(ex); 111 | if (tcs.Task.IsCanceled || !tcs.Task.IsCompleted || !tcs.Task.IsFaulted || tcs.Task.Exception.InnerException != ex) 112 | { 113 | _resetTaskFunc = null; 114 | return; 115 | } 116 | } 117 | 118 | /// 119 | /// Forcibly disables re-use of instances in the method. 120 | /// This is just a safety switch in case when something goes wrong with re-using instances of . 121 | /// 122 | public static void DisableTaskCompletionSourceReUse() 123 | { 124 | _resetTaskFunc = null; 125 | } 126 | 127 | /// 128 | /// Resets a to initial incomplete state. 129 | /// This method by default re-uses the same instance of the by re-setting internal state of its using reflection. 130 | /// If such feature is not available or explicitly disable with the method, it just returns a new instance of a . 131 | /// 132 | /// Type of the result value 133 | /// Target to be reset or recreated. It's safe to pass null. 134 | /// Optional state object that you pass into constructor. 135 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 136 | public static void Reset(ref TaskCompletionSource taskCompletionSource, object stateObject = null) 137 | { 138 | if (_resetTaskFunc != null && taskCompletionSource != null) 139 | { 140 | _resetTaskFunc(taskCompletionSource.Task, stateObject); 141 | } 142 | else 143 | { 144 | taskCompletionSource = new TaskCompletionSource(); 145 | } 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /IOCPClient/Client.cs: -------------------------------------------------------------------------------- 1 | using IOCPUtils; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Net.Sockets; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | 11 | namespace IOCPClient 12 | { 13 | public class Client : IOCPBase, IDisposable 14 | { 15 | #region Fields 16 | 17 | /// 18 | /// 连接服务器的socket 19 | /// 20 | private Socket _clientSocket; 21 | 22 | /// 23 | /// 服务器监听地址 24 | /// 25 | private IPEndPoint _remoteEndPoint; 26 | 27 | /// 28 | /// 本地监听地址 29 | /// 30 | private IPEndPoint _localEndPoint; 31 | 32 | /// 33 | /// 用于每个I/O Socket操作的缓冲区大小 34 | /// 35 | private int _bufferSize = 1024; 36 | 37 | /// 38 | /// 连接超时 39 | /// 40 | private int _timeOut; 41 | 42 | private int _state; 43 | 44 | private const int _none = 0; 45 | 46 | private const int _connecting = 1; 47 | 48 | private const int _connected = 2; 49 | 50 | private const int _disconnecting = 3; 51 | 52 | private const int _disconnected = 4; 53 | 54 | private const int _closed = 5; 55 | /// 56 | /// 缓冲区管理 57 | /// 58 | private BufferManager _bufferManager; 59 | 60 | /// 61 | /// 对象池 62 | /// 63 | private UserTokenPool _objectPool; 64 | 65 | const int opsToPreAlloc = 2; 66 | 67 | private int _maxClient = 10; 68 | #endregion 69 | 70 | #region Properties 71 | 72 | public bool Connected 73 | { 74 | get 75 | { 76 | return _clientSocket != null && _clientSocket.Connected; 77 | } 78 | } 79 | 80 | public IPEndPoint RemoteEndPoint 81 | { 82 | get 83 | { 84 | return Connected ? (IPEndPoint)_clientSocket.RemoteEndPoint : _remoteEndPoint; 85 | } 86 | } 87 | 88 | public IPEndPoint LocalEndPoint 89 | { 90 | get 91 | { 92 | return Connected ? (IPEndPoint)_clientSocket.LocalEndPoint : _localEndPoint; 93 | } 94 | } 95 | 96 | public Encoding Encoding { get; set; } 97 | 98 | public override int BufferSize 99 | { 100 | get 101 | { 102 | return _bufferSize; 103 | } 104 | 105 | protected set 106 | { 107 | if(_bufferSize!=value && value>0) 108 | { 109 | _bufferSize = value; 110 | } 111 | } 112 | } 113 | public Socket ConnectSocket 114 | { 115 | get 116 | { 117 | return _clientSocket; 118 | } 119 | } 120 | 121 | #endregion 122 | 123 | #region Ctors 124 | 125 | public Client(IPEndPoint remote):this(null,remote,Encoding.UTF8) 126 | { 127 | 128 | } 129 | 130 | public Client(IPEndPoint local, IPEndPoint remote, Encoding encoding, int timeOut=120) 131 | { 132 | if (remote == null) 133 | throw new ArgumentNullException("remote"); 134 | _remoteEndPoint = remote; 135 | _localEndPoint = local; 136 | this.Encoding = encoding; 137 | this._timeOut = timeOut; 138 | _bufferManager = new BufferManager(_bufferSize * _maxClient * opsToPreAlloc, _bufferSize); 139 | _objectPool = new UserTokenPool(); 140 | Init(); 141 | } 142 | 143 | #endregion 144 | 145 | #region Init 146 | private void Init() 147 | { 148 | // Allocates one large byte buffer which all I/O operations use a piece of. This gaurds 149 | // against memory fragmentation 150 | _bufferManager.InitBuffer(); 151 | 152 | // preallocate pool of SocketAsyncEventArgs objects 153 | SocketAsyncEventArgs readWriteEventArg; 154 | UserToken userToken; 155 | for (int i = 0; i < _maxClient; i++) 156 | { 157 | //Pre-allocate a set of reusable SocketAsyncEventArgs 158 | readWriteEventArg = new SocketAsyncEventArgs(); 159 | readWriteEventArg.Completed += new EventHandler(OnIOCompleted); 160 | 161 | // assign a byte buffer from the buffer pool to the SocketAsyncEventArg object 162 | _bufferManager.SetBuffer(readWriteEventArg); 163 | userToken = new UserToken(); 164 | userToken.ReceiveArgs = readWriteEventArg; 165 | readWriteEventArg.UserToken = userToken; 166 | // add SocketAsyncEventArg to the pool 167 | _objectPool.Push(userToken); 168 | } 169 | } 170 | #endregion 171 | 172 | #region Async Function 173 | 174 | #region Connect 175 | 176 | public Task ConnectAsync(int timeOut=-1) 177 | { 178 | int origin = Interlocked.Exchange(ref _state, _connecting); 179 | if(!(origin == _none || origin == _closed)) 180 | { 181 | CloseClientSocket(null); 182 | throw new InvalidOperationException("This tcp socket client is in invalid state when connecting."); 183 | } 184 | _clientSocket = new Socket(SocketType.Stream, ProtocolType.Tcp); 185 | SetSocketOptions(_clientSocket); 186 | if(_localEndPoint!=null) 187 | { 188 | _clientSocket.Bind(_localEndPoint); 189 | } 190 | var userToken = _objectPool.Pop(); 191 | userToken.ConnectSocket = _clientSocket; 192 | userToken.ReceiveArgs.RemoteEndPoint = _remoteEndPoint; 193 | userToken.TimeOut = timeOut; 194 | userToken.ReceiveArgs.SetBuffer(userToken.ReceiveArgs.Offset, 0); 195 | return _clientSocket.ConnectAsync(this, userToken) 196 | .ContinueWith(t => 197 | { 198 | if (Interlocked.CompareExchange(ref _state, _connected, _connecting) != _connecting) 199 | { 200 | CloseClientSocket(null); 201 | throw new InvalidOperationException("This tcp socket client is in invalid state when connected."); 202 | } 203 | if(t.Result.SocketError!=SocketError.Success) 204 | { 205 | CloseClientSocket(null); 206 | } 207 | return t.Result; 208 | }, TaskContinuationOptions.ExecuteSynchronously); 209 | } 210 | 211 | #endregion 212 | 213 | #region Disconnect 214 | 215 | public override Task DisconnectAsync(Socket socket, int timeOut = -1) 216 | { 217 | int origin = Interlocked.Exchange(ref _state, _disconnecting); 218 | if (origin != _connected) 219 | { 220 | throw new InvalidOperationException("This tcp socket client is in invalid state when disconnecting."); 221 | } 222 | var userToken = _objectPool.Pop(); 223 | userToken.ConnectSocket = socket; 224 | userToken.TimeOut = timeOut; 225 | userToken.ReceiveArgs.SetBuffer(userToken.ReceiveArgs.Offset, 0); 226 | return socket.DisconnectAsync(this, userToken) 227 | .ContinueWith(t => 228 | { 229 | if(Interlocked.CompareExchange(ref _state, _disconnected,_disconnecting)!=_disconnecting) 230 | { 231 | throw new InvalidOperationException("This tcp socket client is in invalid state when disconnected"); 232 | } 233 | return t.Result; 234 | }, TaskContinuationOptions.ExecuteSynchronously); 235 | } 236 | 237 | #endregion 238 | 239 | #endregion 240 | 241 | #region Close 242 | 243 | public override void CloseClientSocket(Socket socket) 244 | { 245 | if (Interlocked.Exchange(ref _state, _closed) == _closed) 246 | { 247 | return; 248 | } 249 | if(socket==null) 250 | { 251 | CleanClientSocket(); 252 | return; 253 | } 254 | if (socket.IsDisposed()) 255 | return; 256 | if(socket!=null && socket.Connected) 257 | { 258 | socket.Shutdown(SocketShutdown.Both); 259 | } 260 | if (socket != null) 261 | { 262 | socket.Close(); 263 | } 264 | 265 | } 266 | 267 | private void CleanClientSocket() 268 | { 269 | if(_clientSocket!=null && _clientSocket.Connected) 270 | { 271 | _clientSocket.Shutdown(SocketShutdown.Both); 272 | } 273 | if(_clientSocket!=null) 274 | { 275 | _clientSocket.Close(); 276 | _clientSocket = null; 277 | } 278 | } 279 | 280 | protected override void RecycleToken(UserToken token) 281 | { 282 | _bufferManager.ResetBuffer(token); 283 | _objectPool.Reture(token); 284 | } 285 | 286 | #endregion 287 | 288 | #region IDisposable Support 289 | private bool disposedValue = false; // 要检测冗余调用 290 | 291 | protected virtual void Dispose(bool disposing) 292 | { 293 | if (!disposedValue) 294 | { 295 | if (disposing) 296 | { 297 | _clientSocket?.Close(); 298 | _objectPool.Clear(); 299 | } 300 | disposedValue = true; 301 | } 302 | } 303 | public void Dispose() 304 | { 305 | Dispose(true); 306 | GC.SuppressFinalize(this); 307 | } 308 | 309 | public override Task ReceiveAsync(Socket socket, int timeOut = -1) 310 | { 311 | if (socket == null || !socket.Connected) 312 | return Task.FromResult(SocketResult.NotSocket); 313 | var userToken = _objectPool.Pop(); 314 | userToken.ConnectSocket = socket; 315 | userToken.ReceiveArgs.RemoteEndPoint = _remoteEndPoint; 316 | userToken.TimeOut = timeOut; 317 | return socket.ReceiveAsync(this, userToken); 318 | } 319 | 320 | public override Task SendAsync(Socket socket, byte[] data, int timeOut = -1) 321 | { 322 | if (socket == null || !socket.Connected) 323 | return Task.FromResult(SocketResult.NotSocket); 324 | var userToken = _objectPool.Pop(); 325 | userToken.ConnectSocket = socket; 326 | userToken.ReceiveArgs.RemoteEndPoint = _remoteEndPoint; 327 | userToken.TimeOut = timeOut; 328 | return socket.SendAsync(data, this, userToken); 329 | } 330 | 331 | public override Task SendFileAsync(Socket socket, string fileName, int timeOut = -1) 332 | { 333 | if (socket == null || !socket.Connected) 334 | return Task.FromResult(SocketResult.NotSocket); 335 | var userToken = _objectPool.Pop(); 336 | userToken.ConnectSocket = socket; 337 | userToken.ReceiveArgs.RemoteEndPoint = _remoteEndPoint; 338 | userToken.TimeOut = timeOut; 339 | return socket.SendFileAsync(fileName, this, userToken); 340 | } 341 | #endregion 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /IOCPServer/Server.cs: -------------------------------------------------------------------------------- 1 | using IOCPUtils; 2 | using System; 3 | using System.Collections.Concurrent; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Net.Sockets; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | 11 | namespace IOCPServer 12 | { 13 | public class Server : IOCPBase,IDisposable 14 | { 15 | const int opsToPreAlloc = 2; 16 | const int _waitResource = 0; 17 | const int _accepting = 1; 18 | const int _accepted = 2; 19 | #region Fields 20 | 21 | /// 22 | /// 服务器程序允许的最大客户端连接数 23 | /// 24 | private int _maxClient; 25 | 26 | /// 27 | /// 监听Socket,用于接受客户端的连接请求 28 | /// 29 | private Socket _serverSock; 30 | 31 | /// 32 | /// 当前的连接的客户端数 33 | /// 34 | private int _clientCount; 35 | 36 | /// 37 | /// 用于每个I/O Socket操作的缓冲区大小 38 | /// 39 | private int _bufferSize = 1024; 40 | 41 | /// 42 | /// 发送和接收超时 43 | /// 44 | private int _timeOut = 120; 45 | 46 | /// 47 | /// 队列排队超时 48 | /// 49 | private int _acceptTimeOut = 30 * 1000; 50 | 51 | /// 52 | /// 信号量 53 | /// 54 | private SemaphoreSlim _maxAcceptedClients; 55 | 56 | /// 57 | /// 缓冲区管理 58 | /// 59 | private BufferManager _bufferManager; 60 | 61 | /// 62 | /// 对象池 63 | /// 64 | private UserTokenPool _objectPool; 65 | 66 | /// 67 | /// 已连接Socket集合 68 | /// 69 | private ConcurrentDictionary _connectedSockets; 70 | 71 | /// 72 | /// 监听socket状态 73 | /// 74 | private int _state; 75 | 76 | #endregion 77 | 78 | #region Events 79 | 80 | private OnAcceptedHandler OnAccepted; 81 | 82 | public event OnServerErrorHandler OnAccpetErrored; 83 | 84 | protected void RaiseOnAccepted(int num, Socket socket) 85 | { 86 | OnAccepted?.BeginInvoke(this, num, socket, null, null); 87 | } 88 | 89 | protected void RaiseOnErrored(Exception ex, params object[] args) 90 | { 91 | OnAccpetErrored?.BeginInvoke(ex, args, null, null); 92 | } 93 | 94 | #endregion 95 | 96 | #region Properties 97 | 98 | /// 99 | /// 服务器是否正在运行 100 | /// 101 | public bool IsRunning { get; private set; } 102 | /// 103 | /// 监听的IP地址 104 | /// 105 | public IPAddress Address { get; private set; } 106 | /// 107 | /// 监听的端口 108 | /// 109 | public int Port { get; private set; } 110 | /// 111 | /// 通信使用的编码 112 | /// 113 | public Encoding Encoding { get; set; } 114 | 115 | public override int BufferSize 116 | { 117 | get 118 | { 119 | return _bufferSize; 120 | } 121 | 122 | protected set 123 | { 124 | _bufferSize = value; 125 | } 126 | } 127 | 128 | #endregion 129 | 130 | #region Ctors 131 | 132 | /// 133 | /// 异步IOCP SOCKET服务器 134 | /// 135 | /// 监听的端口 136 | /// 最大的客户端数量 137 | public Server(int listenPort, int maxClient) 138 | : this(IPAddress.Any, listenPort, maxClient, Encoding.UTF8) 139 | { 140 | } 141 | 142 | /// 143 | /// 异步Socket TCP服务器 144 | /// 145 | /// 监听的终结点 146 | /// 最大客户端数量 147 | public Server(IPEndPoint localEP, int maxClient) 148 | : this(localEP.Address, localEP.Port, maxClient, Encoding.UTF8) 149 | { 150 | } 151 | 152 | /// 153 | /// 异步Socket TCP服务器 154 | /// 155 | /// 监听的IP地址 156 | /// 监听的端口 157 | /// 最大客户端数量 158 | public Server(IPAddress localIPAddress, int listenPort, int maxClient, Encoding encoding, int timeOut = 120) 159 | { 160 | this.Address = localIPAddress; 161 | this.Port = listenPort; 162 | this.Encoding = encoding; 163 | 164 | _maxClient = maxClient; 165 | 166 | _serverSock = new Socket(localIPAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp); 167 | 168 | _bufferManager = new BufferManager(_bufferSize * _maxClient * opsToPreAlloc, _bufferSize); 169 | 170 | _objectPool = new UserTokenPool(); 171 | 172 | _maxAcceptedClients = new SemaphoreSlim(_maxClient, _maxClient); 173 | 174 | _connectedSockets = new ConcurrentDictionary(DefaultConcurrencyLevel, _maxClient); 175 | } 176 | 177 | #endregion 178 | 179 | #region Initialize 180 | 181 | private void Init() 182 | { 183 | // Allocates one large byte buffer which all I/O operations use a piece of. This gaurds 184 | // against memory fragmentation 185 | _bufferManager.InitBuffer(); 186 | 187 | // preallocate pool of SocketAsyncEventArgs objects 188 | SocketAsyncEventArgs readWriteEventArg; 189 | UserToken userToken; 190 | for (int i = 0; i < _maxClient; i++) 191 | { 192 | //Pre-allocate a set of reusable SocketAsyncEventArgs 193 | readWriteEventArg = new SocketAsyncEventArgs(); 194 | readWriteEventArg.Completed += new EventHandler(OnIOCompleted); 195 | 196 | // assign a byte buffer from the buffer pool to the SocketAsyncEventArg object 197 | _bufferManager.SetBuffer(readWriteEventArg); 198 | userToken = new UserToken(); 199 | userToken.ReceiveArgs = readWriteEventArg; 200 | readWriteEventArg.UserToken = userToken; 201 | // add SocketAsyncEventArg to the pool 202 | _objectPool.Push(userToken); 203 | } 204 | } 205 | 206 | #endregion 207 | 208 | #region Start 209 | 210 | public void Start(OnAcceptedHandler onAccepted) 211 | { 212 | if (!IsRunning) 213 | { 214 | if (onAccepted == null) 215 | throw new InvalidOperationException(); 216 | Init(); 217 | IsRunning = true; 218 | IPEndPoint localEndPoint = new IPEndPoint(Address, Port); 219 | //创建监听socket 220 | _serverSock = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); 221 | _serverSock.ReceiveTimeout = _timeOut; 222 | _serverSock.SendTimeout = _timeOut; 223 | _serverSock.ReceiveBufferSize = _bufferSize; 224 | if (localEndPoint.AddressFamily == AddressFamily.InterNetworkV6) 225 | { 226 | // 配置监听socket为 dual-mode (IPv4 & IPv6) 227 | // 27 is equivalent to IPV6_V6ONLY socket option in the winsock snippet below, 228 | _serverSock.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, false); 229 | _serverSock.Bind(new IPEndPoint(IPAddress.IPv6Any, localEndPoint.Port)); 230 | } 231 | else 232 | { 233 | _serverSock.Bind(localEndPoint); 234 | } 235 | this.OnAccepted = onAccepted; 236 | //开始监听 237 | _serverSock.Listen(this._maxClient); 238 | StartAccept(null); 239 | } 240 | } 241 | 242 | #endregion 243 | 244 | #region Stop 245 | 246 | public void Stop() 247 | { 248 | if (IsRunning) 249 | { 250 | IsRunning = false; 251 | if(_state==_accepting) 252 | { 253 | try 254 | { 255 | _maxAcceptedClients.Release(); 256 | } 257 | catch(SemaphoreFullException) 258 | { 259 | 260 | } 261 | } 262 | _serverSock.Close(); 263 | _objectPool.Clear(); 264 | SpinWait spinWait = default(SpinWait); 265 | do 266 | { 267 | spinWait.SpinOnce(); 268 | var sockets = _connectedSockets.Values.ToArray(); 269 | for(int i=0;i(OnAcceptCompleted); 290 | } 291 | else 292 | { 293 | //socket must be cleared since the context object is being reused 294 | asyniar.AcceptSocket = null; 295 | } 296 | Interlocked.Exchange(ref _state, _waitResource); 297 | if(!_maxAcceptedClients.Wait(_acceptTimeOut)) 298 | { 299 | RaiseOnErrored(new Exception(string.Format("获取可用连接资源超时,总资源数:{0}",_maxClient))); 300 | Task.Run(() => 301 | { 302 | StartAccept(asyniar); 303 | }); 304 | return; 305 | } 306 | Interlocked.Exchange(ref _state, _accepting); 307 | if (!_serverSock.AcceptAsync(asyniar)) 308 | { 309 | ProcessAccept(asyniar); 310 | } 311 | } 312 | 313 | private void OnAcceptCompleted(object sender, SocketAsyncEventArgs e) 314 | { 315 | ProcessAccept(e); 316 | } 317 | 318 | private void ProcessAccept(SocketAsyncEventArgs e) 319 | { 320 | Interlocked.Exchange(ref _state, _accepted); 321 | if (e.SocketError == SocketError.Success) 322 | { 323 | Socket s = e.AcceptSocket; 324 | if (s.Connected) 325 | { 326 | try 327 | { 328 | Interlocked.Increment(ref _clientCount); 329 | SetSocketOptions(s); 330 | _connectedSockets.TryAdd(s.Handle, s); 331 | #if DEBUG 332 | Log4Debug(String.Format("客户 {0} 连入, 共有 {1} 个连接。", s.RemoteEndPoint.ToString(), _clientCount)); 333 | #endif 334 | RaiseOnAccepted(_clientCount, s); 335 | } 336 | catch (SocketException ex) 337 | { 338 | #if DEBUG 339 | Log4Debug(String.Format("接收客户 {0} 数据出错, 异常信息: {1} 。", s.RemoteEndPoint, ex.ToString())); 340 | #endif 341 | RaiseOnErrored(new Exception(string.Format("接受客户 {0} 连接出错, 异常信息: {1} .", s.RemoteEndPoint, ex.ToString()))); 342 | } 343 | } 344 | } 345 | StartAccept(e); 346 | } 347 | #endregion 348 | 349 | #region Async Function 350 | 351 | #region Receive 352 | 353 | public override Task ReceiveAsync(Socket socket, int timeOut=-1) 354 | { 355 | if (socket == null || !socket.Connected) 356 | return Task.FromResult(SocketResult.NotSocket); 357 | var userToken = _objectPool.Pop(); 358 | userToken.ConnectSocket = socket; 359 | userToken.TimeOut = timeOut; 360 | return socket.ReceiveAsync(this, userToken); 361 | } 362 | 363 | #endregion 364 | 365 | #region Send 366 | 367 | public override Task SendAsync(Socket socket, byte[] data, int timeOut=-1) 368 | { 369 | if(socket == null || !socket.Connected) 370 | return Task.FromResult(SocketResult.NotSocket); 371 | var userToken = _objectPool.Pop(); 372 | #if DEBUG 373 | Console.WriteLine($"SendAsync Create a new UserToken : id: {userToken.Id}"); 374 | #endif 375 | userToken.ConnectSocket = socket; 376 | userToken.TimeOut = timeOut; 377 | return socket.SendAsync(data, this, userToken); 378 | } 379 | 380 | public override Task SendFileAsync(Socket socket, string fileName, int timeOut=-1) 381 | { 382 | if (socket == null || !socket.Connected) 383 | return Task.FromResult(SocketResult.NotSocket); 384 | var userToken = _objectPool.Pop(); 385 | userToken.ConnectSocket = socket; 386 | userToken.TimeOut = timeOut; 387 | return socket.SendFileAsync(fileName, this, userToken); 388 | } 389 | 390 | #endregion 391 | 392 | #region Disconnect 393 | public override Task DisconnectAsync(Socket socket, int timeOut = -1) 394 | { 395 | if (socket == null || !socket.Connected) 396 | return Task.FromResult(SocketResult.NotSocket); 397 | var userToken = _objectPool.Pop(); 398 | userToken.ConnectSocket = socket; 399 | userToken.TimeOut = timeOut; 400 | userToken.ReceiveArgs.SetBuffer(userToken.ReceiveArgs.Offset, 0); 401 | return socket.DisconnectAsync(this, userToken); 402 | } 403 | 404 | #endregion 405 | 406 | #endregion 407 | 408 | #region Close 409 | 410 | public override void CloseClientSocket(Socket s) 411 | { 412 | try 413 | { 414 | if (s.IsDisposed()) 415 | return; 416 | int r = Interlocked.Decrement(ref _clientCount); 417 | Socket temp; 418 | _connectedSockets.TryRemove(s.Handle, out temp); 419 | temp = null; 420 | #if DEBUG 421 | Log4Debug(String.Format("客户 {0} 断开连接! 剩余 {1} 个连接", s.RemoteEndPoint.ToString(), r)); 422 | #endif 423 | if (s.Connected) 424 | s.Shutdown(SocketShutdown.Both); 425 | _maxAcceptedClients.Release(); 426 | } 427 | catch(SemaphoreFullException) 428 | { 429 | 430 | } 431 | finally 432 | { 433 | s.Close(); 434 | } 435 | 436 | } 437 | 438 | protected override void RecycleToken(UserToken token) 439 | { 440 | _bufferManager.ResetBuffer(token); 441 | _objectPool.Reture(token); 442 | } 443 | #endregion 444 | 445 | #if DEBUG 446 | private void Log4Debug(string msg) 447 | { 448 | Console.WriteLine($"notice: {msg}"); 449 | } 450 | #endif 451 | #region IDisposable Support 452 | private bool disposedValue = false; // 要检测冗余调用 453 | 454 | protected virtual void Dispose(bool disposing) 455 | { 456 | if (!disposedValue) 457 | { 458 | if (disposing) 459 | { 460 | try 461 | { 462 | Stop(); 463 | if(_serverSock!=null) 464 | { 465 | _serverSock = null; 466 | } 467 | } 468 | catch 469 | { 470 | 471 | } 472 | } 473 | disposedValue = true; 474 | } 475 | } 476 | public void Dispose() 477 | { 478 | Dispose(true); 479 | GC.SuppressFinalize(this); 480 | } 481 | #endregion 482 | 483 | } 484 | } 485 | --------------------------------------------------------------------------------