콘텐츠로 이동

iOS에서 NativeWebSocket Close 호출 시 예외가 발생하는 문제

NativeWebSocket의 Close 처리는 Android와 iOS에서 다르게 동작한다.

모바일 카지노 게임의 소켓은 NativeWebSocket 라이브러리를 사용하고 있고, 이 문제는 NativeWebSocket issue #67에 리포트되어 있다.

이 이슈에서는 iOS에서 불필요한 지연으로 Close 이벤트가 늦게 도착하는 것을 원인으로 설명한다.

근본적인 해결 방법은 패키지를 바꾸거나 업데이트하는 것이다. 하지만 현재 서비스 흐름에서는 게임에서 로비로 돌아갈 때 소켓을 다시 생성하고 처리하므로, try-catch 블록으로 예외를 잡아 회피했다.

// Before
await m_Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, m_CancellationToken);
// After
try
{
await m_Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, m_CancellationToken);
}
catch(Exception ex)
{
Debug.LogWarning($"WebSocket Exception {ex.Message}");
}

아래는 로그 전문이다.

<color=orange>[WS] Closed Web Socket code[Abnormal] dns[wss://chat-server.example.com/]</color>
Game.Common.WebSocketService:OnCloseSocket(WebSocketHandle, WebSocketCloseCode)
Game.Common.<>c__DisplayClass11_0:<_CreateSocket>b__1(WebSocketCloseCode)
NativeWebSocket.<Receive>d__37:MoveNext()
System.Threading.ExecutionContext:RunInternal(ExecutionContext, ContextCallback, Object, Boolean)
System.Runtime.CompilerServices.MoveNextRunner:Run()
<CoroutineWrapper>d__4:MoveNext()
UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)
<color=orange>CHAT: OnWSClose</color>
Game.CHAT.ChatSocket:OnWSClose()
Game.CHAT.<>c__DisplayClass3_0:<ConnectWebSocket>b__2()
Game.Common.WebSocketService:OnCloseSocket(WebSocketHandle, WebSocketCloseCode)
Game.Common.<>c__DisplayClass11_0:<_CreateSocket>b__1(WebSocketCloseCode)
NativeWebSocket.<Receive>d__37:MoveNext()
System.Threading.ExecutionContext:RunInternal(ExecutionContext, ContextCallback, Object, Boolean)
System.Runtime.CompilerServices.MoveNextRunner:Run()
<CoroutineWrapper>d__4:MoveNext()
UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)
SocketException: Connection timed out
Rethrow as IOException: Unable to read data from the transport connection: Connection timed out.
at System.Net.Sockets.Socket+AwaitableSocketAsyncEventArgs.ThrowException (System.Net.Sockets.SocketError error) [0x00000] in <00000000000000000000000000000000>:0
at System.Net.Sockets.Socket+AwaitableSocketAsyncEventArgs.GetResult (System.Int16 token) [0x00000] in <00000000000000000000000000000000>:0
at System.Threading.Tasks.ValueTask`1+ValueTaskSourceAsTask+<>c[TResult].<.cctor>b__4_0 (System.Object state) [0x00000] in <00000000000000000000000000000000>:0
at System.Net.Sockets.Socket+<>c.<.cctor>b__367_10 (System.IAsyncResult ares) [0x00000] in <00000000000000000000000000000000>:0
at System.Threading.ThreadPoolWorkQueue.Dispatch () [0x00000] in <00000000000000000000000000000000>:0
--- End of stack trace from previous location where exception was thrown ---
at Mono.Net.Security.MobileAuthenticatedStream.InnerRead (System.Boolean sync, System.Int32 requestedSize, System.Threading.CancellationToken cancellationToken) [0x00000] in <00000000000000000000000000000000>:0
at System.Threading.ExecutionContext.RunInternal (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00000] in <00000000000000000000000000000000>:0
at System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner.Run () [0x00000] in <00000000000000000000000000000000>:0
at System.Threading.Tasks.AwaitTaskContinuation.RunOrScheduleAction (System.Action action, System.Boolean allowInlining, System.Threading.Tasks.Task& currentTask) [0x00000] in <00000000000000000000000000000000>:0
at System.Threading.Tasks.Task.FinishContinuations () [0x00000] in <00000000000000000000000000000000>:0
at System.Threading.Tasks.Task.TrySetException (System.Object exceptionObject) [0x00000] in <00000000000000000000000000000000>:0
at System.Net.Sockets.Socket+<>c.<.cctor>b__367_10 (System.IAsyncResult ares) [0x00000] in <00000000000000000000000000000000>:0
at System.Threading.ThreadPoolWorkQueue.Dispatch () [0x00000] in <00000000000000000000000000000000>:0
--- End of stack trace from previous location where exception was thrown ---