이것은 a입니다 간단하고 사용하기 쉽습니다 네트워크 라이브러리.
이것은 네트워크 모듈을위한 보편적 인 솔루션입니다. 애플리케이션 네트워크 모듈에 통합 된 하이 레벨 인터페이스를 제공하도록 설계되었습니다.
전체 클래스 라이브러리는 여러 DLL으로 나뉩니다. 간단히 말해서 : netRemotestAndard.dll은 인터페이스 정의 만있는 표준입니다. megumin.remote.dll은 구현입니다. DotnetStandard와 DotNetCore의 관계를 유사합니다.
왜 여러 DLL으로 나뉘어져 있습니까?
특정 구현은 다른 많은 DLL에 대한 의존성이 필요할 수 있으며 인터페이스 정의에는 이러한 종속성이 필요하지 않습니다. 인터페이스 및 사용자 정의 구현 만 사용하려는 사용자의 경우 추가 종속성을 도입하는 데 불필요합니다. 예를 들어 Messagestandard, 사용자는 여러 직렬화 라이브러리를 참조하지 않고도 선택한 직렬화 라이브러리 만 참조 할 수 있습니다.

그렇습니다. Nuget을 사용하여 megumin.remote를 얻으십시오. 그러나 직렬화 라이브러리와 일치해야하며 다양한 직렬화 라이브러리에 추가 요구 사항이있을 수 있습니다. C# 7.3 구문 사용으로 인해 소스 코드가 Unity에서 사용되는 경우 최소 2018.3이 필요합니다.
대상 프레임 워크 Netstandard2.1은 Unity 버전 2021.2 이상에있는 것이 좋습니다. 소스 코드는 너무 작은 버전에 사용될 수 있지만 종속성을 직접 해결해야합니다.

