RPC(Panggilan Prosedur Jauh) ialah kaedah komunikasi yang digunakan secara meluas antara nod berbeza dalam sistem teragih dan teknologi asas era Internet. Pustaka standard Go menyediakan pelaksanaan RPC yang mudah di bawah pakej net/rpc. Artikel ini bertujuan untuk membantu anda memahami RPC dengan memandu anda melaksanakan antara muka RPC yang mudah menggunakan pakej net/rpc.
Artikel ini pertama kali diterbitkan dalam pelan MPP Sederhana. Jika anda pengguna Medium, sila ikuti saya di Medium. Terima kasih banyak-banyak.
Untuk membolehkan fungsi dipanggil dari jauh dalam net/rpc, ia mesti memenuhi lima syarat berikut:
- Jenis kaedah dieksport.
- Kaedah ini dieksport.
- Kaedah ini mempunyai dua argumen, kedua-duanya adalah jenis yang dieksport (atau terbina dalam).
- Hujah kedua kaedah ialah penunjuk.
- Kaedah ini mempunyai jenis ralat pemulangan.
Dalam erti kata lain, tandatangan fungsi mestilah:
func (t *T) MethodName(argType T1, replyType *T2) error
Berdasarkan lima syarat ini, kami boleh membina antara muka RPC yang mudah:
type HelloService struct{} func (p *HelloService) Hello(request string, reply *string) error { log.Println("HelloService Hello") *reply = "hello:" + request return nil }
Seterusnya, anda boleh mendaftarkan objek jenis HelloService sebagai perkhidmatan 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) } }
Pelaksanaan pihak klien adalah seperti berikut:
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) }
Mula-mula, pelanggan mendail perkhidmatan RPC menggunakan rpc.Dial, kemudian menggunakan kaedah RPC tertentu melalui klien.Call(). Parameter pertama ialah nama perkhidmatan RPC dan nama kaedah digabungkan dengan titik, yang kedua ialah input, dan yang ketiga ialah nilai pulangan, iaitu penunjuk. Contoh ini menunjukkan betapa mudahnya menggunakan RPC.
Dalam kedua-dua pelayan dan kod pelanggan, kita perlu mengingati nama perkhidmatan RPC HelloService dan nama kaedah Hello. Ini dengan mudah boleh membawa kepada ralat semasa pembangunan, jadi kita boleh membungkus kod sedikit dengan mengabstrak bahagian biasa. Kod lengkap adalah seperti berikut:
// 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) }
Adakah ia kelihatan biasa?
Secara lalai, pustaka RPC standard Go menggunakan pengekodan Gob proprietari Go. Walau bagaimanapun, adalah mudah untuk melaksanakan pengekodan lain, seperti Protobuf atau JSON, di atasnya. Pustaka standard sudah pun menyokong pengekodan jsonrpc dan kami boleh melaksanakan pengekodan JSON dengan membuat perubahan kecil pada pelayan dan kod klien.
// 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 }
Objek data permintaan JSON secara dalaman sepadan dengan dua struktur: pada bahagian klien, ia adalah clientRequest, dan pada bahagian pelayan, ia adalah serverRequest. Kandungan struktur clientRequest dan serverRequest pada asasnya adalah sama:
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"` }
Di sini, Kaedah mewakili nama perkhidmatan yang terdiri daripada nama perkhidmatan dan Kaedah. Elemen pertama Params ialah parameter dan Id ialah nombor panggilan unik yang dikekalkan oleh pemanggil, digunakan untuk membezakan permintaan dalam senario serentak.
Kami boleh menggunakan nc untuk mensimulasikan pelayan dan kemudian menjalankan kod klien untuk melihat maklumat yang dihantar oleh klien berkod JSON kepada pelayan:
nc -l 1234
Arahan nc menerima data berikut:
{"method":"HelloService.Hello","params":["hello"],"id":0}
Ini konsisten dengan serverRequest.
Kami juga boleh menjalankan kod pelayan dan menggunakan nc untuk menghantar permintaan:
echo -e '{"method":"HelloService.Hello","params":["Hello"],"Id":1}' | nc localhost 1234 --- {"id":1,"result":"hello:Hello","error":null}
Artikel ini memperkenalkan pakej rpc daripada pustaka standard Go, menyerlahkan kesederhanaan dan prestasi berkuasanya. Banyak perpustakaan rpc pihak ketiga dibina di atas pakej rpc. Artikel ini berfungsi sebagai ansuran pertama dalam siri penyelidikan RPC. Dalam artikel seterusnya, kami akan menggabungkan protobuf dengan RPC dan akhirnya melaksanakan rangka kerja RPC kami sendiri.
Atas ialah kandungan terperinci EPI Tindakan RPC Laksanakan antara muka RPC yang mudah dalam Go. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!