메인 콘텐츠로 건너뛰기
Translation of pages other than English and Japanese versions is maintained by community contributions. The project does not guarantee the accuracy of the content and may not reflect the latest content.

7.0의 새로운 기능

최종 업데이트

일반

StreamingHub 메서드의 반환값으로 void를 지원합니다.

StreamingHub 메서드의 반환값으로 void를 지원합니다. 이는 서버의 반환값이 필요 없는 Fire-and-Forget 패턴에서 사용할 수 있습니다.

클라이언트 결과

StreamingHub에서 리시버(receiver)의 메서드 호출을 대기하고, 클라이언트에서의 처리 결과를 서버에서 받을 수 있는 구조를 추가했습니다. 자세한 내용은 클라이언트 결과 페이지를 참조하시기 바랍니다.

StreamingHub의 내장 하트비트(heartbeat) 기능

StreamingHub에서 내장 하트비트 기능을 추가했습니다. 자세한 내용은 하트비트 (Haertbeat) 페이지를 참조하시기 바랍니다.

MessagePack v3로 업그레이드

MessagePack이 v3로 업그레이드되었습니다. 이로 인해 Unity나 AOT 환경에서 MessagePack Generator(mpc)의 사용이 불필요하게 되었습니다. 자세한 내용은 MessagePack-CSharp의 릴리스를 참조하시기 바랍니다.

서버

StreamingHub의 그룹 백엔드 구현 개선

StreamingHub의 그룹 백엔드가 Multicaster 기반으로 개선되어, 보다 유연한 그룹 조작이 가능하게 되었습니다. 이에 따라 그룹 관련 API가 변경되었습니다

  • InMemoryStorage 제거
  • IGroup 인터페이스의 API 변경
    • Group에 Receiver를 가져오는 API 변경
      • All, Single, Only, Except 메서드의 추가
      • StreamingHubBase의 Broadcast 계열 메서드 및 Group의 CreateBroadcaster 계열 메서드의 삭제

자세한 내용은 그룹애플리케이션 관리형 그룹을 참조하시기 바랍니다. 또한 v6에서의 마이그레이션에 관해서는 마이그레이션 가이드도 참조하시기 바랍니다.

MagicOnion.Server.JsonTranscoding 도입 및 MagicOnion.Server.HttpGateway 제거

Swagger 지원에 사용되던 MagicOnion.Server.HttpGateway가 제거되었습니다. 대신, 요청(Request)/응답(Response)을 JSON으로 인코딩/디코딩하는 MagicOnion.Server.JsonTranscoding이 도입되었으며, Swagger 지원이 추가되었습니다. 더 자세한 정보는 JSON 트랜스코딩 페이지를 참조하시기 바랍니다.

기타 개선 사항

  • .NET 9 지원
  • AddLoggingAddGrpc를 암묵적으로 호출하도록 했습니다.

클라이언트

WaitForDisconnectAsync API 추가

WaitForDisconnectAsync 메서드가 StreamingHub 클라이언트에 추가되었습니다. 이 메서드는 이전의 WaitForDisconnect처럼 연결 해제를 기다리지만, 이제는 연결 해제의 이유를 반환합니다. 이를 통해 하트비트(heartbeat)로 인한 타임아웃과 같은 연결 해제 이유를 얻을 수 있습니다. 더 자세한 정보는 연결 해제 페이지를 참조하시기 바랍니다.

기타 개선 사항

  • 클라이언트 라이브러리에서의 트리밍(trimming) 경고를 활성화했습니다.
  • StreamingHubClient에서의 메모리 할당 감소

호환성이 깨지는 변경 사항

서버

.NET 7에 대한 지원이 삭제되었습니다.

.NET 7은 런타임 지원이 종료됨에 따라 더 이상 지원되지 않습니다. .NET 8 이상을 사용하시기 바랍니다.

StreamingHubContext가 이제 재사용됩니다

StreamingHubContext가 이제 재사용되므로, Hub 메서드 호출 외부에서 StreamingHubContext를 캐싱하면 예기치 않은 동작이 발생할 수 있습니다. 컨텍스트는 호출 중에만 유효합니다.

gRPC 메서드에 대한 바인딩 로직 재설계

서버에서 MagicOnion 메서드를 gRPC 메서드로 등록하는 메커니즘을 재설계했습니다. 이는 MagicOnion의 Unary/StreamingHub 서비스를 등록하는 시점을 애플리케이션 빌드에서 요청 파이프라인/라우트 구성으로 변경되었습니다.

AddMagicOnion은 Unary/StreamingHub 서비스를 등록하는 데 사용되었지만, 이제는 MapMagicOnionService로 등록하도록 변경해야 합니다.

클라이언트

C-Core gRPC 라이브러리 지원이 삭제되었습니다.

Unity에서의 C-core gRPC 라이브러리 지원을 제거했습니다. 대신 grpc-dotnet과 YetAnotherHttpHandler를 사용하시기 바랍니다. 자세한 내용은 Unity 환경에서 사용하기 페이지를 참조하시기 바랍니다.

StreamingHub 클라이언트가 연결 해제된 후, ObjectDisposedException이 아닌 RpcException을 throw하도록 변경되었습니다.

이전 버전에서는 StreamingHub에서 연결이 해제된 후 클라이언트에서 메서드 호출을 하면 ObjectDisposedException이 throw되었지만, RpcException이 throw되도록 변경되었습니다. 일반적인 클래스와 마찬가지로 Dispose된 경우에만 ObjectDisposedException이 throw됩니다.

v6에서 마이그레이션 가이드

Unity 클라이언트 마이그레이션

일반적인 .NET 애플리케이션과 달리, Unity 클라이언트의 업그레이드에서는 빈 Unity 프로젝트에서 MagicOnion을 설치하고 복사해야 합니다.