또는 "com.megumin.net": "https://github.com/KumoKyaku/Megumin.Net.git?path=UnityPackage/Packages/Net" Packages/manifest.json 하십시오.
대상 버전을 설정하려면
*.*.*릴리스 태그를 사용하여#2.1.0과 같은 버전을 지정할 수 있습니다. 예를 들어https://github.com/KumoKyaku/Megumin.Net.git?path=UnityPackage/Packages/Net#2.1.0입니다.
Span<T> . 고성능 IO 버퍼로 System.IO.Pipelines 사용하십시오.MIT许可证 설계 원칙 : 가장 일반적으로 사용되는 코드는 가장 단순화되고 모든 복잡한 부분이 캡슐화됩니다.发送一个消息,并等待一个消息返回클래스 라이브러리의 전체 내용입니다.
결과 값에서 예외를 반환하는 것이 합리적입니다.
///实际使用中的例子
IRemote remote = new TCPRemote ( ) ; ///省略连接代码……
public async void TestSend ( )
{
Login login = new Login ( ) { Account = "LiLei" , Password = "HanMeiMei" } ;
/// 泛型类型为期待返回的类型
var ( result , exception ) = await remote . SendAsync < LoginResult > ( login ) ;
///如果没有遇到异常,那么我们可以得到远端发回的返回值
if ( exception == null )
{
Console . WriteLine ( result . IsSuccess ) ;
}
} 메소드 서명 :
ValueTask < Result > SendAsyncSafeAwait < Result > ( object message , object options = null , Action < Exception > onException = null ) ; 결과 값은 값이 보장됩니다. 결과 값이 비어 있거나 다른 예외가되면 예외 콜백 함수가 발생하지 않으므로 캐치를 시도 할 필요가 없습니다.异步方法的后续部分不会触发빈 점검에서 후속 부분을 제거 할 수 있습니다.
(注意:这不是语言特性,也不是异步编程特性,这依赖于具体Remote的实现,这是类库的特性。如果你使用了这个接口的其他实现,要确认实现遵守了这个约定。
IRemote remote = new TCPRemote ( ) ; ///省略连接代码……
public async void TestSend ( )
{
Login login = new Login ( ) { Account = "LiLei" , Password = "HanMeiMei" } ;
/// 泛型类型为期待返回的类型
LoginResult result = await remote . SendAsyncSafeAwait < LoginResult > ( login , ( ex ) => { } ) ;
///后续代码 不用任何判断,也不用担心异常。
Console . WriteLine ( result . IsSuccess ) ;
}하나의 요청이 여러 답장 유형에 해당하는 것이 권장되지는 않지만 일부 비즈니스 설계는 여전히이 요구 사항이 있습니다. 예를 들어, 모든 ErrorCode가 독립 유형으로 응답되면 요청에는 두 개의 응답 유형이있을 수 있습니다 : 해당 답글과 ErrorCode.
IMessage接口Protobuf 프로토콜에서 반환을 기다리는 유형으로 사용할 수 있습니다.
class ErrorCode { }
class Resp { }
class Req { }
async void Test ( IRemote remote ) {
Req req = new Req ( ) ;
///泛型中填写所有期待返回类型的基类,然后根据类型分别处理。
///如果泛型处仅使用一种类型,那么服务器回复另一种类型时,底层会转换为 InvalidCastException 进如异常处理逻辑。
var ret = await remote . SendAsyncSafeAwait < object > ( req ) ;
if ( ret is ErrorCode ec )
{
}
else if ( ret is Resp resp )
{
}
} ValueTask<object> OnReceive(short cmd, int messageID, object message);수신기 콜백 함수
protected virtual async ValueTask < object > OnReceive ( short cmd , int messageID , object message )
{
switch ( message )
{
case TestPacket1 packet1 :
Console . WriteLine ( $ "接收消息{ nameof ( TestPacket1 ) } -- { packet1 . Value } " ) ;
return null ;
case Login login :
Console . WriteLine ( $ "接收消息{ nameof ( Login ) } -- { login . Account } " ) ;
return new LoginResult { IsSuccess = true } ;
case TestPacket2 packet2 :
return new TestPacket1 { Value = packet2 . Value } ;
default :
break ;
}
return null ;
} 특정 응답 방법의 경우 PreReceive 기능 소스 코드를 참조하십시오.
하트 비트, RTT, 타임 스탬프 동기화 및 기타 기능은 모두이 메커니즘에 의해 구현됩니다.
PreReceive 함수로 이러한 메시지를 처리하고 GetResponse를 호출하여 결과를 발신자에게 반환합니다. public interface IAutoResponseable : IPreReceiveable
{
ValueTask < object > GetResponse ( object request ) ;
} 스레드 스케줄링
원격을 사용하여 bool UseThreadSchedule(int rpcID, short cmd, int messageID, object message) 기능을 사용하여 메시지 콜백 함수가 실행되는 스레드를 결정합니다. 사실 일 때, 모든 메시지는 megumin.threadscheduler.update로 요약됩니다.
수신 된 콜백을 처리하려면이 기능을 폴링해야합니다.이 기능은 수신 된 메시지 순서대로 콜백이 트리거되도록합니다 (순서대로 버그를 제출하십시오). 고정 업데이트는 일반적으로 통일에 사용해야합니다.如果你的消息在分布式服务器之间传递,你可能希望消息在中转进程中尽快传递,那么거짓으로 수신 된 콜백은 작업을 사용하여 실행되며 여론 조사에서 기다릴 필요는 없지만 질서가 될 수 없으며 Fish and Bear의 발을 모두 가질 수는 없습니다.
///建立主线程 或指定的任何线程 轮询。(确保在unity中使用主线程轮询)
///ThreadScheduler保证网络底层的各种回调函数切换到主线程执行以保证执行顺序。
ThreadPool . QueueUserWorkItem ( ( A ) =>
{
while ( true )
{
ThreadScheduler . Update ( 0 ) ;
Thread . Yield ( ) ;
}
} ) ; Message.dll
(AOT/IL2CPP) 직렬화 된 클래스가 DLL 형태로 Unity로 가져 오면 (메시지 클래스 라이브러리는 때때로 Unity 외부의 공유 프로젝트로 설계 되었기 때문에) 링크 파일을 추가하여 직렬화 된 클래스 속성의 GET 및 설정 방법을 IL2CPP로 절단하는 것을 방지해야합니다.重中之重,因为缺失get,set函数不会报错,错误通常会被定位到序列化库的多个不同位置(我在这里花费了16个小时)。
<linker>
<assembly fullname="Message" preserve="all"/>
</linker>
| 총 길이 (총 길이 4 바이트 포함 값) | RPCID | CMD | msgid | 몸 |
|---|---|---|---|---|
| 총 길이 (값은 총 길이 자체의 4 바이트를 포함합니다) | 메시지 ID | 메시지 텍스트 | ||
| int32 (int) | int32 (int) | int16 (짧은) | int32 (int) | 바이트[] |
| 4BYTE | 4BYTE | 2BYTE | 4BYTE | 바이트 []. lenght |
当服务器不使用本库,或者不是C#语言时。满足报头格式,即可支持本库所有特性。 MessagePipeline은 megumin.remote로 분리 된 함수의 일부입니다.
또한 프로토콜 스택으로 이해할 수 있습니다.
메시지를 보내거나받는 데 사용되는 특정 단계를 결정합니다. MessagePipeline을 사용자 정의하여 원격으로 주입하여 특별한 요구를 충족시킬 수 있습니다.
예를 들어:
你可以为每个Remote指定一个MessagePipeline实例,如果没有指定,默认使用MessagePipeline.Default。버전 2.0 MessagePipeline을 삭제하고 여러 원격 구현에서 재 작성 가능한 기능으로 변경했습니다. 엔지니어링 실무에서, 메시지 파이프 라인을 리모컨에서 분리하는 것은 의미가없고 과도하게 설계되었습니다. 3 개의 프로토콜의 파이프 라인을 원격으로 동시에 사용자 정의 해야하는 경우 사용자는 스스로 분할 할 수 있으며 프레임 워크는 처리되지 않습니다.
人生就是反反复复。
버전 3.0은 원래 디자인으로 다시 변경하기로 결정했으며 첫 번째 버전의 디자인 아이디어가 더 좋습니다.
엔지니어링 연습 후, 2.0의 설계는 다시 작성하기에 편리하지 않다는 것이 밝혀졌습니다. 사용자는 tcpremote, udpremote 및 kcpremote에서 상속되는 다른 프로토콜에 대해 동일한 재 작성 코드의 여러 사본을 다시 작성해야합니다. 그들이 수정할 때마다 여러 사본을 동시에 수정해야하며, 이는 매우 부피가 크다.
사용자는 주로 수신 메시지 부품과 연결이 끊어진 부분을 다시 작성하고 연결 해제 된 재 연결 부분도 다른 프로토콜에 대해 다르게 처리됩니다.
따라서 운송과 Idisconnecthandler를 리모컨에서 나누십시오.
기본적으로 3.0의 원격은 MessagePipeline 1.0과 같습니다. 3.0의 전송은 원격 1.0과 같습니다.
Messagelut (메시지 직렬화 사형화 콜백 룩업 테이블)은 Messagestandard의 핵심 클래스입니다. MessagePipeline은 Messagelut에 등록 된 기능을 찾아 직렬화됩니다.因此在程序最开始你需要进行函数注册.
일반 등록 기능 :
void RegistIMeguminFormatter < T > ( KeyAlreadyHave key = KeyAlreadyHave . Skip ) where T : class , IMeguminFormatter , new ( ) 직렬화 클래스 라이브러리의 미들웨어는 Messagelut을 기반으로 여러 간단하고 사용하기 쉬운 API를 제공하며 자동으로 직렬화 및 사막화 기능을 생성합니다. 프로토콜 클래스에 msgidattribute를 추가하여 조회 테이블에서 사용하는 ID를 제공해야합니다. ID는 하나의 직렬화 함수 세트에만 해당 할 수 있으므로 각 프로토콜 클래스는 동시에 하나의 직렬화 라이브러리 만 사용할 수 있습니다.
namespace Message
{
[ MSGID ( 1001 ) ] //MSGID 是框架定义的一个特性,注册函数通过反射它取得ID
[ ProtoContract ] //ProtoContract 是protobuf-net 序列化库的标志
[ MessagePackObject ] //MessagePackObject 是MessagePack 序列化库的标志
public class Login //同时使用多个序列化类库的特性标记,但程序中每个消息同时只能使用一个序列化库
{
[ ProtoMember ( 1 ) ] //protobuf-net 从 1 开始
[ Key ( 0 ) ] //MessagePack 从 0 开始
public string Account { get ; set ; }
[ ProtoMember ( 2 ) ]
[ Key ( 1 ) ]
public string Password { get ; set ; }
}
[ MSGID ( 1002 ) ]
[ ProtoContract ]
[ MessagePackObject ]
public class LoginResult
{
[ ProtoMember ( 1 ) ]
[ Key ( 0 ) ]
public bool IsSuccess { get ; set ; }
}
}조립은 JIT 환경 바로 아래에 등록 할 수 있습니다.
private static async void InitServer ( )
{
//MessagePackLUT.Regist(typeof(Login).Assembly);
Protobuf_netLUT . Regist ( typeof ( Login ) . Assembly ) ;
ThreadPool . QueueUserWorkItem ( ( A ) =>
{
while ( true )
{
ThreadScheduler . Update ( 0 ) ;
Thread . Yield ( ) ;
}
} ) ;
} AOT/IL2CPP 환경에서 AOT/IL2CPP编译器정적 분석 중에 해당 일반 함수가 생성되도록 일반 함수를 통해 각 프로토콜 클래스의 등록을显示해야합니다.
public void TestDefine ( )
{
Protobuf_netLUT . Regist < Login > ( ) ;
Protobuf_netLUT . Regist < LoginResult > ( ) ;
} 알아채다:序列化库代码生成器生成代码.
이는 정적 분석 중에 직렬화 된 클래스 라이브러리의 일반 API에 대한 일반적인 기능을 생성하는 것입니다.
예를 들어 :
ProtoBuf.Serializer.Serialize<T>()ProtoBuf.Serializer.Serialize<Login>()로 생성됩니다.
둘이 다릅니다.
각 라이브러리에는 자체 제한 사항이 있으며 IL2CPP에 대한 지원도 다릅니다. 이 프레임 워크는 각 지원되는 라이브러리에 대해 Messagestandard/Messagelut에서 상속 된 새로운 Messagelut를 작성합니다.
각 직렬화 라이브러리는 Span<byte> 다르게지지하므로 중간 계층에서 약간의 성능 손실이있을 수 있습니다.
직렬화 기능에는 세 가지 형식이 있습니다.
RPC功能: 요청 및 반환 메시지의 일대일 일치를 보장합니다. RPCID는 보낼 때 음수이며, RPCID*-1은 반환 할 때 양수이며, 양수와 음수는 위아래로 구별하는 데 사용됩니다.内存分配:内存池사용하여 할당을 줄입니다.内存池: 표준 라이브러리 메모리 풀, ArrayPool<byte>.Shared .序列化: 유형을 사용하여 키 조회 기능을 수행하십시오.反序列化: MSGID (int)를 사용하여 주요 조회 함수를 수행하십시오.MessageLUT.Regist<T> 함수는 수동으로 다른 유형을 추가합니다.消息类型: 전체 직렬화 프로세스가 여러 포장 및 Unboxing으로 이어질有可能큰 맞춤형 문자를 사용하지 마십시오. 매개 변수 전송 프로세스 중에는 여러 번 복사되며 성능은 클래스의 성능보다 낮습니다.<TargetFrameworks>netstandard2.0;netstandard2.1;net5;net6</TargetFrameworks> 지원합니다.时间和空间上的折衷직렬화 전에 메시지 크기를 결정할 수 없으므로 충분히 큰 버퍼를 직렬화 계층으로 전달해야합니다. 복사하지 않으면 전체 대형 버퍼가 전송 레이어로 직접 전달됩니다. 비동기 특성으로 인해 보내는 과정의 수명주기를 정확하게 아는 것은 불가능합니다. 전송 레이어에 다수의 큰 버퍼가 축적 될 수 있으며, 이는 심한 메모리를 소비합니다. 따라서 라이브러리는 직렬화 계층과 전송 계층 사이에 사본을 만듭니다.
버전 2.0은 IBufferWriter<byte> 및 ReadOnlySequence<byte> 사용 하여이 문제를 해결합니다. 이는 더 효율적입니다.
이것은 작문 도서관에서 요약 된 지식 또는 추측입니다.
Megumin.Remote는 MMORPG의 목표로 달성됩니다. MORPG가 아닌 게임에 최선의 선택이 아닐 수도 있습니다. 먼 미래에는 NetRemotestandard의 다양한 구현이 다른 게임 유형에 대해 작성 될 수 있습니다.
Where the data stores before we invoke 'socket.read(buffer, offset, count)'?Doubt regarding Winsock Kernel Buffer and Nagle algorithm