Преглед изворни кода

完成TcpAcceptor单元测试,发现C#的socket accept回调可能会回调到其它线程
这意味着如果逻辑单线程,网络层还需要把数据push到逻辑线程

tanghai пре 12 година
родитељ
комит
2f92bd96b8

+ 15 - 0
CSharp/CSharp.sln

@@ -70,6 +70,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TNet", "Platform\TNet\TNet.
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Logger", "Platform\Logger\Logger.csproj", "{72E16572-FC1F-4A9E-BC96-035417239298}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TNetTest", "Platform\TNetTest\TNetTest.csproj", "{20D53127-D24C-4E52-9423-7DFC9FDD6DC1}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -374,6 +376,18 @@ Global
 		{72E16572-FC1F-4A9E-BC96-035417239298}.Release|Mixed Platforms.Build.0 = Release|Any CPU
 		{72E16572-FC1F-4A9E-BC96-035417239298}.Release|Win32.ActiveCfg = Release|Any CPU
 		{72E16572-FC1F-4A9E-BC96-035417239298}.Release|x86.ActiveCfg = Release|Any CPU
+		{20D53127-D24C-4E52-9423-7DFC9FDD6DC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{20D53127-D24C-4E52-9423-7DFC9FDD6DC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{20D53127-D24C-4E52-9423-7DFC9FDD6DC1}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+		{20D53127-D24C-4E52-9423-7DFC9FDD6DC1}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+		{20D53127-D24C-4E52-9423-7DFC9FDD6DC1}.Debug|Win32.ActiveCfg = Debug|Any CPU
+		{20D53127-D24C-4E52-9423-7DFC9FDD6DC1}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{20D53127-D24C-4E52-9423-7DFC9FDD6DC1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{20D53127-D24C-4E52-9423-7DFC9FDD6DC1}.Release|Any CPU.Build.0 = Release|Any CPU
+		{20D53127-D24C-4E52-9423-7DFC9FDD6DC1}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+		{20D53127-D24C-4E52-9423-7DFC9FDD6DC1}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+		{20D53127-D24C-4E52-9423-7DFC9FDD6DC1}.Release|Win32.ActiveCfg = Release|Any CPU
+		{20D53127-D24C-4E52-9423-7DFC9FDD6DC1}.Release|x86.ActiveCfg = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -393,6 +407,7 @@ Global
 		{CBA52DC8-1C80-4A79-9AC5-73514EBBD749} = {ADBF5F67-B480-4A93-9D50-C81856FC61A9}
 		{F74367BF-FEFE-45F9-8303-94414D128B0B} = {ADBF5F67-B480-4A93-9D50-C81856FC61A9}
 		{72E16572-FC1F-4A9E-BC96-035417239298} = {ADBF5F67-B480-4A93-9D50-C81856FC61A9}
+		{20D53127-D24C-4E52-9423-7DFC9FDD6DC1} = {ADBF5F67-B480-4A93-9D50-C81856FC61A9}
 		{5D6ECBCD-BE14-4DCB-BAEC-57089748B164} = {C4C64188-4FAE-4CC3-A9E6-D9D4AF7429B6}
 		{5AA48F9A-455D-4CD8-A605-A3AC38283E60} = {C4C64188-4FAE-4CC3-A9E6-D9D4AF7429B6}
 		{B07D70E7-F902-4F67-A486-B7AF27D6B813} = {C4C64188-4FAE-4CC3-A9E6-D9D4AF7429B6}

+ 6 - 0
CSharp/Platform/TNet/TNet.csproj

@@ -42,6 +42,12 @@
     <Compile Include="TcpAcceptor.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
   </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\Logger\Logger.csproj">
+      <Project>{72e16572-fc1f-4a9e-bc96-035417239298}</Project>
+      <Name>Logger</Name>
+    </ProjectReference>
+  </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.

+ 34 - 20
CSharp/Platform/TNet/TcpAcceptor.cs

@@ -1,27 +1,37 @@
 using System;
 using System.Net;
 using System.Net.Sockets;
-using System.Threading.Tasks;
-
+using System.Threading.Tasks;
+
 namespace TNet
 {
-    public class TcpAcceptor
+    public class TcpAcceptor: IDisposable
     {
 	    private readonly Socket socket;
 		private readonly SocketAsyncEventArgs asyncEventArgs = new SocketAsyncEventArgs();
 
-	    public TcpAcceptor(Socket socket, EndPoint endPoint)
-	    {
-		    this.socket = socket;
+	    public TcpAcceptor(ushort port, int backLog = 100)
+	    {
+			this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
 			this.asyncEventArgs.Completed += OnArgsCompletion;
-			this.socket.Bind(endPoint);
-	    }
+			this.socket.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), port));
+			this.socket.Listen(backLog);
+	    }
+
+		public void Dispose()
+		{
+			this.socket.Dispose();
+		}
 
+		/// <summary>
+		/// SocketAsyncEventArgs Accept回调函数,回调可能已经到了另外的线程
+		/// </summary>
+		/// <returns></returns>
 	    public Task<NetworkStream> AcceptAsync()
 	    {
 			var tcs = new TaskCompletionSource<NetworkStream>();
 		    
-			try
+			try
 			{
 				asyncEventArgs.UserToken = tcs;
 
@@ -29,9 +39,11 @@ namespace TNet
 
 				if (!ret)
 				{
-					if (asyncEventArgs.SocketError == SocketError.Success)
-					{
-						return Task.FromResult(new NetworkStream(asyncEventArgs.AcceptSocket, true));
+					if (asyncEventArgs.SocketError == SocketError.Success)
+					{
+						var acceptSocket = asyncEventArgs.AcceptSocket;
+						asyncEventArgs.AcceptSocket = null;
+						return Task.FromResult(new NetworkStream(acceptSocket, true));
 					}
 					tcs.TrySetException(new InvalidOperationException(this.asyncEventArgs.SocketError.ToString()));
 				}
@@ -45,14 +57,16 @@ namespace TNet
 
 	    private void OnArgsCompletion(object sender, SocketAsyncEventArgs e)
 	    {
-			var tcs = (TaskCompletionSource<NetworkStream>)e.UserToken;
-
-			if (asyncEventArgs.SocketError != SocketError.Success)
-			{
-				tcs.TrySetException(new InvalidOperationException(asyncEventArgs.SocketError.ToString()));
+			var tcs = (TaskCompletionSource<NetworkStream>)e.UserToken;
+
+			if (e.SocketError != SocketError.Success)
+			{
+				tcs.TrySetException(new InvalidOperationException(e.SocketError.ToString()));
 				return;
-			}
-			tcs.SetResult(new NetworkStream(e.AcceptSocket, true));
-	    }
+			}
+			var acceptSocket = e.AcceptSocket;
+			e.AcceptSocket = null;
+			tcs.SetResult(new NetworkStream(acceptSocket, true));
+	    }
     }
 }

+ 36 - 0
CSharp/Platform/TNetTest/Properties/AssemblyInfo.cs

@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// 有关程序集的常规信息通过以下特性集 
+// 控制。更改这些特性值可修改
+// 与程序集关联的信息。
+[assembly: AssemblyTitle("TNetTest")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("TNetTest")]
+[assembly: AssemblyCopyright("Copyright ©  2014")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// 将 ComVisible 设置为 false 会使此程序集中的类型 
+// 对 COM 组件不可见。  如果需要从 COM 访问此程序集中的类型,
+// 请将该类型上的 ComVisible 特性设置为 true。
+[assembly: ComVisible(false)]
+
+// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
+[assembly: Guid("84b92d54-68e0-4fe3-ab5c-dc830a422f63")]
+
+// 程序集的版本信息由以下四个值组成: 
+//
+//      主版本
+//      次版本
+//      生成号
+//      修订号
+//
+// 可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值,
+// 方法是按如下所示使用“*”: 
+// [assembly:  AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

+ 97 - 0
CSharp/Platform/TNetTest/TNetTest.csproj

@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{20D53127-D24C-4E52-9423-7DFC9FDD6DC1}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>TNetTest</RootNamespace>
+    <AssemblyName>TNetTest</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
+    <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
+    <ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
+    <IsCodedUITest>False</IsCodedUITest>
+    <TestProjectType>UnitTest</TestProjectType>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>..\..\Bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>..\..\Bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+  </ItemGroup>
+  <Choose>
+    <When Condition="('$(VisualStudioVersion)' == '10.0' or '$(VisualStudioVersion)' == '') and '$(TargetFrameworkVersion)' == 'v3.5'">
+      <ItemGroup>
+        <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
+      </ItemGroup>
+    </When>
+    <Otherwise>
+      <ItemGroup>
+        <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework" />
+      </ItemGroup>
+    </Otherwise>
+  </Choose>
+  <ItemGroup>
+    <Compile Include="TcpAcceptorTest.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\Helper\Helper.csproj">
+      <Project>{24233cd5-a5df-484b-a482-b79cb7a0d9cb}</Project>
+      <Name>Helper</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\Logger\Logger.csproj">
+      <Project>{72e16572-fc1f-4a9e-bc96-035417239298}</Project>
+      <Name>Logger</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\TNet\TNet.csproj">
+      <Project>{f74367bf-fefe-45f9-8303-94414d128b0b}</Project>
+      <Name>TNet</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <Choose>
+    <When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
+      <ItemGroup>
+        <Reference Include="Microsoft.VisualStudio.QualityTools.CodedUITestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <Private>False</Private>
+        </Reference>
+        <Reference Include="Microsoft.VisualStudio.TestTools.UITest.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <Private>False</Private>
+        </Reference>
+        <Reference Include="Microsoft.VisualStudio.TestTools.UITest.Extension, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <Private>False</Private>
+        </Reference>
+        <Reference Include="Microsoft.VisualStudio.TestTools.UITesting, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+          <Private>False</Private>
+        </Reference>
+      </ItemGroup>
+    </When>
+  </Choose>
+  <Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>

+ 90 - 0
CSharp/Platform/TNetTest/TcpAcceptorTest.cs

@@ -0,0 +1,90 @@
+using System;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+using Helper;
+using Logger;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using TNet;
+
+namespace TNetTest
+{
+	[TestClass]
+	public class TcpAcceptorTest
+	{
+		private const ushort port = 11111;
+		private int count;
+		private readonly Barrier barrier = new Barrier(100);
+		private readonly object lockObject = new object();
+
+		[TestMethod]
+		public void AcceptAsync()
+		{
+			var thread1 = new Thread(Server);
+			thread1.Start();
+
+			Thread.Sleep(2);
+
+			for (int i = 0; i < 99; ++i)
+			{
+				var thread = new Thread(this.Client);
+				thread.Start();
+			}
+			barrier.SignalAndWait();
+		}
+
+		private async void Client()
+		{
+			using (var tcpClient = new TcpClient(AddressFamily.InterNetwork))
+			{
+				await tcpClient.ConnectAsync("127.0.0.1", port);
+				using (NetworkStream ns = tcpClient.GetStream())
+				{
+					try
+					{
+						var bytes = "tanghai".ToByteArray();
+						await ns.WriteAsync(bytes, 0, bytes.Length);
+						int n = await ns.ReadAsync(bytes, 0, bytes.Length);
+						Assert.AreEqual(7, n);
+					}
+					catch (Exception e)
+					{
+						Log.Debug(e.ToString());
+					}
+				}
+			}
+			barrier.RemoveParticipants(1);
+		}
+
+		private async void Server()
+		{
+			using (var tcpAcceptor = new TcpAcceptor(port))
+			{
+				while (count != 99)
+				{
+					NetworkStream ns = await tcpAcceptor.AcceptAsync();
+					// 这里可能已经不在Server函数线程了
+					Response(ns);
+				}
+			}
+		}
+
+		private async void Response(NetworkStream ns)
+		{
+			try
+			{
+				var bytes = new byte[1000];
+				int n = await ns.ReadAsync(bytes, 0, 100);
+				await ns.WriteAsync(bytes, 0, n);
+				lock (lockObject)
+				{
+					++count;
+				}
+			}
+			catch (Exception e)
+			{
+				Log.Debug(e.ToString());
+			}
+		}
+	}
+}