diff options
29 files changed, 1504 insertions, 96 deletions
diff --git a/Documents/资料.xlsx b/Documents/资料.xlsx Binary files differindex 94d5672..f390a9a 100644 --- a/Documents/资料.xlsx +++ b/Documents/资料.xlsx diff --git a/MultiplayerToolkit/Assets/MultiplayerToolkit/Message.meta b/MultiplayerToolkit/Assets/MultiplayerToolkit/Message.meta new file mode 100644 index 0000000..b3bcd16 --- /dev/null +++ b/MultiplayerToolkit/Assets/MultiplayerToolkit/Message.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3227b22007e540546967afc661e59c92 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/MultiplayerToolkit/Assets/MultiplayerToolkit/IRecyclable.cs b/MultiplayerToolkit/Assets/MultiplayerToolkit/Message/IRecyclable.cs index 17d3104..17d3104 100644 --- a/MultiplayerToolkit/Assets/MultiplayerToolkit/IRecyclable.cs +++ b/MultiplayerToolkit/Assets/MultiplayerToolkit/Message/IRecyclable.cs diff --git a/MultiplayerToolkit/Assets/MultiplayerToolkit/MessageReader.cs b/MultiplayerToolkit/Assets/MultiplayerToolkit/Message/MessageReader.cs index 3bd5db8..7ec134b 100644 --- a/MultiplayerToolkit/Assets/MultiplayerToolkit/MessageReader.cs +++ b/MultiplayerToolkit/Assets/MultiplayerToolkit/Message/MessageReader.cs @@ -6,31 +6,37 @@ using System.Text; namespace MultiplayerToolkit { + /// <summary> + /// + /// </summary> public class MessageReader : IRecyclable { public static readonly ObjectPool<MessageReader> ReaderPool = new ObjectPool<MessageReader>(() => new MessageReader()); - public byte[] Buffer; - public byte Tag; + public byte[] Buffer; // √ 缓冲区,会被子协议共享这个缓冲区 + public byte Tag; // √ 协议名 - public int Length; + public int Length; // √ 协议长度,对应的是MessageWriter里写入的2bytes包长度(不含length和tag) + public int Offset; // √ length和tag后面的位置(在Buffer中的位置),不是游标,不会随读取发生改变。会影响readHead的值 + + //数据位于Buffer中Offset索引开始Length长度,协议位于Buffer中Offset-3开始的Length+3长度 public int BytesRemaining => this.Length - this.Position; - private MessageReader Parent; + private MessageReader Parent; // 保存父协议索引,和父协议共享父协议的Buffer public int Position { get { return this._position; } set { - this._position = value; - this.readHead = value ; + this._position = value; // value是 + this.readHead = value + Offset; } } - private int _position; - private int readHead; + private int _position; // √ 读取游标,相对于协议内容部分的偏移,从0开始,子协议也是从0开始,不是在Buffer中的索引 + private int readHead; // √ 读取游标,协议头部在Buffer中的位置,参考ReadMessage() public static MessageReader GetSized(int minSize) { @@ -64,9 +70,14 @@ namespace MultiplayerToolkit return output; } + /// <summary> + /// 创建一个新的父message,保存source。用于缓存message,因为source所在的原父message可能会被销毁,如果需要延迟处理消息,需要拷贝一份新的 + /// </summary> + /// <param name="source"></param> + /// <returns></returns> public static MessageReader CopyMessageIntoParent(MessageReader source) { - var output = MessageReader.GetSized(source.Length + 3); + var output = MessageReader.GetSized(source.Length + 3); //3=2bytes(length) + 1byte(tag) System.Buffer.BlockCopy(source.Buffer, source.Offset - 3, output.Buffer, 0, source.Length + 3); output.Offset = 0; @@ -77,7 +88,7 @@ namespace MultiplayerToolkit } /// <summary> - /// 复制一份 + /// 完全复制 一份 /// </summary> /// <param name="source"></param> /// <returns></returns> @@ -108,7 +119,7 @@ namespace MultiplayerToolkit output.Offset = offset; output.Position = 0; - output.Length = output.ReadUInt16(); + output.Length = output.ReadUInt16(); // 读到的值为length output.Tag = output.ReadByte(); output.Offset += 3; @@ -125,22 +136,24 @@ namespace MultiplayerToolkit // Ensure there is at least a header if (this.BytesRemaining < 3) throw new InvalidDataException($"ReadMessage header is longer than message length: 3 of {this.BytesRemaining}"); - var output = new MessageReader(); + MessageReader output = new MessageReader(); output.Parent = this; output.Buffer = this.Buffer; - output.Offset = this.readHead; + output.Offset = this.readHead; // 下面会读取 output.Position = 0; + // 读length和tag的值并保存在字段里 output.Length = output.ReadUInt16(); output.Tag = output.ReadByte(); - output.Offset += 3; + // Offset, readHead齐步走,移到Length和Tag后面,_position=0 + output.Offset += 3; // 3=length+tag output.Position = 0; if (this.BytesRemaining < output.Length + 3) throw new InvalidDataException($"Message Length at Position {this.readHead} is longer than message length: {output.Length + 3} of {this.BytesRemaining}"); - this.Position += output.Length + 3; + this.Position += output.Length + 3; //跳过整个子协议 return output; } @@ -205,41 +218,41 @@ namespace MultiplayerToolkit } } - public void InsertMessage(MessageReader reader, MessageWriter writer) - { - var temp = MessageReader.GetSized(reader.Buffer.Length); - try - { - var headerOffset = reader.Offset - 3; - var startOfMessage = reader.Offset; - var len = reader.Buffer.Length - startOfMessage; - int writerOffset = 0; - //switch (writer.SendOption) - //{ - // case SendOption.Reliable: - // writerOffset = 3; - // break; - // case SendOption.None: - // writerOffset = 1; - // break; - //} - - //store the original buffer in temp - Array.Copy(reader.Buffer, headerOffset, temp.Buffer, 0, len); - - //put the contents of writer in at headerOffset - Array.Copy(writer.Buffer, writerOffset, this.Buffer, headerOffset, writer.Length-writerOffset); - - //put the original buffer in after that - Array.Copy(temp.Buffer, 0, this.Buffer, headerOffset + (writer.Length-writerOffset), len - writer.Length); - - this.AdjustLength(-1 * reader.Offset , -1 * (writer.Length - writerOffset)); - } - finally - { - temp.Recycle(); - } - } + //public void InsertMessage(MessageReader reader, MessageWriter writer) + //{ + // var temp = MessageReader.GetSized(reader.Buffer.Length); + // try + // { + // var headerOffset = reader.Offset - 3; + // var startOfMessage = reader.Offset; + // var len = reader.Buffer.Length - startOfMessage; + // int writerOffset = 3; + // switch (writer.SendOption) + // { + // case SendOption.Reliable: + // writerOffset = 3; + // break; + // case SendOption.None: + // writerOffset = 1; + // break; + // } + + // //store the original buffer in temp + // Array.Copy(reader.Buffer, headerOffset, temp.Buffer, 0, len); + + // //put the contents of writer in at headerOffset + // Array.Copy(writer.Buffer, writerOffset, this.Buffer, headerOffset, writer.Length - writerOffset); + + // //put the original buffer in after that + // Array.Copy(temp.Buffer, 0, this.Buffer, headerOffset + (writer.Length - writerOffset), len - writer.Length); + + // this.AdjustLength(-1 * reader.Offset, -1 * (writer.Length - writerOffset)); + // } + // finally + // { + // temp.Recycle(); + // } + //} private void AdjustLength(int offset, int amount) { diff --git a/MultiplayerToolkit/Assets/MultiplayerToolkit/MessageReader.cs.meta b/MultiplayerToolkit/Assets/MultiplayerToolkit/Message/MessageReader.cs.meta index 2f470c6..2f470c6 100644 --- a/MultiplayerToolkit/Assets/MultiplayerToolkit/MessageReader.cs.meta +++ b/MultiplayerToolkit/Assets/MultiplayerToolkit/Message/MessageReader.cs.meta diff --git a/MultiplayerToolkit/Assets/MultiplayerToolkit/MessageWriter.cs b/MultiplayerToolkit/Assets/MultiplayerToolkit/Message/MessageWriter.cs index 226a2d4..b23ce82 100644 --- a/MultiplayerToolkit/Assets/MultiplayerToolkit/MessageWriter.cs +++ b/MultiplayerToolkit/Assets/MultiplayerToolkit/Message/MessageWriter.cs @@ -10,19 +10,26 @@ namespace MultiplayerToolkit /// AmongUsùȥ˰ͷش /// ṹ /// ------------------------------------- - /// 2bytes(ushort) Эij - /// Э + /// 2bytes (ushort)Эijȣ2bytes + /// ------------------------------------- + /// 1byte ЭͣЭ0ԶЭ1 + /// (1byte) (ԶЭ飬ṩһmodţSteam.UGC.Itemidulong 8bytesеţ֧255mod) + /// 2byte (ushort)ЭID + /// /// ------------------------------------- /// </summary> public class MessageWriter : IRecyclable { - public static int BufferSize = 64000; + public const int BYTE_COUNT_OF_LENGTH = 2; // ռ2 bytes + public static int BufferSize = 64000; // Э֧64000ֽڣ62.5KB public static readonly ObjectPool<MessageWriter> WriterPool = new ObjectPool<MessageWriter>(() => new MessageWriter(BufferSize)); - + public byte[] Buffer; - public int Length; // Эܳ + public int Length; // ЧbufferеijȣܰǶЭ public int Position; // дλ + // lengthһڻĴСLengthһÿЭЭݲֵֽ + private Stack<int> messageStarts = new Stack<int>(); // ¼ЭbufferеʼλãǶṹ public MessageWriter(byte[] buffer) @@ -53,27 +60,31 @@ namespace MultiplayerToolkit return output; } + /// <summary> + /// Ƿexpectedȵ + /// </summary> + /// <param name="expected"></param> + /// <returns></returns> public bool HasBytes(int expected) { - return this.Length >= 3 + expected; + return this.Length >= expected; } /// - public void StartMessage(/*byte typeFlag*/) + public void StartMessage() { var messageStart = this.Position; messageStarts.Push(messageStart); this.Buffer[messageStart] = 0; // length this.Buffer[messageStart + 1] = 0; // length this.Position += 2; // ushort length - //this.Write(typeFlag); } /// public void EndMessage() { var lastMessageStart = messageStarts.Pop(); - ushort length = (ushort)(this.Position - lastMessageStart - /*3*/2); // Minus length and type byte + ushort length = (ushort)(this.Position - lastMessageStart - 2); // Minus length this.Buffer[lastMessageStart] = (byte)length; this.Buffer[lastMessageStart + 1] = (byte)(length >> 8); } diff --git a/MultiplayerToolkit/Assets/MultiplayerToolkit/MessageWriter.cs.meta b/MultiplayerToolkit/Assets/MultiplayerToolkit/Message/MessageWriter.cs.meta index ec9023e..ec9023e 100644 --- a/MultiplayerToolkit/Assets/MultiplayerToolkit/MessageWriter.cs.meta +++ b/MultiplayerToolkit/Assets/MultiplayerToolkit/Message/MessageWriter.cs.meta diff --git a/MultiplayerToolkit/Assets/MultiplayerToolkit/ObjectPool.cs b/MultiplayerToolkit/Assets/MultiplayerToolkit/Message/ObjectPool.cs index e42cdd6..e42cdd6 100644 --- a/MultiplayerToolkit/Assets/MultiplayerToolkit/ObjectPool.cs +++ b/MultiplayerToolkit/Assets/MultiplayerToolkit/Message/ObjectPool.cs diff --git a/MultiplayerToolkit/Assets/MultiplayerToolkit/Test/UnitTests.meta b/MultiplayerToolkit/Assets/MultiplayerToolkit/Test/UnitTests.meta new file mode 100644 index 0000000..e1988f5 --- /dev/null +++ b/MultiplayerToolkit/Assets/MultiplayerToolkit/Test/UnitTests.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1bb97683c50825f4499202eb0d241254 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/MultiplayerToolkit/Assets/MultiplayerToolkit/Test/UnitTests/MessageUnitTests.cs b/MultiplayerToolkit/Assets/MultiplayerToolkit/Test/UnitTests/MessageUnitTests.cs new file mode 100644 index 0000000..ca028cf --- /dev/null +++ b/MultiplayerToolkit/Assets/MultiplayerToolkit/Test/UnitTests/MessageUnitTests.cs @@ -0,0 +1,11 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + + +public class MessageUnitTests +{ + + + +}
\ No newline at end of file diff --git a/MultiplayerToolkit/Assets/MultiplayerToolkit/Test/UnitTests/MessageUnitTests.cs.meta b/MultiplayerToolkit/Assets/MultiplayerToolkit/Test/UnitTests/MessageUnitTests.cs.meta new file mode 100644 index 0000000..a5ad157 --- /dev/null +++ b/MultiplayerToolkit/Assets/MultiplayerToolkit/Test/UnitTests/MessageUnitTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6c70f76fcb12ced489b49c7ac64eda2d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Projects/Message/Message/.gitignore b/Projects/Message/Message/.gitignore new file mode 100644 index 0000000..9bf6cfb --- /dev/null +++ b/Projects/Message/Message/.gitignore @@ -0,0 +1,194 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates +.vs/ + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +x64/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +#Sandcastle generated documentation +[Hh]elp/ + +# Roslyn cache directories +*.ide/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +#NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding addin-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +## TODO: Comment the next line if you want to checkin your +## web deploy settings but do note that will include unencrypted +## passwords +#*.pubxml + +# NuGet Packages Directory +packages/* +## TODO: If the tool you use requires repositories.config +## uncomment the next line +#!packages/repositories.config + +# Enable "build/" folder in the NuGet Packages folder since +# NuGet packages use it for MSBuild targets. +# This line needs to be after the ignore of the build folder +# (and the packages folder if the line above has been uncommented) +!packages/build/ + +# Windows Azure Build Output +csx/ +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# LightSwitch generated files +GeneratedArtifacts/ +_Pvt_Extensions/ +ModelManifest.xml +*.ide diff --git a/Projects/Message/Message/IRecyclable.cs b/Projects/Message/Message/IRecyclable.cs new file mode 100644 index 0000000..17d3104 --- /dev/null +++ b/Projects/Message/Message/IRecyclable.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MultiplayerToolkit +{ + /// <summary> + /// Interface for all items that can be returned to an object pool. + /// </summary> + /// <threadsafety static="true" instance="true"/> + public interface IRecyclable + { + /// <summary> + /// Returns this object back to the object pool. + /// </summary> + /// <remarks> + /// <para> + /// Calling this when you are done with the object returns the object back to a pool in order to be reused. + /// This can reduce the amount of work the GC has to do dramatically but it is optional to call this. + /// </para> + /// <para> + /// Calling this indicates to Hazel that this can be reused and thus you should only call this when you are + /// completely finished with the object as the contents can be overwritten at any point after. + /// </para> + /// </remarks> + void Recycle(); + } +} diff --git a/Projects/Message/Message/Message.csproj b/Projects/Message/Message/Message.csproj new file mode 100644 index 0000000..f2f13d5 --- /dev/null +++ b/Projects/Message/Message/Message.csproj @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="packages\MSTest.TestAdapter.2.2.7\build\net45\MSTest.TestAdapter.props" Condition="Exists('packages\MSTest.TestAdapter.2.2.7\build\net45\MSTest.TestAdapter.props')" /> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{AADABFAD-53FB-43DA-BAC8-83D11CA36DEC}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>Message</RootNamespace> + <AssemblyName>Message</AssemblyName> + <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> + <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">15.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> + <NuGetPackageImportStamp> + </NuGetPackageImportStamp> + </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> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + </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> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + </PropertyGroup> + <ItemGroup> + <Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>packages\MSTest.TestFramework.2.2.7\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll</HintPath> + </Reference> + <Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <HintPath>packages\MSTest.TestFramework.2.2.7\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath> + </Reference> + <Reference Include="System" /> + <Reference Include="System.Core" /> + </ItemGroup> + <ItemGroup> + <Compile Include="IRecyclable.cs" /> + <Compile Include="MessageReader.cs" /> + <Compile Include="MessageWriter.cs" /> + <Compile Include="ObjectPool.cs" /> + <Compile Include="UnitTest1.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="packages.config" /> + </ItemGroup> + <Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" /> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> + <PropertyGroup> + <ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText> + </PropertyGroup> + <Error Condition="!Exists('packages\MSTest.TestAdapter.2.2.7\build\net45\MSTest.TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\MSTest.TestAdapter.2.2.7\build\net45\MSTest.TestAdapter.props'))" /> + <Error Condition="!Exists('packages\MSTest.TestAdapter.2.2.7\build\net45\MSTest.TestAdapter.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\MSTest.TestAdapter.2.2.7\build\net45\MSTest.TestAdapter.targets'))" /> + </Target> + <Import Project="packages\MSTest.TestAdapter.2.2.7\build\net45\MSTest.TestAdapter.targets" Condition="Exists('packages\MSTest.TestAdapter.2.2.7\build\net45\MSTest.TestAdapter.targets')" /> +</Project>
\ No newline at end of file diff --git a/Projects/Message/Message/Message.lutconfig b/Projects/Message/Message/Message.lutconfig new file mode 100644 index 0000000..596a860 --- /dev/null +++ b/Projects/Message/Message/Message.lutconfig @@ -0,0 +1,6 @@ +<LUTConfig Version="1.0"> + <Repository /> + <ParallelBuilds>true</ParallelBuilds> + <ParallelTestRuns>true</ParallelTestRuns> + <TestCaseTimeout>180000</TestCaseTimeout> +</LUTConfig>
\ No newline at end of file diff --git a/Projects/Message/Message/Message.sln b/Projects/Message/Message/Message.sln new file mode 100644 index 0000000..3381718 --- /dev/null +++ b/Projects/Message/Message/Message.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.33213.308 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Message", "Message.csproj", "{AADABFAD-53FB-43DA-BAC8-83D11CA36DEC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AADABFAD-53FB-43DA-BAC8-83D11CA36DEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AADABFAD-53FB-43DA-BAC8-83D11CA36DEC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AADABFAD-53FB-43DA-BAC8-83D11CA36DEC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AADABFAD-53FB-43DA-BAC8-83D11CA36DEC}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E601FAC4-CE34-4006-A518-979962273BFB} + EndGlobalSection +EndGlobal diff --git a/Projects/Message/Message/MessageReader.cs b/Projects/Message/Message/MessageReader.cs new file mode 100644 index 0000000..7ec134b --- /dev/null +++ b/Projects/Message/Message/MessageReader.cs @@ -0,0 +1,469 @@ +using System; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; + +namespace MultiplayerToolkit +{ + /// <summary> + /// + /// </summary> + public class MessageReader : IRecyclable + { + public static readonly ObjectPool<MessageReader> ReaderPool = new ObjectPool<MessageReader>(() => new MessageReader()); + + public byte[] Buffer; // √ 缓冲区,会被子协议共享这个缓冲区 + public byte Tag; // √ 协议名 + + public int Length; // √ 协议长度,对应的是MessageWriter里写入的2bytes包长度(不含length和tag) + public int Offset; // √ length和tag后面的位置(在Buffer中的位置),不是游标,不会随读取发生改变。会影响readHead的值 + + //数据位于Buffer中Offset索引开始Length长度,协议位于Buffer中Offset-3开始的Length+3长度 + + public int BytesRemaining => this.Length - this.Position; + + private MessageReader Parent; // 保存父协议索引,和父协议共享父协议的Buffer + + public int Position + { + get { return this._position; } + set + { + this._position = value; // value是 + this.readHead = value + Offset; + } + } + + private int _position; // √ 读取游标,相对于协议内容部分的偏移,从0开始,子协议也是从0开始,不是在Buffer中的索引 + private int readHead; // √ 读取游标,协议头部在Buffer中的位置,参考ReadMessage() + + public static MessageReader GetSized(int minSize) + { + var output = ReaderPool.GetObject(); + + if (output.Buffer == null || output.Buffer.Length < minSize) + { + output.Buffer = new byte[minSize]; + } + else + { + Array.Clear(output.Buffer, 0, output.Buffer.Length); + } + + output.Offset = 0; + output.Position = 0; + output.Tag = byte.MaxValue; + return output; + } + + public static MessageReader Get(byte[] buffer) + { + var output = ReaderPool.GetObject(); + + output.Buffer = buffer; + output.Offset = 0; + output.Position = 0; + output.Length = buffer.Length; + output.Tag = byte.MaxValue; + + return output; + } + + /// <summary> + /// 创建一个新的父message,保存source。用于缓存message,因为source所在的原父message可能会被销毁,如果需要延迟处理消息,需要拷贝一份新的 + /// </summary> + /// <param name="source"></param> + /// <returns></returns> + public static MessageReader CopyMessageIntoParent(MessageReader source) + { + var output = MessageReader.GetSized(source.Length + 3); //3=2bytes(length) + 1byte(tag) + System.Buffer.BlockCopy(source.Buffer, source.Offset - 3, output.Buffer, 0, source.Length + 3); + + output.Offset = 0; + output.Position = 0; + output.Length = source.Length + 3; + + return output; + } + + /// <summary> + /// 完全复制 一份 + /// </summary> + /// <param name="source"></param> + /// <returns></returns> + public static MessageReader Get(MessageReader source) + { + var output = MessageReader.GetSized(source.Buffer.Length); + System.Buffer.BlockCopy(source.Buffer, 0, output.Buffer, 0, source.Buffer.Length); + + output.Offset = source.Offset; + + output._position = source._position; + output.readHead = source.readHead; + + output.Length = source.Length; + output.Tag = source.Tag; + + return output; + } + + public static MessageReader Get(byte[] buffer, int offset) + { + // Ensure there is at least a header + if (offset + 3 > buffer.Length) return null; + + var output = ReaderPool.GetObject(); + + output.Buffer = buffer; + output.Offset = offset; + output.Position = 0; + + output.Length = output.ReadUInt16(); // 读到的值为length + output.Tag = output.ReadByte(); + + output.Offset += 3; + output.Position = 0; + + return output; + } + + /// <summary> + /// Produces a MessageReader using the parent's buffer. This MessageReader should **NOT** be recycled. + /// </summary> + public MessageReader ReadMessage() + { + // Ensure there is at least a header + if (this.BytesRemaining < 3) throw new InvalidDataException($"ReadMessage header is longer than message length: 3 of {this.BytesRemaining}"); + + MessageReader output = new MessageReader(); + + output.Parent = this; + output.Buffer = this.Buffer; + output.Offset = this.readHead; // 下面会读取 + output.Position = 0; + + // 读length和tag的值并保存在字段里 + output.Length = output.ReadUInt16(); + output.Tag = output.ReadByte(); + + // Offset, readHead齐步走,移到Length和Tag后面,_position=0 + output.Offset += 3; // 3=length+tag + output.Position = 0; + + if (this.BytesRemaining < output.Length + 3) throw new InvalidDataException($"Message Length at Position {this.readHead} is longer than message length: {output.Length + 3} of {this.BytesRemaining}"); + + this.Position += output.Length + 3; //跳过整个子协议 + return output; + } + + /// <summary> + /// Produces a MessageReader with a new buffer. This MessageReader should be recycled. + /// </summary> + public MessageReader ReadMessageAsNewBuffer() + { + if (this.BytesRemaining < 3) throw new InvalidDataException($"ReadMessage header is longer than message length: 3 of {this.BytesRemaining}"); + + var len = this.ReadUInt16(); + var tag = this.ReadByte(); + + if (this.BytesRemaining < len) throw new InvalidDataException($"Message Length at Position {this.readHead} is longer than message length: {len} of {this.BytesRemaining}"); + + var output = MessageReader.GetSized(len); + + Array.Copy(this.Buffer, this.readHead, output.Buffer, 0, len); + + output.Length = len; + output.Tag = tag; + + this.Position += output.Length; + return output; + } + + public MessageWriter StartWriter() + { + var output = new MessageWriter(this.Buffer); + output.Position = this.readHead; + return output; + } + + public MessageReader Duplicate() + { + var output = GetSized(this.Length); + Array.Copy(this.Buffer, this.Offset, output.Buffer, 0, this.Length); + output.Length = this.Length; + output.Offset = 0; + output.Position = 0; + + return output; + } + + public void RemoveMessage(MessageReader reader) + { + var temp = MessageReader.GetSized(reader.Buffer.Length); + try + { + var headerOffset = reader.Offset - 3; + var endOfMessage = reader.Offset + reader.Length; + var len = reader.Buffer.Length - endOfMessage; + + Array.Copy(reader.Buffer, endOfMessage, temp.Buffer, 0, len); + Array.Copy(temp.Buffer, 0, this.Buffer, headerOffset, len); + + this.AdjustLength(reader.Offset, reader.Length + 3); + } + finally + { + temp.Recycle(); + } + } + + //public void InsertMessage(MessageReader reader, MessageWriter writer) + //{ + // var temp = MessageReader.GetSized(reader.Buffer.Length); + // try + // { + // var headerOffset = reader.Offset - 3; + // var startOfMessage = reader.Offset; + // var len = reader.Buffer.Length - startOfMessage; + // int writerOffset = 3; + // switch (writer.SendOption) + // { + // case SendOption.Reliable: + // writerOffset = 3; + // break; + // case SendOption.None: + // writerOffset = 1; + // break; + // } + + // //store the original buffer in temp + // Array.Copy(reader.Buffer, headerOffset, temp.Buffer, 0, len); + + // //put the contents of writer in at headerOffset + // Array.Copy(writer.Buffer, writerOffset, this.Buffer, headerOffset, writer.Length - writerOffset); + + // //put the original buffer in after that + // Array.Copy(temp.Buffer, 0, this.Buffer, headerOffset + (writer.Length - writerOffset), len - writer.Length); + + // this.AdjustLength(-1 * reader.Offset, -1 * (writer.Length - writerOffset)); + // } + // finally + // { + // temp.Recycle(); + // } + //} + + private void AdjustLength(int offset, int amount) + { + if (this.readHead > offset) + { + this.Position -= amount; + } + + if (Parent != null) + { + var lengthOffset = this.Offset - 3; + var curLen = this.Buffer[lengthOffset] + | (this.Buffer[lengthOffset + 1] << 8); + + curLen -= amount; + this.Length -= amount; + + this.Buffer[lengthOffset] = (byte)curLen; + this.Buffer[lengthOffset + 1] = (byte)(this.Buffer[lengthOffset + 1] >> 8); + + Parent.AdjustLength(offset, amount); + } + } + + public void Recycle() + { + this.Parent = null; + ReaderPool.PutObject(this); + } + + #region Read Methods + public bool ReadBoolean() + { + byte val = this.FastByte(); + return val != 0; + } + + public sbyte ReadSByte() + { + return (sbyte)this.FastByte(); + } + + public byte ReadByte() + { + return this.FastByte(); + } + + public ushort ReadUInt16() + { + ushort output = + (ushort)(this.FastByte() + | this.FastByte() << 8); + return output; + } + + public short ReadInt16() + { + short output = + (short)(this.FastByte() + | this.FastByte() << 8); + return output; + } + + public uint ReadUInt32() + { + uint output = this.FastByte() + | (uint)this.FastByte() << 8 + | (uint)this.FastByte() << 16 + | (uint)this.FastByte() << 24; + + return output; + } + + public int ReadInt32() + { + int output = this.FastByte() + | this.FastByte() << 8 + | this.FastByte() << 16 + | this.FastByte() << 24; + + return output; + } + + public ulong ReadUInt64() + { + ulong output = (ulong)this.FastByte() + | (ulong)this.FastByte() << 8 + | (ulong)this.FastByte() << 16 + | (ulong)this.FastByte() << 24 + | (ulong)this.FastByte() << 32 + | (ulong)this.FastByte() << 40 + | (ulong)this.FastByte() << 48 + | (ulong)this.FastByte() << 56; + + return output; + } + + public long ReadInt64() + { + long output = (long)this.FastByte() + | (long)this.FastByte() << 8 + | (long)this.FastByte() << 16 + | (long)this.FastByte() << 24 + | (long)this.FastByte() << 32 + | (long)this.FastByte() << 40 + | (long)this.FastByte() << 48 + | (long)this.FastByte() << 56; + + return output; + } + + public unsafe float ReadSingle() + { + float output = 0; + fixed (byte* bufPtr = &this.Buffer[this.readHead]) + { + byte* outPtr = (byte*)&output; + + *outPtr = *bufPtr; + *(outPtr + 1) = *(bufPtr + 1); + *(outPtr + 2) = *(bufPtr + 2); + *(outPtr + 3) = *(bufPtr + 3); + } + + this.Position += 4; + return output; + } + + public string ReadString() + { + int len = this.ReadPackedInt32(); + if (this.BytesRemaining < len) throw new InvalidDataException($"Read length is longer than message length: {len} of {this.BytesRemaining}"); + + string output = UTF8Encoding.UTF8.GetString(this.Buffer, this.readHead, len); + + this.Position += len; + return output; + } + + public byte[] ReadBytesAndSize() + { + int len = this.ReadPackedInt32(); + if (this.BytesRemaining < len) throw new InvalidDataException($"Read length is longer than message length: {len} of {this.BytesRemaining}"); + + return this.ReadBytes(len); + } + + public byte[] ReadBytes(int length) + { + if (this.BytesRemaining < length) throw new InvalidDataException($"Read length is longer than message length: {length} of {this.BytesRemaining}"); + + byte[] output = new byte[length]; + Array.Copy(this.Buffer, this.readHead, output, 0, output.Length); + this.Position += output.Length; + return output; + } + + /// + public int ReadPackedInt32() + { + return (int)this.ReadPackedUInt32(); + } + + /// + public uint ReadPackedUInt32() + { + bool readMore = true; + int shift = 0; + uint output = 0; + + while (readMore) + { + if (this.BytesRemaining < 1) throw new InvalidDataException($"Read length is longer than message length."); + + byte b = this.ReadByte(); + if (b >= 0x80) + { + readMore = true; + b ^= 0x80; + } + else + { + readMore = false; + } + + output |= (uint)(b << shift); + shift += 7; + } + + return output; + } + #endregion + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private byte FastByte() + { + this._position++; + return this.Buffer[this.readHead++]; + } + + public unsafe static bool IsLittleEndian() + { + byte b; + unsafe + { + int i = 1; + byte* bp = (byte*)&i; + b = *bp; + } + + return b == 1; + } + } +} diff --git a/Projects/Message/Message/MessageWriter.cs b/Projects/Message/Message/MessageWriter.cs new file mode 100644 index 0000000..7646135 --- /dev/null +++ b/Projects/Message/Message/MessageWriter.cs @@ -0,0 +1,318 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace MultiplayerToolkit +{ + + /// <summary> + /// AmongUsùȥ˰ͷش + /// ṹ + /// ------------------------------------- + /// 2bytes (ushort)Эijȣ2bytes + /// ------------------------------------- + /// 1byte ЭͣЭ0ԶЭ1 + /// (2byte) (ԶЭ飬ṩһmodţSteam.UGC.Itemidulong 8bytesеţ֧65535mod) + /// 2byte (ushort)ЭID 0-65535 + /// + /// ------------------------------------- + /// </summary> + public class MessageWriter : IRecyclable + { + public const int BYTE_COUNT_OF_LENGTH = 2; // ռ2 bytes + public static int BufferSize = 64000; // Э֧64000ֽڣ62.5KB + public static readonly ObjectPool<MessageWriter> WriterPool = new ObjectPool<MessageWriter>(() => new MessageWriter(BufferSize)); + + public byte[] Buffer; + public int Length; // ЧbufferеijȣܰǶЭ + public int Position; // дλ + + // lengthһڻĴСLengthһÿЭЭݲֵֽ + + private Stack<int> messageStarts = new Stack<int>(); // ¼ЭbufferеʼλãǶṹ + + public MessageWriter(byte[] buffer) + { + this.Buffer = buffer; + this.Length = this.Buffer.Length; + } + + /// + public MessageWriter(int bufferSize) + { + this.Buffer = new byte[bufferSize]; + } + + public byte[] ToByteArray() + { + byte[] output = new byte[this.Length]; + System.Buffer.BlockCopy(this.Buffer, 0, output, 0, this.Length); + return output; + } + + /// <param name="sendOption">The option specifying how the message should be sent.</param> + public static MessageWriter Get() + { + MessageWriter output = WriterPool.GetObject(); + output.Clear(); + + return output; + } + + /// <summary> + /// Ƿexpectedȵ + /// </summary> + /// <param name="expected"></param> + /// <returns></returns> + public bool HasBytes(int expected) + { + return this.Length >= expected; + } + + /// + public void StartMessage() + { + var messageStart = this.Position; + messageStarts.Push(messageStart); + this.Buffer[messageStart] = 0; // length + this.Buffer[messageStart + 1] = 0; // length + this.Position += 2; // ushort length + } + + /// + public void EndMessage() + { + var lastMessageStart = messageStarts.Pop(); + ushort length = (ushort)(this.Position - lastMessageStart - 2); // Minus length + this.Buffer[lastMessageStart] = (byte)length; + this.Buffer[lastMessageStart + 1] = (byte)(length >> 8); + } + + /// <summary> + /// ȡǰ༭messageصһ + /// </summary> + public void CancelMessage() + { + this.Position = this.messageStarts.Pop(); + this.Length = this.Position; + } + + public void Clear() + { + Array.Clear(this.Buffer, 0, this.Buffer.Length); + this.messageStarts.Clear(); + this.Length = 0; + this.Position = 0; + } + + /// <summary> + /// message + /// </summary> + public void Recycle() + { + this.Position = this.Length = 0; + WriterPool.PutObject(this); + } + +#region WriteMethods + + public void CopyFrom(MessageReader target) + { + int offset, length; + if (target.Tag == byte.MaxValue) + { + offset = target.Offset; + length = target.Length; + } + else + { + offset = target.Offset - 3; + length = target.Length + 3; + } + + System.Buffer.BlockCopy(target.Buffer, offset, this.Buffer, this.Position, length); + this.Position += length; + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(bool value) + { + this.Buffer[this.Position++] = (byte)(value ? 1 : 0); + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(sbyte value) + { + this.Buffer[this.Position++] = (byte)value; + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(byte value) + { + this.Buffer[this.Position++] = value; + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(short value) + { + this.Buffer[this.Position++] = (byte)value; + this.Buffer[this.Position++] = (byte)(value >> 8); + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(ushort value) + { + this.Buffer[this.Position++] = (byte)value; + this.Buffer[this.Position++] = (byte)(value >> 8); + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(uint value) + { + this.Buffer[this.Position++] = (byte)value; + this.Buffer[this.Position++] = (byte)(value >> 8); + this.Buffer[this.Position++] = (byte)(value >> 16); + this.Buffer[this.Position++] = (byte)(value >> 24); + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(int value) + { + this.Buffer[this.Position++] = (byte)value; + this.Buffer[this.Position++] = (byte)(value >> 8); + this.Buffer[this.Position++] = (byte)(value >> 16); + this.Buffer[this.Position++] = (byte)(value >> 24); + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(ulong value) + { + this.Buffer[this.Position++] = (byte)value; + this.Buffer[this.Position++] = (byte)(value >> 8); + this.Buffer[this.Position++] = (byte)(value >> 16); + this.Buffer[this.Position++] = (byte)(value >> 24); + this.Buffer[this.Position++] = (byte)(value >> 32); + this.Buffer[this.Position++] = (byte)(value >> 40); + this.Buffer[this.Position++] = (byte)(value >> 48); + this.Buffer[this.Position++] = (byte)(value >> 56); + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(long value) + { + this.Buffer[this.Position++] = (byte)value; + this.Buffer[this.Position++] = (byte)(value >> 8); + this.Buffer[this.Position++] = (byte)(value >> 16); + this.Buffer[this.Position++] = (byte)(value >> 24); + this.Buffer[this.Position++] = (byte)(value >> 32); + this.Buffer[this.Position++] = (byte)(value >> 40); + this.Buffer[this.Position++] = (byte)(value >> 48); + this.Buffer[this.Position++] = (byte)(value >> 56); + if (this.Position > this.Length) this.Length = this.Position; + } + + public unsafe void Write(float value) + { + fixed (byte* ptr = &this.Buffer[this.Position]) + { + byte* valuePtr = (byte*)&value; + + *ptr = *valuePtr; + *(ptr + 1) = *(valuePtr + 1); + *(ptr + 2) = *(valuePtr + 2); + *(ptr + 3) = *(valuePtr + 3); + } + + this.Position += 4; + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(string value) + { + var bytes = UTF8Encoding.UTF8.GetBytes(value); + this.WritePacked(bytes.Length); + this.Write(bytes); + } + + public void WriteBytesAndSize(byte[] bytes) + { + this.WritePacked((uint)bytes.Length); + this.Write(bytes); + } + + public void WriteBytesAndSize(byte[] bytes, int length) + { + this.WritePacked((uint)length); + this.Write(bytes, length); + } + + public void WriteBytesAndSize(byte[] bytes, int offset, int length) + { + this.WritePacked((uint)length); + this.Write(bytes, offset, length); + } + + public void Write(byte[] bytes) + { + Array.Copy(bytes, 0, this.Buffer, this.Position, bytes.Length); + this.Position += bytes.Length; + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(byte[] bytes, int offset, int length) + { + Array.Copy(bytes, offset, this.Buffer, this.Position, length); + this.Position += length; + if (this.Position > this.Length) this.Length = this.Position; + } + + public void Write(byte[] bytes, int length) + { + Array.Copy(bytes, 0, this.Buffer, this.Position, length); + this.Position += length; + if (this.Position > this.Length) this.Length = this.Position; + } + + /// + public void WritePacked(int value) + { + this.WritePacked((uint)value); + } + + /// + public void WritePacked(uint value) + { + do + { + byte b = (byte)(value & 0xFF); + if (value >= 0x80) + { + b |= 0x80; + } + + this.Write(b); + value >>= 7; + } while (value > 0); + } +#endregion + + public void Write(MessageWriter msg) + { + this.Write(msg.Buffer, 0, msg.Length); + } + + public unsafe static bool IsLittleEndian() + { + byte b; + unsafe + { + int i = 1; + byte* bp = (byte*)&i; + b = *bp; + } + + return b == 1; + } + } +} diff --git a/Projects/Message/Message/ObjectPool.cs b/Projects/Message/Message/ObjectPool.cs new file mode 100644 index 0000000..e42cdd6 --- /dev/null +++ b/Projects/Message/Message/ObjectPool.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; + +namespace MultiplayerToolkit +{ + /// <summary> + /// A fairly simple object pool for items that will be created a lot. + /// </summary> + /// <typeparam name="T">The type that is pooled.</typeparam> + /// <threadsafety static="true" instance="true"/> + public sealed class ObjectPool<T> where T : IRecyclable + { + private int numberCreated; + public int NumberCreated { get { return numberCreated; } } + + public int NumberInUse { get { return this.inuse.Count; } } + public int NumberNotInUse { get { return this.pool.Count; } } + public int Size { get { return this.NumberInUse + this.NumberNotInUse; } } + +#if HAZEL_BAG + private readonly ConcurrentBag<T> pool = new ConcurrentBag<T>(); +#else + private readonly List<T> pool = new List<T>(); +#endif + + // Unavailable objects + private readonly ConcurrentDictionary<T, bool> inuse = new ConcurrentDictionary<T, bool>(); + + /// <summary> + /// The generator for creating new objects. + /// </summary> + /// <returns></returns> + private readonly Func<T> objectFactory; + + /// <summary> + /// Internal constructor for our ObjectPool. + /// </summary> + internal ObjectPool(Func<T> objectFactory) + { + this.objectFactory = objectFactory; + } + + /// <summary> + /// Returns a pooled object of type T, if none are available another is created. + /// </summary> + /// <returns>An instance of T.</returns> + internal T GetObject() + { +#if HAZEL_BAG + if (!pool.TryTake(out T item)) + { + Interlocked.Increment(ref numberCreated); + item = objectFactory.Invoke(); + } +#else + T item; + lock (this.pool) + { + if (this.pool.Count > 0) + { + var idx = this.pool.Count - 1; + item = this.pool[idx]; + this.pool.RemoveAt(idx); + } + else + { + Interlocked.Increment(ref numberCreated); + item = objectFactory.Invoke(); + } + } +#endif + + if (!inuse.TryAdd(item, true)) + { + throw new Exception("Duplicate pull " + typeof(T).Name); + } + + return item; + } + + /// <summary> + /// Returns an object to the pool. + /// </summary> + /// <param name="item">The item to return.</param> + internal void PutObject(T item) + { + if (inuse.TryRemove(item, out bool b)) + { +#if HAZEL_BAG + pool.Add(item); +#else + lock (this.pool) + { + pool.Add(item); + } +#endif + } + else + { +#if DEBUG + throw new Exception("Duplicate add " + typeof(T).Name); +#endif + } + } + } +} diff --git a/Projects/Message/Message/Properties/AssemblyInfo.cs b/Projects/Message/Message/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..9f03f92 --- /dev/null +++ b/Projects/Message/Message/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Message")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Message")] +[assembly: AssemblyCopyright("Copyright © 2023")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("aadabfad-53fb-43da-bac8-83d11ca36dec")] + +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Projects/Message/Message/UnitTest1.cs b/Projects/Message/Message/UnitTest1.cs new file mode 100644 index 0000000..2e02c6e --- /dev/null +++ b/Projects/Message/Message/UnitTest1.cs @@ -0,0 +1,16 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; + +namespace Message +{ + [TestClass] + public class UnitTest1 + { + [TestMethod] + public void TestMethod1() + { + + Assert.AreEqual(1, 1); + } + } +} diff --git a/Projects/Message/Message/packages.config b/Projects/Message/Message/packages.config new file mode 100644 index 0000000..54eb31a --- /dev/null +++ b/Projects/Message/Message/packages.config @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="MSTest.TestAdapter" version="2.2.7" targetFramework="net472" /> + <package id="MSTest.TestFramework" version="2.2.7" targetFramework="net472" /> +</packages>
\ No newline at end of file diff --git a/Tools/Hazel-Networking/Hazel.UnitTests/MessageReaderTests.cs b/Tools/Hazel-Networking/Hazel.UnitTests/MessageReaderTests.cs index 6cf4ba8..7a073ae 100644 --- a/Tools/Hazel-Networking/Hazel.UnitTests/MessageReaderTests.cs +++ b/Tools/Hazel-Networking/Hazel.UnitTests/MessageReaderTests.cs @@ -125,7 +125,7 @@ namespace Hazel.UnitTests reader.Length = msg.Length; var zero = reader.ReadMessage(); - + var one = reader.ReadMessage(); var two = one.ReadMessage(); var three = two.ReadMessage(); @@ -218,6 +218,27 @@ namespace Hazel.UnitTests const byte Test5 = 55; const byte TestInsert = 66; +/* +2 length=1 +1 tag=0 +1 Test0=11 + +2 length=11 +1 tag=12 + 2 length=8 + 1 tag=23 + 2 length=1 + 1 tag=34 + 1 Test3=33 + + 2 length=1 + 1 tag=45 + 1 Test4=44 +2 length=1 +1 tag=56 +1 Test5=55 +*/ + var msg = new MessageWriter(2048); msg.StartMessage(0); msg.Write(Test0); @@ -247,6 +268,12 @@ namespace Hazel.UnitTests writer.StartMessage(5); writer.Write(TestInsert); writer.EndMessage(); +/* +1 SendOption.None +2 length=2 +1 tag=5 +1 TestInsert=66 + */ reader.ReadMessage(); var one = reader.ReadMessage(); diff --git a/Tools/Hazel-Networking/Hazel.lutconfig b/Tools/Hazel-Networking/Hazel.lutconfig new file mode 100644 index 0000000..87625ea --- /dev/null +++ b/Tools/Hazel-Networking/Hazel.lutconfig @@ -0,0 +1,6 @@ +<LUTConfig Version="1.0"> + <Repository>..\..\</Repository> + <ParallelBuilds>true</ParallelBuilds> + <ParallelTestRuns>true</ParallelTestRuns> + <TestCaseTimeout>180000</TestCaseTimeout> +</LUTConfig>
\ No newline at end of file diff --git a/Tools/Hazel-Networking/Hazel/MessageReader.cs b/Tools/Hazel-Networking/Hazel/MessageReader.cs index bd3b0d8..b6e35de 100644 --- a/Tools/Hazel-Networking/Hazel/MessageReader.cs +++ b/Tools/Hazel-Networking/Hazel/MessageReader.cs @@ -1,4 +1,5 @@ -using System; +#define UNIT_TEST +using System; using System.IO; using System.Linq; using System.Runtime.CompilerServices; @@ -6,32 +7,37 @@ using System.Text; namespace Hazel { + /// <summary> + /// + /// </summary> public class MessageReader : IRecyclable { public static readonly ObjectPool<MessageReader> ReaderPool = new ObjectPool<MessageReader>(() => new MessageReader()); - public byte[] Buffer; - public byte Tag; + public byte[] Buffer; // √ 缓冲区,会被子协议共享这个缓冲区 + public byte Tag; // √ 协议名 - public int Length; // 总长度 - public int Offset; // length和tag后面 + public int Length; // √ 协议长度,对应的是MessageWriter里写入的2bytes包长度(不含length和tag) + public int Offset; // √ length和tag后面的位置(在Buffer中的位置),不是游标,不会随读取发生改变。会影响readHead的值 + + //数据位于Buffer中Offset索引开始Length长度,协议位于Buffer中Offset-3开始的Length+3长度 public int BytesRemaining => this.Length - this.Position; - private MessageReader Parent; + private MessageReader Parent; // 保存父协议索引,和父协议共享父协议的Buffer public int Position { get { return this._position; } set { - this._position = value; + this._position = value; // value是 this.readHead = value + Offset; } } - private int _position; - private int readHead; + private int _position; // √ 读取游标,相对于协议内容部分的偏移,从0开始,子协议也是从0开始,不是在Buffer中的索引 + private int readHead; // √ 读取游标,协议头部在Buffer中的位置,参考ReadMessage() public static MessageReader GetSized(int minSize) { @@ -65,10 +71,15 @@ namespace Hazel return output; } + /// <summary> + /// 创建一个新的message,保存source。用于缓存message,因为source所在的原父message可能会被销毁,如果需要延迟处理消息,需要拷贝一份新的 + /// </summary> + /// <param name="source"></param> + /// <returns></returns> public static MessageReader CopyMessageIntoParent(MessageReader source) { - var output = MessageReader.GetSized(source.Length + 3); - System.Buffer.BlockCopy(source.Buffer, source.Offset - 3, output.Buffer, 0, source.Length + 3); + var output = MessageReader.GetSized(source.Length + 3); //3=2bytes(length) + 1byte(tag) + System.Buffer.BlockCopy(source.Buffer, source.Offset - 3, output.Buffer, 0, source.Length + 3); output.Offset = 0; output.Position = 0; @@ -77,6 +88,11 @@ namespace Hazel return output; } + /// <summary> + /// 完全复制 一份 + /// </summary> + /// <param name="source"></param> + /// <returns></returns> public static MessageReader Get(MessageReader source) { var output = MessageReader.GetSized(source.Buffer.Length); @@ -96,7 +112,7 @@ namespace Hazel public static MessageReader Get(byte[] buffer, int offset) { // Ensure there is at least a header - if (offset + 3 > buffer.Length) return null; + if (offset + 3 > buffer.Length) return null; //3=length+tag var output = ReaderPool.GetObject(); @@ -104,7 +120,7 @@ namespace Hazel output.Offset = offset; output.Position = 0; - output.Length = output.ReadUInt16(); + output.Length = output.ReadUInt16(); // 读到的值为length output.Tag = output.ReadByte(); output.Offset += 3; @@ -121,22 +137,24 @@ namespace Hazel // Ensure there is at least a header if (this.BytesRemaining < 3) throw new InvalidDataException($"ReadMessage header is longer than message length: 3 of {this.BytesRemaining}"); - var output = new MessageReader(); + MessageReader output = new MessageReader(); output.Parent = this; output.Buffer = this.Buffer; - output.Offset = this.readHead; + output.Offset = this.readHead; // 下面会读取 output.Position = 0; + // 读length和tag的值并保存在字段里 output.Length = output.ReadUInt16(); output.Tag = output.ReadByte(); - output.Offset += 3; + // Offset, readHead齐步走,移到Length和Tag后面,_position=0 + output.Offset += 3; // 3=length+tag output.Position = 0; if (this.BytesRemaining < output.Length + 3) throw new InvalidDataException($"Message Length at Position {this.readHead} is longer than message length: {output.Length + 3} of {this.BytesRemaining}"); - this.Position += output.Length + 3; + this.Position += output.Length + 3; //跳过整个子协议 return output; } @@ -201,22 +219,29 @@ namespace Hazel } } + +#if UNIT_TEST // 我自己加的,作为备注 + /// <summary> + /// 仅用于单元测试的方法 + /// </summary> + /// <param name="reader">父reader</param> + /// <param name="writer"></param> public void InsertMessage(MessageReader reader, MessageWriter writer) { var temp = MessageReader.GetSized(reader.Buffer.Length); try { - var headerOffset = reader.Offset - 3; - var startOfMessage = reader.Offset; - var len = reader.Buffer.Length - startOfMessage; - int writerOffset = 3; + var headerOffset = reader.Offset - 3; // headerOffset是length+tag,这个方法仅仅接受reader不含sendoption的情况 + var startOfMessage = reader.Offset; // 头部后面的数据开始的索引 + var len = reader.Buffer.Length - startOfMessage; // 疑似写错了,应该是headerOffset + int writerOffset = 3;// √ 跳过header switch (writer.SendOption) { case SendOption.Reliable: writerOffset = 3; break; case SendOption.None: - writerOffset = 1; + writerOffset = 1; break; } @@ -236,6 +261,7 @@ namespace Hazel temp.Recycle(); } } +#endif private void AdjustLength(int offset, int amount) { @@ -266,7 +292,7 @@ namespace Hazel ReaderPool.PutObject(this); } - #region Read Methods +#region Read Methods public bool ReadBoolean() { byte val = this.FastByte(); @@ -427,7 +453,7 @@ namespace Hazel return output; } - #endregion +#endregion [MethodImpl(MethodImplOptions.AggressiveInlining)] private byte FastByte() diff --git a/Tools/Hazel-Networking/Hazel/MessageWriter.cs b/Tools/Hazel-Networking/Hazel/MessageWriter.cs index cbae11c..68280cd 100644 --- a/Tools/Hazel-Networking/Hazel/MessageWriter.cs +++ b/Tools/Hazel-Networking/Hazel/MessageWriter.cs @@ -6,12 +6,15 @@ using System.Text; namespace Hazel { /// <summary> - /// 嵌套结构的Message + /// 单向写入的Message,嵌套结构的Message,如果有嵌套,也只有一个header,子协议没有header + /// 整个结构都会被socket发送,参见UdpConnection.cs /// 结构: - /// ------------------------------------ - /// 2bytes (ushort) 包长度 - /// 1bytes (tag) 协议ID,在AmongUS里是tags.cs里定义的tag和subtag - /// ------------------------------------ + /// -------------header----------------- + /// 1bytes SendOption 是否是可靠传输 + /// (2bytes) (可靠传输用到的) + /// --------------数据------------------ + /// 2bytes 包长度(不含这2bytes和下面的tag 1byte) + /// 1bytes 协议ID,在AmongUS里是tags.cs里定义的tag和subtag /// 数据 包括嵌套的子协议 /// ------------------------------------ /// </summary> @@ -20,8 +23,8 @@ namespace Hazel public static int BufferSize = 64000; public static readonly ObjectPool<MessageWriter> WriterPool = new ObjectPool<MessageWriter>(() => new MessageWriter(BufferSize)); - public byte[] Buffer; - public int Length; // 总长度 + public byte[] Buffer; // 缓冲区,保存了整个包体,包括头部和内容 + public int Length; // 有效数据在buffer中的长度,可能包含多个嵌套子协议。Length>=Position public int Position; // 写入游标 public SendOption SendOption { get; private set; } @@ -86,6 +89,11 @@ namespace Hazel return output; } + /// <summary> + /// expected没有header + /// </summary> + /// <param name="expected"></param> + /// <returns></returns> public bool HasBytes(int expected) { if (this.SendOption == SendOption.None) diff --git a/Tools/Hazel-Networking/Hazel/Udp/UdpClientConnection.cs b/Tools/Hazel-Networking/Hazel/Udp/UdpClientConnection.cs index f6da329..90aeb0d 100644 --- a/Tools/Hazel-Networking/Hazel/Udp/UdpClientConnection.cs +++ b/Tools/Hazel-Networking/Hazel/Udp/UdpClientConnection.cs @@ -205,9 +205,10 @@ namespace Hazel.Udp } #endif - var msg = MessageReader.GetSized(this.ReceiveBufferSize); + var msg = MessageReader.GetSized(this.ReceiveBufferSize);//һmessage try { + // BufferMessageWriterݣheader socket.BeginReceive(msg.Buffer, 0, msg.Buffer.Length, SocketFlags.None, ReadCallback, msg); } catch @@ -282,7 +283,7 @@ namespace Hazel.Udp //Begin receiving again try { - StartListeningForData(); + StartListeningForData(); //Ϣûasync awaitһwhileѯҪǶ } catch (SocketException e) { @@ -305,6 +306,7 @@ namespace Hazel.Udp DataReceivedRaw?.Invoke(msg.Buffer, msg.Length); #endif + //c //! ص㿴泤ʲô HandleReceive(msg, msg.Length); } diff --git a/Tools/Hazel-Networking/Hazel/Udp/UdpConnection.Reliable.cs b/Tools/Hazel-Networking/Hazel/Udp/UdpConnection.Reliable.cs index bed4738..cff403b 100644 --- a/Tools/Hazel-Networking/Hazel/Udp/UdpConnection.Reliable.cs +++ b/Tools/Hazel-Networking/Hazel/Udp/UdpConnection.Reliable.cs @@ -280,7 +280,18 @@ namespace Hazel.Udp ushort id; if (ProcessReliableReceive(message.Buffer, 1, out id)) { - InvokeDataReceived(SendOption.Reliable, message, 3, bytesReceived); + //c +/* + void InvokeDataReceived(SendOption sendOption, MessageReader buffer, int dataOffset, int bytesReceived) + { + buffer.Offset = dataOffset; + buffer.Length = bytesReceived - dataOffset; + buffer.Position = 0; + + InvokeDataReceived(buffer, sendOption); + } +*/ + InvokeDataReceived(SendOption.Reliable, message, 3, bytesReceived); // √ 3是header,不是length+tag } else { @@ -302,7 +313,7 @@ namespace Hazel.Udp byte b2 = bytes[offset + 1]; //Get the ID form the packet - id = (ushort)((b1 << 8) + b2); + id = (ushort)((b1 << 8) + b2); // 用于可靠传输的id /* * It gets a little complicated here (note the fact I'm actually using a multiline comment for once...) diff --git a/Tools/Hazel-Networking/Hazel/Udp/UdpConnection.cs b/Tools/Hazel-Networking/Hazel/Udp/UdpConnection.cs index e64576a..78f1788 100644 --- a/Tools/Hazel-Networking/Hazel/Udp/UdpConnection.cs +++ b/Tools/Hazel-Networking/Hazel/Udp/UdpConnection.cs @@ -77,7 +77,7 @@ namespace Hazel.Udp case SendOption.Reliable: ResetKeepAliveTimer(); - AttachReliableID(buffer, 1); + AttachReliableID(buffer, 1); // д֮ǰյڿɿID WriteBytesToConnection(buffer, buffer.Length); Statistics.LogReliableSend(buffer.Length - 3); break; @@ -132,6 +132,7 @@ namespace Hazel.Udp { //Handle reliable receives case (byte)SendOption.Reliable: + //c //! ReliableMessageReceive(message, bytesReceived); break; @@ -161,6 +162,7 @@ namespace Hazel.Udp break; case (byte)SendOption.None: + //c //! InvokeDataReceived(SendOption.None, message, 1, bytesReceived); Statistics.LogUnreliableReceive(bytesReceived - 1, bytesReceived); break; |