Silky Microservice FrameworkSilky Microservice Framework
Home
Docs
Config
Source
github
gitee
  • 简体中文
  • English
Home
Docs
Config
Source
github
gitee
  • 简体中文
  • English
  • Startup

    • Silky Framework Source Code Analysis
    • Host Construction
    • Service Engine
    • Module System
    • Service & Service Entry Resolution
    • Service Registration
    • Dependency Injection Conventions
    • RPC Service Proxy
  • Runtime

    • Endpoints & Routing
    • Executor Dispatch System
    • Local Executor & Server-Side Filters
    • Remote Executor & RPC Call Chain
    • RPC Server Message Handling
    • Service Governance
    • Cache Interceptor
    • Distributed Transactions (TCC)
    • HTTP Gateway Pipeline
    • Filter Pipeline
    • Polly Resilience Pipeline
    • Endpoint Health Monitor

Overview

Silky's RPC communication is built on DotNetty (.NET port of Netty), using long-lived TCP connections to transport serialized messages. The server listens on a dedicated RPC port (default 2200), receives call requests from other microservice instances, executes the business logic, and writes the result back to the connection.

DotNetty Channel (network layer)
    │  Receives TCP byte stream
    ▼
DecoderHandler
    │  TransportMessageDecoder: bytes → TransportMessage
    ▼
ServerHandler (dispatch layer)
    │  Passes TransportMessage to MessageListenerBase
    ▼
DefaultServerMessageReceivedHandler (processing layer)
    │  Locate ServiceEntry → parse parameters → execute → build response
    ▼
EncoderHandler
    │  TransportMessageEncoder: RemoteResultMessage → bytes
    ▼
DotNetty Channel writes response back

DotNetty Server Startup

DotNettyTcpServerMessageListener starts the DotNetty server during module Initialize():

public async Task Listen()
{
    var bootstrap = new ServerBootstrap();

    // I/O model selection
    if (_rpcOptions.UseLibuv)
    {
        m_bossGroup = new DispatcherEventLoopGroup();
        m_workerGroup = new WorkerEventLoopGroup((DispatcherEventLoopGroup)m_bossGroup);
        bootstrap.Channel<TcpServerChannel>();
    }
    else
    {
        m_bossGroup = new MultithreadEventLoopGroup(1);       // 1 Accept thread
        m_workerGroup = new MultithreadEventLoopGroup();     // CPU cores × 2 Worker threads
        bootstrap.Channel<TcpServerSocketChannel>();
    }

    bootstrap
        .Group(m_bossGroup, m_workerGroup)
        .Option(ChannelOption.SoBacklog, 128)
        .ChildHandler(new ActionChannelInitializer<IChannel>(channel =>
        {
            var pipeline = channel.Pipeline;

            // Optional TLS
            if (_rpcOptions.IsSsl)
            {
                var cert = new X509Certificate2(
                    _rpcOptions.SslCertificateName,
                    _rpcOptions.SslCertificatePassword);
                pipeline.AddLast(TlsHandler.Server(cert));
            }

            // Idle detection (heartbeat)
            pipeline.AddLast(new IdleStateHandler(0, 0, _rpcOptions.HeartbeatWatchIntervalSeconds));

            // Framing (length-prefix protocol)
            pipeline.AddLast(new LengthFieldPrepender(4));
            pipeline.AddLast(new LengthFieldBasedFrameDecoder(int.MaxValue, 0, 4, 0, 4));

            // Message codec
            pipeline.AddLast(_transportMessageDecoder);
            pipeline.AddLast(_transportMessageEncoder);

            // Business handler
            pipeline.AddLast(new ServerHandler(async (ctx, message) =>
            {
                await OnReceived(ctx.Channel, message);
            }));
        }));

    m_boundChannel = await bootstrap.BindAsync(_server.RpcEndpoint.Port);
}

Channel Pipeline Summary

HandlerRole
TlsHandler (optional)SSL/TLS encryption/decryption
IdleStateHandlerIdle detection — triggers heartbeat events after inactivity
LengthFieldPrepender / LengthFieldBasedFrameDecoderLength-prefix framing — prevents TCP packet fragmentation/sticking
TransportMessageDecoderDeserializes JSON bytes into TransportMessage
TransportMessageEncoderSerializes TransportMessage into JSON bytes
ServerHandlerDispatches received messages to DefaultServerMessageReceivedHandler

TransportMessage — Wire Format

Every RPC message (request and response) is wrapped in a TransportMessage:

FieldTypeDescription
IdstringUUID — used to correlate responses to requests
ContentTypestringMessage content type (e.g., RemoteInvokeMessage, RemoteResultMessage)
ContentstringJSON-serialized payload

DefaultServerMessageReceivedHandler

This handler executes on the worker thread after a message is decoded:

public async Task ReceivedAsync(IMessageSender sender, TransportMessage message)
{
    // 1. Deserialize the RemoteInvokeMessage from message.Content
    var invokeMessage = message.GetContent<RemoteInvokeMessage>();

    // 2. Locate the ServiceEntry by ServiceEntryId
    var serviceEntry = _serviceEntryLocator.GetServiceEntryById(invokeMessage.ServiceEntryId);

    // 3. Populate RpcContext from message attachments (UserId, TenantId, TraceId, ...)
    RpcContext.Context.SetAttachments(invokeMessage.Attachments);
    RpcContext.Context.SetTransAttachments(invokeMessage.TransAttachments);

    // 4. Parse parameters according to ParameterDescriptors
    var parameters = _parameterResolver.Resolve(serviceEntry, invokeMessage);

    // 5. Execute (local executor)
    var result = await _executor.Execute(serviceEntry, parameters, invokeMessage.ServiceKey);

    // 6. Build and send RemoteResultMessage
    var resultMessage = new RemoteResultMessage { Result = result };
    await sender.SendMessageAsync(
        new TransportMessage(message.Id, resultMessage)); // same UUID for correlation
}

RpcContext — Implicit Call Chain Propagation

RpcContext is a thread-local (actually AsyncLocal) context that carries cross-cutting data across the call chain without explicit parameter threading:

KeyDescription
UserIdCurrent authenticated user ID
TenantIdCurrent tenant ID (multi-tenancy)
TraceIdDistributed tracing correlation ID
ServiceKeyTarget service implementation key
TransactionContextTCC distributed transaction context

Propagation: On the client side, RpcContext.Attachments are copied into RemoteInvokeMessage.Attachments before sending. On the server side, they are restored from the message into the server-side RpcContext — transparently propagating user identity, tenant, and trace information across microservice boundaries.

Edit this page
Prev
Remote Executor & RPC Call Chain
Next
Service Governance