RPC (Remote Procedure Call) est une méthode de communication largement utilisée entre différents nœuds dans les systèmes distribués et une technologie fondamentale de l'ère Internet. La bibliothèque standard de Go fournit une implémentation simple de RPC sous le package net/rpc. Cet article vise à vous aider à comprendre RPC en vous guidant dans la mise en œuvre d'une interface RPC simple à l'aide du package net/rpc.
Cet article a été publié pour la première fois dans le plan Medium MPP. Si vous êtes un utilisateur Medium, veuillez me suivre sur Medium. Merci beaucoup.
Pour permettre à une fonction d'être appelée à distance dans net/rpc, elle doit remplir les cinq conditions suivantes :
- Le type de la méthode est exporté.
- La méthode est exportée.
- La méthode a deux arguments, qui sont tous deux des types exportés (ou intégrés).
- Le deuxième argument de la méthode est un pointeur.
- La méthode a un type d'erreur de retour.
En d'autres termes, la signature de la fonction doit être :
func (t *T) MethodName(argType T1, replyType *T2) error
Sur la base de ces cinq conditions, nous pouvons construire une interface RPC simple :
type HelloService struct{} func (p *HelloService) Hello(request string, reply *string) error { log.Println("HelloService Hello") *reply = "hello:" + request return nil }
Ensuite, vous pouvez enregistrer un objet de type HelloService en tant que service RPC :
func main() { _ = rpc.RegisterName("HelloService", new(HelloService)) listener, err := net.Listen("tcp", ":1234") if err != nil { log.Fatal("ListenTCP error:", err) } for { conn, err := listener.Accept() if err != nil { log.Fatal("Accept error:", err) } go rpc.ServeConn(conn) } }
L'implémentation côté client est la suivante :
func main() { conn, err := net.Dial("tcp", ":1234") if err != nil { log.Fatal("net.Dial:", err) } client := rpc.NewClient(conn) var reply string err = client.Call("HelloService.Hello", "hello", &reply) if err != nil { log.Fatal(err) } fmt.Println(reply) }
Tout d'abord, le client appelle le service RPC à l'aide de rpc.Dial, puis invoque une méthode RPC spécifique via client.Call(). Le premier paramètre est le nom du service RPC et le nom de la méthode combinés avec un point, le deuxième est l'entrée et le troisième est la valeur de retour, qui est un pointeur. Cet exemple montre à quel point il est facile d'utiliser RPC.
Dans le code serveur et client, nous devons nous souvenir du nom du service RPC HelloService et du nom de la méthode Hello. Cela peut facilement conduire à des erreurs lors du développement, nous pouvons donc envelopper légèrement le code en faisant abstraction des parties communes. Le code complet est le suivant :
// server.go const ServerName = "HelloService" type HelloServiceInterface = interface { Hello(request string, reply *string) error } func RegisterHelloService(srv HelloServiceInterface) error { return rpc.RegisterName(ServerName, srv) } type HelloService struct{} func (p *HelloService) Hello(request string, reply *string) error { log.Println("HelloService Hello") *reply = "hello:" + request return nil } func main() { _ = RegisterHelloService(new(HelloService)) listener, err := net.Listen("tcp", ":1234") if err != nil { log.Fatal("ListenTCP error:", err) } for { conn, err := listener.Accept() if err != nil { log.Fatal("Accept error:", err) } go rpc.ServeConn(conn) } }
// client.go type HelloServiceClient struct { *rpc.Client } var _ HelloServiceInterface = (*HelloServiceClient)(nil) const ServerName = "HelloService" func DialHelloService(network, address string) (*HelloServiceClient, error) { conn, err := net.Dial(network, address) client := rpc.NewClient(conn) if err != nil { return nil, err } return &HelloServiceClient{Client: client}, nil } func (p *HelloServiceClient) Hello(request string, reply *string) error { return p.Client.Call(ServerName+".Hello", request, reply) } func main() { client, err := DialHelloService("tcp", "localhost:1234") if err != nil { log.Fatal("net.Dial:", err) } var reply string err = client.Hello("hello", &reply) if err != nil { log.Fatal(err) } fmt.Println(reply) }
Est-ce que cela vous semble familier ?
Par défaut, la bibliothèque RPC standard de Go utilise l'encodage Gob propriétaire de Go. Cependant, il est simple d'implémenter d'autres encodages, tels que Protobuf ou JSON, par-dessus. La bibliothèque standard prend déjà en charge le codage jsonrpc, et nous pouvons implémenter le codage JSON en apportant des modifications mineures au code serveur et client.
// server.go func main() { _ = rpc.RegisterName("HelloService", new(HelloService)) listener, err := net.Listen("tcp", ":1234") if err != nil { log.Fatal("ListenTCP error:", err) } for { conn, err := listener.Accept() if err != nil { log.Fatal("Accept error:", err) } go rpc.ServeCodec(jsonrpc.NewServerCodec(conn)) //go rpc.ServeConn(conn) } } //client.go func DialHelloService(network, address string) (*HelloServiceClient, error) { conn, err := net.Dial(network, address) //client := rpc.NewClient(conn) client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn)) if err != nil { return nil, err } return &HelloServiceClient{Client: client}, nil }
L'objet de données de requête JSON correspond en interne à deux structures : côté client, c'est clientRequest, et côté serveur, c'est serverRequest. Le contenu des structures clientRequest et serverRequest est essentiellement le même :
type clientRequest struct { Method string `json:"method"` Params [1]any `json:"params"` Id uint64 `json:"id"` } type serverRequest struct { Method string `json:"method"` Params *json.RawMessage `json:"params"` Id *json.RawMessage `json:"id"` }
Ici, Method représente le nom du service composé de serviceName et Method. Le premier élément de Params est le paramètre, et Id est un numéro d'appel unique conservé par l'appelant, utilisé pour distinguer les demandes dans des scénarios simultanés.
Nous pouvons utiliser nc pour simuler le serveur, puis exécuter le code client pour voir quelles informations le client codé en JSON envoie au serveur :
nc -l 1234
La commande nc reçoit les données suivantes :
{"method":"HelloService.Hello","params":["hello"],"id":0}
Ceci est cohérent avec serverRequest.
Nous pouvons également exécuter le code du serveur et utiliser nc pour envoyer une requête :
echo -e '{"method":"HelloService.Hello","params":["Hello"],"Id":1}' | nc localhost 1234 --- {"id":1,"result":"hello:Hello","error":null}
Cet article présente le package rpc de la bibliothèque standard de Go, soulignant sa simplicité et ses performances puissantes. De nombreuses bibliothèques rpc tierces sont construites sur le package rpc. Cet article constitue le premier volet d’une série sur la recherche RPC. Dans le prochain article, nous combinerons protobuf avec RPC et éventuellement implémenterons notre propre framework RPC.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!