이는 Unity 프로젝트에 이미 Unity 패키지 매니저를 통해 MagicOnion이나 MessagePack이 설치된 상태에서는 NuGet 패키지를 설치할 수 없으며, 단순히 이러한 라이브러리를 삭제한 경우에도 빌드 에러가 발생하고 패키지가 있는 상태가 유지되기 때문입니다.

작업용 빈 Unity 프로젝트 만들기

  • NuGetForUnity를 설치합니다.
  • NuGetForUnity로 MagicOnion.Client를 설치합니다.

업그레이드 대상 프로젝트

  • NuGetForUnity를 설치합니다.
  • MagicOnion, MessagePack을 삭제합니다.

작업용 프로젝트에 설치된 NuGet 패키지 일체를 업그레이드 대상 프로젝트에 복사

  • Assets 아래의 NuGet.config와 packages.config도 복사 또는 병합합니다.

Packages/manifest.json 재작성

    "com.cysharp.magiconion": "https://github.com/Cysharp/MagicOnion.git?path=/src/MagicOnion.Client.Unity/Assets/Scripts/MagicOnion#{Version}",

위의 행을 아래와 같이 다시 작성합니다. {Version}은 설치하려는 버전에 맞게 변경하시기 바랍니다.

    "com.cysharp.magiconion.client.unity": "https://github.com/Cysharp/MagicOnion.git?path=/src/MagicOnion.Client.Unity/Assets/Scripts/MagicOnion.Client.Unity#{Version}",

서버 API: StreamingHub

StreamingHub의 그룹(Group) 기능이 크게 변경되어 코드 레벨에서의 API 호환성이 없어졌습니다. 따라서 v6에서 v7로 마이그레이션할 때는 v7의 사용 방법으로 변경하거나, 호환 API shim을 사용해야 합니다.

호환 API shim

StreamingHub를 사용하는 경우, v7로 마이그레이션할 때 다음의 호환 API shim을 사용하여 단계적인 마이그레이션이 가능합니다. 단, 이 호환 shim에는 그룹의 InMemoryStorage 기능이 포함되어 있지 않기 때문에, 해당 기능들을 사용하고 있는 경우에는 별도의 대응이 필요합니다.

using Cysharp.Runtime.Multicast;

namespace MagicOnion.Server.Hubs;

public abstract class StreamingHubBaseCompat<THub, TReceiver> : StreamingHubBase<THub, TReceiver>
where THub : IStreamingHub<THub, TReceiver>
{
protected TReceiver Broadcast(IGroup group) => ((GroupCompat<TReceiver>)group).Inner.All;
protected TReceiver BroadcastExceptSelf(IGroup group) => ((GroupCompat<TReceiver>)group).Inner.Except(ConnectionId);
protected TReceiver BroadcastExcept(IGroup group, Guid except) => ((GroupCompat<TReceiver>)group).Inner.Except(except);
protected TReceiver BroadcastExcept(IGroup group, IReadOnlyList<Guid> excepts) => ((GroupCompat<TReceiver>)group).Inner.Except(excepts);
protected TReceiver BroadcastToSelf(IGroup group) => ((GroupCompat<TReceiver>)group).Inner.Single(ConnectionId);
protected TReceiver BroadcastTo(IGroup group, Guid toConnectionId) => ((GroupCompat<TReceiver>)group).Inner.Single(toConnectionId);
protected TReceiver BroadcastTo(IGroup group, IReadOnlyList<Guid> toConnectionIds) => ((GroupCompat<TReceiver>)group).Inner.Only(toConnectionIds);

protected new HubGroupRepository Group { get; }

protected StreamingHubBaseCompat()
{
Group = new HubGroupRepositoryCompat<TReceiver>(base.Group);
}

class HubGroupRepositoryCompat<T> : HubGroupRepository
{
readonly HubGroupRepository<T> groupRepo;

public HubGroupRepositoryCompat(HubGroupRepository<T> groupRepo)
{
this.groupRepo = groupRepo;
}

public override async ValueTask<IGroup> AddAsync(string name)
=> new GroupCompat<T>(await groupRepo.AddAsync(name));
}

class GroupCompat<T> : IGroup
{
public IGroup<T> Inner { get; }

public GroupCompat(IGroup<T> inner)
{
this.Inner = inner;
}

public ValueTask RemoveAsync(ServiceContext serviceContext)
=> this.Inner.RemoveAsync(serviceContext);

public T1 CreateBroadcaster<T1>() => (T1)(object)Inner.All!;
public T1 CreateBroadcasterTo<T1>(Guid toConnectionId) => (T1)(object)Inner.Single(toConnectionId)!;
public T1 CreateBroadcasterTo<T1>(IReadOnlyList<Guid> toConnectionIds) => (T1)(object)Inner.Only(toConnectionIds)!;
public T1 CreateBroadcasterExcept<T1>(Guid toConnectionId) => (T1)(object)Inner.Except(toConnectionId)!;
public T1 CreateBroadcasterExcept<T1>(IReadOnlyList<Guid> toConnectionIds) => (T1)(object)Inner.Except(toConnectionIds)!;
}
}

public interface IGroup
{
ValueTask RemoveAsync(ServiceContext serviceContext);

T CreateBroadcaster<T>();
T CreateBroadcasterTo<T>(Guid toConnectionId);
T CreateBroadcasterTo<T>(IReadOnlyList<Guid> toConnectionIds);
T CreateBroadcasterExcept<T>(Guid toConnectionId);
T CreateBroadcasterExcept<T>(IReadOnlyList<Guid> toConnectionIds);
}

public abstract class HubGroupRepository
{
public abstract ValueTask<IGroup> AddAsync(string name);
}