// Receive from udp socket and push value to subscribers. async void RunReceiveLoop() { var token = cancellationTokenSource.Token; var udpServer = server.Value; while (!token.IsCancellationRequested) { ReadOnlyMemory <byte> value; try { value = await udpServer.ReceiveAsync(token).ConfigureAwait(false); if (value.Length == 0) { return; // invalid data? } var len = MessageBuilder.FetchMessageLength(value.Span); if (len != value.Length - 4) { throw new InvalidOperationException("Receive invalid message size."); } value = value.Slice(4); } catch (Exception ex) { if (ex is OperationCanceledException) { return; } if (token.IsCancellationRequested) { return; } // network error, terminate. options.UnhandledErrorHandler("network error, receive loop will terminate." + Environment.NewLine, ex); return; } try { var message = MessageBuilder.ReadPubSubMessage(value.ToArray()); publisher.Publish(message, message, CancellationToken.None); } catch (Exception ex) { if (ex is OperationCanceledException) { return; } if (token.IsCancellationRequested) { return; } options.UnhandledErrorHandler("", ex); } } }
// Receive from udp socket and push value to subscribers. async void RunReceiveLoop(Stream pipeStream, Func <CancellationToken, Task>?waitForConnection) { RECONNECT: var token = cancellationTokenSource.Token; if (waitForConnection != null) { try { await waitForConnection(token).ConfigureAwait(false); } catch (IOException) { return; // connection closed. } } var buffer = new byte[65536]; while (!token.IsCancellationRequested) { ReadOnlyMemory <byte> value = Array.Empty <byte>(); try { var readLen = await pipeStream.ReadAsync(buffer, 0, buffer.Length, token).ConfigureAwait(false); if (readLen == 0) { if (waitForConnection != null) { server.Value.Dispose(); server = CreateLazyServerStream(); pipeStream = server.Value; goto RECONNECT; // end of stream(disconnect, wait reconnect) } } var messageLen = MessageBuilder.FetchMessageLength(buffer); if (readLen == (messageLen + 4)) { value = buffer.AsMemory(4, messageLen); // skip length header } else { // read more if (buffer.Length < (messageLen + 4)) { Array.Resize(ref buffer, messageLen + 4); } var remain = messageLen - (readLen - 4); await ReadFullyAsync(buffer, pipeStream, readLen, remain, token).ConfigureAwait(false); value = buffer.AsMemory(4, messageLen); } } catch (IOException) { return; // connection closed. } catch (Exception ex) { if (ex is OperationCanceledException) { return; } if (token.IsCancellationRequested) { return; } // network error, terminate. options.UnhandledErrorHandler("network error, receive loop will terminate." + Environment.NewLine, ex); return; } try { var message = MessageBuilder.ReadPubSubMessage(value.ToArray()); // can avoid copy? switch (message.MessageType) { case MessageType.PubSub: publisher.Publish(message, message, CancellationToken.None); break; case MessageType.RemoteRequest: { // NOTE: should use without reflection(Expression.Compile) var header = Deserialize <RequestHeader>(message.KeyMemory, options.MessagePackSerializerOptions); var(mid, reqTypeName, resTypeName) = (header.MessageId, header.RequestType, header.ResponseType); byte[] resultBytes; try { var t = AsyncRequestHandlerRegistory.Get(reqTypeName, resTypeName); var interfaceType = t.GetInterfaces().Where(x => x.IsGenericType && x.Name.StartsWith("IAsyncRequestHandler")) .First(x => x.GetGenericArguments().Any(x => x.FullName == header.RequestType)); var coreInterfaceType = t.GetInterfaces().Where(x => x.IsGenericType && x.Name.StartsWith("IAsyncRequestHandlerCore")) .First(x => x.GetGenericArguments().Any(x => x.FullName == header.RequestType)); var service = provider.GetRequiredService(interfaceType); // IAsyncRequestHandler<TRequest,TResponse> var genericArgs = interfaceType.GetGenericArguments(); // [TRequest, TResponse] var request = MessagePackSerializer.Deserialize(genericArgs[0], message.ValueMemory, options.MessagePackSerializerOptions); var responseTask = coreInterfaceType.GetMethod("InvokeAsync") !.Invoke(service, new[] { request, CancellationToken.None }); var task = typeof(ValueTask <>).MakeGenericType(genericArgs[1]).GetMethod("AsTask") !.Invoke(responseTask, null); await((System.Threading.Tasks.Task)task !); // Task<T> -> Task var result = task.GetType().GetProperty("Result") !.GetValue(task); resultBytes = MessageBuilder.BuildRemoteResponseMessage(mid, genericArgs[1], result !, options.MessagePackSerializerOptions); } catch (Exception ex) { // NOTE: ok to send stacktrace? resultBytes = MessageBuilder.BuildRemoteResponseError(mid, ex.ToString(), options.MessagePackSerializerOptions); } await pipeStream.WriteAsync(resultBytes, 0, resultBytes.Length).ConfigureAwait(false); } break; case MessageType.RemoteResponse: case MessageType.RemoteError: { var mid = Deserialize <int>(message.KeyMemory, options.MessagePackSerializerOptions); if (responseCompletions.TryRemove(mid, out var tcs)) { if (message.MessageType == MessageType.RemoteResponse) { tcs.TrySetResult(message); // synchronous completion, use memory buffer immediately. } else { var errorMsg = MessagePackSerializer.Deserialize <string>(message.ValueMemory, options.MessagePackSerializerOptions); tcs.TrySetException(new RemoteRequestException(errorMsg)); } } } break; default: break; } } catch (IOException) { return; // connection closed. } catch (Exception ex) { if (ex is OperationCanceledException) { continue; } options.UnhandledErrorHandler("", ex); } } }
// Receive from tcp socket and push value to subscribers. async void RunReceiveLoop(SocketTcpClient client) { var token = cancellationTokenSource.Token; var buffer = new byte[65536]; ReadOnlyMemory <byte> readBuffer = Array.Empty <byte>(); while (!token.IsCancellationRequested) { ReadOnlyMemory <byte> value = Array.Empty <byte>(); try { if (readBuffer.Length == 0) { var readLen = await client.ReceiveAsync(buffer, 0, buffer.Length, token).ConfigureAwait(false); if (readLen == 0) { return; // end of stream(disconnect) } readBuffer = buffer.AsMemory(0, readLen); } else if (readBuffer.Length < 4) // rare case { var readLen = await client.ReceiveAsync(buffer, 0, buffer.Length, token).ConfigureAwait(false); if (readLen == 0) { return; } var newBuffer = new byte[readBuffer.Length + readLen]; readBuffer.CopyTo(newBuffer); buffer.AsSpan(readLen).CopyTo(newBuffer.AsSpan(readBuffer.Length)); readBuffer = newBuffer; } var messageLen = MessageBuilder.FetchMessageLength(readBuffer.Span); if (readBuffer.Length == (messageLen + 4)) // just size { value = readBuffer.Slice(4, messageLen); // skip length header readBuffer = Array.Empty <byte>(); goto PARSE_MESSAGE; } else if (readBuffer.Length > (messageLen + 4)) // over size { value = readBuffer.Slice(4, messageLen); readBuffer = readBuffer.Slice(messageLen + 4); goto PARSE_MESSAGE; } else // needs to read more { var readLen = readBuffer.Length; if (readLen < (messageLen + 4)) { if (readBuffer.Length != buffer.Length) { var newBuffer = new byte[buffer.Length]; readBuffer.CopyTo(newBuffer); buffer = newBuffer; } if (buffer.Length < messageLen + 4) { Array.Resize(ref buffer, messageLen + 4); } } var remain = messageLen - (readLen - 4); await ReadFullyAsync(buffer, client, readLen, remain, token).ConfigureAwait(false); value = buffer.AsMemory(4, messageLen); readBuffer = Array.Empty <byte>(); goto PARSE_MESSAGE; } } catch (Exception ex) { if (ex is OperationCanceledException) { return; } if (token.IsCancellationRequested) { return; } // network error, terminate. options.UnhandledErrorHandler("network error, receive loop will terminate." + Environment.NewLine, ex); return; } PARSE_MESSAGE: try { var message = MessageBuilder.ReadPubSubMessage(value.ToArray()); // can avoid copy? switch (message.MessageType) { case MessageType.PubSub: publisher.Publish(message, message, CancellationToken.None); break; case MessageType.RemoteRequest: { // NOTE: should use without reflection(Expression.Compile) var header = Deserialize <RequestHeader>(message.KeyMemory, options.MessagePackSerializerOptions); var(mid, reqTypeName, resTypeName) = (header.MessageId, header.RequestType, header.ResponseType); byte[] resultBytes; try { var t = AsyncRequestHandlerRegistory.Get(reqTypeName, resTypeName); var interfaceType = t.GetInterfaces().First(x => x.IsGenericType && x.Name.StartsWith("IAsyncRequestHandler")); var coreInterfaceType = t.GetInterfaces().First(x => x.IsGenericType && x.Name.StartsWith("IAsyncRequestHandlerCore")); var service = provider.GetRequiredService(interfaceType); // IAsyncRequestHandler<TRequest,TResponse> var genericArgs = interfaceType.GetGenericArguments(); // [TRequest, TResponse] // Unity IL2CPP does not work(can not invoke nongenerics MessagePackSerializer) var request = MessagePackSerializer.Deserialize(genericArgs[0], message.ValueMemory, options.MessagePackSerializerOptions); var responseTask = coreInterfaceType.GetMethod("InvokeAsync") !.Invoke(service, new[] { request, CancellationToken.None }); #if !UNITY_2018_3_OR_NEWER var task = typeof(ValueTask <>).MakeGenericType(genericArgs[1]).GetMethod("AsTask") !.Invoke(responseTask, null); #else var asTask = typeof(UniTaskExtensions).GetMethods().First(x => x.IsGenericMethod && x.Name == "AsTask") .MakeGenericMethod(genericArgs[1]); var task = asTask.Invoke(null, new[] { responseTask }); #endif await((System.Threading.Tasks.Task)task !); // Task<T> -> Task var result = task.GetType().GetProperty("Result") !.GetValue(task); resultBytes = MessageBuilder.BuildRemoteResponseMessage(mid, genericArgs[1], result !, options.MessagePackSerializerOptions); } catch (Exception ex) { // NOTE: ok to send stacktrace? resultBytes = MessageBuilder.BuildRemoteResponseError(mid, ex.ToString(), options.MessagePackSerializerOptions); } await client.SendAsync(resultBytes).ConfigureAwait(false); } break; case MessageType.RemoteResponse: case MessageType.RemoteError: { var mid = Deserialize <int>(message.KeyMemory, options.MessagePackSerializerOptions); if (responseCompletions.TryRemove(mid, out var tcs)) { if (message.MessageType == MessageType.RemoteResponse) { tcs.TrySetResult(message); // synchronous completion, use memory buffer immediately. } else { var errorMsg = MessagePackSerializer.Deserialize <string>(message.ValueMemory, options.MessagePackSerializerOptions); tcs.TrySetException(new RemoteRequestException(errorMsg)); } } } break; default: break; } } catch (Exception ex) { if (ex is OperationCanceledException) { continue; } options.UnhandledErrorHandler("", ex); } } }