RPC(Remote Procedure Call)は、分散システムの異なるノード間で広く使用されている通信方法であり、インターネット時代の基盤技術です。 Go の標準ライブラリは、net/rpc パッケージで RPC の単純な実装を提供します。この記事は、net/rpc パッケージを使用して単純な RPC インターフェイスを実装する手順を説明することで、RPC を理解できるようにすることを目的としています。
この記事は、中型 MPP プランで初めて公開されました。あなたが Medium ユーザーであれば、Medium で私をフォローしてください。誠にありがとうございます。
net/rpc で関数をリモートから呼び出せるようにするには、次の 5 つの条件を満たす必要があります。
- メソッドの型がエクスポートされます。
- メソッドがエクスポートされます。
- メソッドには 2 つの引数があり、どちらもエクスポート (または組み込み) 型です。
- メソッドの 2 番目の引数はポインタです。
- メソッドの戻り値の型はエラーです。
つまり、関数シグネチャは次のようにする必要があります:
func (t *T) MethodName(argType T1, replyType *T2) error
これらの 5 つの条件に基づいて、単純な RPC インターフェイスを構築できます。
type HelloService struct{} func (p *HelloService) Hello(request string, reply *string) error { log.Println("HelloService Hello") *reply = "hello:" + request return nil }
次に、HelloService タイプのオブジェクトを 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) } }
クライアント側の実装は次のとおりです:
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) }
まず、クライアントは rpc.Dial を使用して RPC サービスにダイヤルし、次に client.Call() を介して特定の RPC メソッドを呼び出します。最初のパラメータは RPC サービス名とメソッド名をドットで組み合わせたもので、2 番目は入力、3 番目は戻り値 (ポインタ) です。この例は、RPC の使用がいかに簡単であるかを示しています。
サーバー コードとクライアント コードの両方で、RPC サービス名 HelloService とメソッド名 Hello を覚えておく必要があります。これは開発中にエラーにつながりやすいため、共通部分を抽象化することでコードを少しラップすることができます。完全なコードは次のとおりです:
// 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) }
見覚えはありますか?
デフォルトでは、Go の標準 RPC ライブラリは Go 独自の Gob エンコーディングを使用します。ただし、Protobuf や JSON などの他のエンコーディングをその上に実装するのは簡単です。標準ライブラリはすでに jsonrpc エンコーディングをサポートしており、サーバーとクライアントのコードに若干の変更を加えることで JSON エンコーディングを実装できます。
// 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 }
JSON リクエスト データ オブジェクトは、内部的に 2 つの構造に対応しています。クライアント側では clientRequest、サーバー側ではserverRequest です。 clientRequest 構造とserverRequest 構造の内容は本質的に同じです:
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"` }
ここで、Method は、serviceName と Method から構成されるサービス名を表します。 Params の最初の要素はパラメーターであり、Id は呼び出し元によって維持される一意の呼び出し番号であり、同時シナリオでリクエストを区別するために使用されます。
nc を使用してサーバーをシミュレートし、クライアント コードを実行して、JSON エンコードされたクライアントがサーバーに送信する情報を確認できます。
nc -l 1234
nc コマンドは次のデータを受け取ります:
{"method":"HelloService.Hello","params":["hello"],"id":0}
これは、serverRequest と一致します。
サーバー コードを実行し、NC を使用してリクエストを送信することもできます。
echo -e '{"method":"HelloService.Hello","params":["Hello"],"Id":1}' | nc localhost 1234 --- {"id":1,"result":"hello:Hello","error":null}
この記事では、Go の標準ライブラリの rpc パッケージを紹介し、そのシンプルさと強力なパフォーマンスを強調しました。多くのサードパーティ rpc ライブラリは、rpc パッケージ上に構築されています。この記事は、RPC 研究に関するシリーズの最初の記事として機能します。次の記事では、protobuf と RPC を組み合わせて、最終的には独自の RPC フレームワークを実装します。
以上がRPC アクション EPIGo で単純な RPC インターフェイスを実装するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。