The Quick-Start guide provides an introduction to developing a simple tRPC service. This article is an advanced piece that will delve into the considerations and tasks involved in server-side program development. If your service requires downstream calls, please read the Client Development Guide after going through this article.
See How to choose and configure the runtime
A Protobuf Service is a logical combination of a set of interfaces. It requires defines as follows:
package
nameservice
namerpc
name- the
message
types for interface requests and responses.
The IDL language can describe interfaces in a programming language-independent manner, and use tools to convert IDL files into stub code in a specific language, allowing developers to focus on business logic development. For services using IDL protocol types, the definition of Protobuf Service typically involves the following five steps (using tRPC protocol as an example):
-
Define services through proto IDL file
syntax = "proto3"; package trpc.test.helloworld; service Greeter { rpc SayHello (HelloRequest) returns (HelloReply) {} } message HelloRequest { string msg = 1; } message HelloReply { string msg = 1; }
-
Generate project code based on proto IDL file
Refer to the steps in the Quick Start Guide.
-
Implement server-side logic
Refer to the steps in the Quick Start Guide.
-
Register the Proto Service to the Server
This step has already been done in the generated stub code, and the specific code can be found in helloworld_server.cc
:
int helloworldServer::Initialize() {
std::string service_name("trpc.");
service_name += trpc::TrpcConfig::GetInstance()->GetServerConfig().app;
service_name += ".";
service_name += trpc::TrpcConfig::GetInstance()->GetServerConfig().server;
service_name += ".Greeter";
TRPC_LOG_INFO("service name1:" << service_name);
trpc::ServicePtr my_service(new GreeterServiceImpl());
// The service_name needs to correspond to the service/name in the configuration file to associate the service configuration.
RegisterService(service_name, my_service);
return 0;
}
Next, the user needs to implement the specific interfaces in greeter_service.cc, referring to the steps in the Quick Start Guide.
One of the common protocols is the HTTP protocol. For detailed information, please refer to the HTTP Service Development Guide.
As a server, the framework configuration file needs to provide configurations for both the global
and server
sections. The plugins
section should be configured based on the plugins being used.
Here is an example configuration for using the fiber M:N coroutine runtime:
global:
local_ip: xxx.xxx.xxx.xxx # local ip of application, optional
threadmodel:
fiber:
- instance_name: fiber_instance
concurrency_hint: 8
server:
app: test # application name, optional
server: helloworld # module name, optional
service: # services associated with the server, required
- name: trpc.test.helloworld.Greeter # service name, required
ip: xxx.xxx.xxx.xxx # bind ip, required
port: 10001 # bind port, required
protocol: trpc # the application layer protocol used
plugins:
log:
default:
- name: default
sinks:
local_file:
filename: /usr/local/trpc/bin/server.log
Sometimes, when the server receives a request, we need to asynchronously execute certain tasks and then send the response after the tasks are completed. This is where the framework's asynchronous response feature comes into play. The usage is as follows:
::trpc::Status GreeterServiceImpl::SayHello(::trpc::ServerContextPtr context,
const ::trpc::demo::helloworld::HelloRequest* request,
::trpc::demo::helloworld::HelloReply* reply) {
// 1. Set to use asynchronous response mode.
context->SetResponse(false);
// 2. Do async work. Here, `DoAsyncWork` returns a Future.
DoAsyncWork(...).Then([context](){
::trpc::test::helloworld::HelloReply rsp;
rsp.set_msg("xxx");
// 3. Call `SendUnaryResponse` to send response when the asynchronous task is completed.
context->SendUnaryResponse(::trpc::kSuccStatus, rsp);
return ::trpc::MakeReadyFuture<>();
});
return ::trpc::kSuccStatus;
}
Note: If you don't need to set a response body and only want to return the status, you can use the interface with only the Status parameter below:
void SendUnaryResponse(const Status& status);
For the built-in trpc and http protocols in the framework, the maximum length of request packets is limited to 10MB. This limitation is at the service level, users can modify it by increasing the max_packet_size
configuration option for the service. The procedure is as follows:
server:
service:
- name: trpc.test.helloworld.Greeter
max_packet_size: 10000000 # max packet size limited
The default connection timeout for the server is 60 seconds. If the caller does not send any data for 60 seconds continuously, the connection will be disconnected. This limitation is at the service level and can be modified using the idle_time
configuration option:
server:
service:
- name: trpc.test.helloworld.Greeter
idle_time: 60000 # connection idle timeout
By default, the framework does not automatically register or unregister service instances to the name service system. To enable it, the following configuration is required:
server:
registry_name: xxx # the specified name service system
enable_self_register: true # Enable framework's self-registration
The framework supports reporting the heartbeat of service instances to the name service system to indicate the availability status of the service itself. To use this feature, the following configuration needs to be added:
server:
registry_name: xxx # the specified name service system
For the IO/Handle separation and merging thread models, the framework supports dead detection of worker threads (IO and Handle threads). The principle is as follows: the worker threads of framework will mark their own active status every 3 seconds. If a thread has not marked itself as active for a threshold period (60 seconds), it is considered dead.
If all Handle threads are detected as dead in the separation mode or all worker threads are detected as dead in the merging mode, the service is considered unavailable. At this point, the heartbeat reporting to the name service system will be stopped. This allows the caller to promptly perceive the service status through the name service system when the service is unavailable, to avoid calling unavailable service nodes, providing a protective effect.
For services such as offline computing, the request processing time may exceed 60 seconds, leading to false positives. To address this, the timeout threshold can be increased:
global:
heartbeat:
thread_heartbeat_time_out: 60000 # Threshold in milliseconds to determine the dead state of Handle threads.
Additionally, if heartbeat reporting is not required, it can be disabled using the following method:
heartbeat:
enable_heartbeat: false # Heartbeat reporting switch, default value is true.
The 'fork' function has a historical pitfall in multi-threaded environments. Please read the related article to understand this premise.
tRPC-Cpp is a multi-threaded framework that only supports one usage of 'fork': 'fork' with 'exec', meaning that 'exec' functions are immediately called after 'fork'.
It does not support 'fork' without 'exec'. If you must use it in this way, please use 'fork' as early as possible (before framework initialization).
When using 'fork' with 'exec', if 'exec' returns, it indicates a failed execution. Please call '_exit()' immediately to exit the child process instead of using 'exit()'. This is done to avoid undefined behavior caused by the destruction of certain singletons or global variables (such as handling threads created by the parent process in the child process).
See Service Development for tRPC Protocol Type.
See Service Development for HTTP Protocol Type.
See Service Development for gRPC Protocol Type.
See Fixed thread.
See Timeout control.
See Transparent proxy.