Définition du service Protobuf
Commencez par décrire l'interface dans un fichier echo.proto. L'option cc_generic_services est nécessaire pour que brpc génère les stubs C++.
syntax = "proto2";
option cc_generic_services = true;
message PacketRequest {
required string payload = 1;
}
message PacketResponse {
required string payload = 1;
}
service EchoService {
rpc Send(PacketRequest) returns (PacketResponse);
}
Générez les fichiers C++ avec :
protoc --cpp_out=. echo.proto
Implémentation du serveur
Le serveur hérite du service généré et fournit l'implémentation de la méthode Send. Le garde brpc::ClosureGuard garantit que done->Run() est appelé en sortie du traitement, même en cas d'erreur.
#include <brpc/server.h>
#include <brpc/controller.h>
#include "echo.pb.h"
class EchoServiceImpl : public EchoService {
public:
void Send(google::protobuf::RpcController* base,
const PacketRequest* req,
PacketResponse* resp,
google::protobuf::Closure* done) override {
brpc::ClosureGuard guard(done);
brpc::Controller* ctl = static_cast<brpc::Controller*>(base);
if (req == nullptr || !req->has_payload()) {
ctl->SetFailed("Requête invalide");
return;
}
LOG(INFO) << "Reçu de " << ctl->remote_side()
<< " : " << req->payload();
resp->set_payload(req->payload());
}
};
int main(int argc, char* argv[]) {
google::ParseCommandLineFlags(&argc, &argv, true);
brpc::Server server;
EchoServiceImpl impl;
if (server.AddService(&impl, brpc::SERVER_DOESNT_OWN_SERVICE) != 0) {
LOG(FATAL) << "Impossible d'ajouter le service";
return -1;
}
brpc::ServerOptions opts;
opts.num_threads = 4;
if (server.Start(8080, &opts) != 0) {
LOG(FATAL) << "Impossible de démarrer le serveur";
return -1;
}
server.RunUntilAskedToQuit();
return 0;
}
Implémentation du client
Un seul brpc::Channel peut être partagé entre plusieurs threads, mais il doit être correctement initialisé avant toute utilisation concurrente.
#include <brpc/channel.h>
#include <brpc/controller.h>
#include "echo.pb.h"
int main(int argc, char* argv[]) {
google::ParseCommandLineFlags(&argc, &argv, true);
brpc::Channel channel;
brpc::ChannelOptions opts;
opts.protocol = "baidu_std";
opts.timeout_ms = 1000;
opts.max_retry = 1;
if (channel.Init("127.0.0.1", 8080, &opts) != 0) {
LOG(FATAL) << "Échec de l'initialisation du canal";
return -1;
}
EchoService_Stub stub(&channel);
PacketRequest req;
PacketResponse resp;
brpc::Controller ctl;
req.set_payload("hello brpc");
stub.Send(&ctl, &req, &resp, nullptr);
if (ctl.Failed()) {
LOG(ERROR) << "RPC en échec (code=" << ctl.ErrorCode()
<< ") : " << ctl.ErrorText();
return -1;
}
LOG(INFO) << "Réponse : " << resp.payload();
return 0;
}
Flux d'une requête brpc
Une invocation client passe par les étapes suivantes :
- Le
Controllerest attaché à la requête et un identifiant de corrélation est créé. - Le canal choisit un serveur cible selon le mode de conenxion (single, pooled, short).
- La requête est sérialisée selon le protocole indiqué (
baidu_stdpar défaut). - Si un délai d'attente est configuré, une minuterie est armée.
- La réponse est reçue, décodée, et
Controller::Failed()reflète le résultat.
Pourquoi aucune réponse n'est reçue après modification du paquet
Le titre mentionne le symptôme « pas de réponse du serveur distant après modification du paquet ». Ce comportement vient généralement d'une altération du message brpc alors que l'infrastructure attend toujours un cadre (framing) et une sérialité valides.
Le protocole baidu_std ajoute un en-tête contenant au moins la taille du message et un champ magique. Si vous modifiez manuellement le contenu sérialisé sans recalculer la taille, le décodeur côté serveur peut :
- ne plus trouver une limite de message valide et fermer la connexion ;
- obtenir une taille incohérente et attendre indéfiniment le reste du paquet ;
- déclencher une erreur de parsing qui ne génère pas de réponse fonctionnelle.
Du point de vue du client, cela se traduit par un Controller::Failed() vrai avec un code d'erreur tel que EEOF, EREQUEST ou ERPCTIMEDOUT, plutôt que par un retour contenant une réponse.
Vérifications recommandées
- Ne modifiez jamais le contenu d'un
IOBufou d'un message Protobuf sérialisé sans reconstruire l'en-tête de taille correspondant. - Utilisez
cntl->ErrorText()etcntl->ErrorCode()pour identifier précisément la cause. - Sur le serveur, si une erreur de traitement est détectée, appelez
cntl->SetFailed(...)avant que ledonene soit exécuté. Le mécanisme deClosureGuardtransmet alors automatiquement l'erreur au client. - Assurez-vous que
done->Run()n'est appelé qu'une seule fois et qu'aucun code asynchrone ne tente d'écrire dans leControlleraprès cet appel.
Exemple de propagation d'ereur côté serveur
void Send(google::protobuf::RpcController* base,
const PacketRequest* req,
PacketResponse* resp,
google::protobuf::Closure* done) override {
brpc::ClosureGuard guard(done);
brpc::Controller* ctl = static_cast<brpc::Controller*>(base);
if (!ValidatePacket(*req)) {
ctl->SetFailed(brpc::EREQUEST, "Paquet modifié ou invalide");
return;
}
resp->set_payload(req->payload());
}
Exemple de récupérasion d'erreur côté client
PacketResponse resp;
brpc::Controller ctl;
req.set_payload("test");
stub.Send(&ctl, &req, &resp, nullptr);
if (ctl.Failed()) {
LOG(WARNING) << "Échec côté " << ctl.remote_side()
<< " code=" << ctl.ErrorCode()
<< " msg=" << ctl.ErrorText();
} else {
LOG(INFO) << "Réponse : " << resp.payload();
}
Diagnostic à l'aide des logs
Activez les traces utiles lors du développement :
./client -logtostderr -minloglevel=0
./server -logtostderr -minloglevel=0
Les messages suivants indiquent souvent un paquet corrompu ou une fermeture prématurée de la connexion :
Got EOF: le client a fermé le socket avant que le serveur ne réponde, typique d'un timeout client ou d'une trame mal formée.Remote side was closed: la connexion a été fermée par le pair, souvent après un échec de parsing.A message ... is bigger than ...: le champ de taille a été écrasé et pointe hors des limites autorisées par-max_body_size.
Commandes complètes de compilation
protoc --cpp_out=. echo.proto
g++ -std=c++11 server.cpp echo.pb.cc \
-lbrpc -lgflags -lprotobuf -lleveldb -lssl -lcrypto -lpthread -o server
g++ -std=c++11 client.cpp echo.pb.cc \
-lbrpc -lgflags -lprotobuf -lleveldb -lssl -lcrypto -lpthread -o client