├── 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 |
--------------------------------------------------------------------------------