RPC (Remote Procedure Call) ist eine weit verbreitete Kommunikationsmethode zwischen verschiedenen Knoten in verteilten Systemen und eine grundlegende Technologie des Internetzeitalters. Die Standardbibliothek von Go bietet eine einfache Implementierung von RPC unter dem Paket net/rpc. Dieser Artikel soll Ihnen helfen, RPC zu verstehen, indem er Sie durch die Implementierung einer einfachen RPC-Schnittstelle mithilfe des Pakets net/rpc führt.
Dieser Artikel wurde zuerst im Medium MPP-Plan veröffentlicht. Wenn Sie ein Medium-Benutzer sind, folgen Sie mir bitte auf Medium. Vielen Dank.
Damit eine Funktion aus der Ferne in net/rpc aufgerufen werden kann, muss sie die folgenden fünf Bedingungen erfüllen:
- Der Typ der Methode wird exportiert.
- Die Methode wird exportiert.
- Die Methode verfügt über zwei Argumente, die beide exportierte (oder integrierte) Typen sind.
- Das zweite Argument der Methode ist ein Zeiger.
- Die Methode weist einen Fehler vom Typ Rückgabe auf.
Mit anderen Worten, die Funktionssignatur muss wie folgt lauten:
func (t *T) MethodName(argType T1, replyType *T2) error
Basierend auf diesen fünf Bedingungen können wir eine einfache RPC-Schnittstelle erstellen:
type HelloService struct{} func (p *HelloService) Hello(request string, reply *string) error { log.Println("HelloService Hello") *reply = "hello:" + request return nil }
Als nächstes können Sie ein Objekt vom Typ HelloService als RPC-Dienst registrieren:
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) } }
Die clientseitige Implementierung ist wie folgt:
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) }
Zuerst wählt der Client den RPC-Dienst mit rpc.Dial an und ruft dann über client.Call() eine bestimmte RPC-Methode auf. Der erste Parameter ist der RPC-Dienstname und der Methodenname in Kombination mit einem Punkt, der zweite ist die Eingabe und der dritte ist der Rückgabewert, der ein Zeiger ist. Dieses Beispiel zeigt, wie einfach die Verwendung von RPC ist.
Sowohl im Server- als auch im Client-Code müssen wir uns den RPC-Dienstnamen HelloService und den Methodennamen Hello merken. Dies kann während der Entwicklung leicht zu Fehlern führen, sodass wir den Code leicht umbrechen können, indem wir die gemeinsamen Teile abstrahieren. Der vollständige Code lautet wie folgt:
// 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) }
Kommt es Ihnen bekannt vor?
Standardmäßig verwendet die Standard-RPC-Bibliothek von Go die proprietäre Gob-Kodierung. Es ist jedoch problemlos, darüber hinaus auch andere Kodierungen wie Protobuf oder JSON zu implementieren. Die Standardbibliothek unterstützt bereits die JSONRPC-Kodierung, und wir können die JSON-Kodierung implementieren, indem wir geringfügige Änderungen am Server- und Client-Code vornehmen.
// 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 }
Das JSON-Anforderungsdatenobjekt entspricht intern zwei Strukturen: auf der Clientseite ist es clientRequest und auf der Serverseite ist es serverRequest. Der Inhalt der Strukturen clientRequest und serverRequest ist im Wesentlichen derselbe:
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"` }
Methode stellt hier den Dienstnamen dar, der aus serviceName und Methode besteht. Das erste Element von Params ist der Parameter, und Id ist eine eindeutige Rufnummer, die vom Anrufer verwaltet wird und zur Unterscheidung von Anforderungen in gleichzeitigen Szenarien verwendet wird.
Wir können nc verwenden, um den Server zu simulieren und dann den Client-Code ausführen, um zu sehen, welche Informationen der JSON-codierte Client an den Server sendet:
nc -l 1234
Der NC-Befehl empfängt die folgenden Daten:
{"method":"HelloService.Hello","params":["hello"],"id":0}
Dies stimmt mit serverRequest überein.
Wir können auch den Servercode ausführen und nc verwenden, um eine Anfrage zu senden:
echo -e '{"method":"HelloService.Hello","params":["Hello"],"Id":1}' | nc localhost 1234 --- {"id":1,"result":"hello:Hello","error":null}
In diesem Artikel wurde das RPC-Paket aus der Standardbibliothek von Go vorgestellt und dessen Einfachheit und leistungsstarke Leistung hervorgehoben. Viele RPC-Bibliotheken von Drittanbietern bauen auf dem RPC-Paket auf. Dieser Artikel ist der erste Teil einer Reihe zur RPC-Forschung. Im nächsten Artikel werden wir Protobuf mit RPC kombinieren und schließlich unser eigenes RPC-Framework implementieren.
Das obige ist der detaillierte Inhalt vonRPC Action EPIimplementieren Sie eine einfache RPC-Schnittstelle in Go. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!