RPC(Remote Procedure Call)는 분산 시스템에서 서로 다른 노드 사이에 널리 사용되는 통신 방식으로 인터넷 시대의 기반 기술입니다. Go의 표준 라이브러리는 net/rpc 패키지에서 간단한 RPC 구현을 제공합니다. 이 글의 목표는 net/rpc 패키지를 사용하여 간단한 RPC 인터페이스를 구현하는 과정을 안내하여 RPC에 대한 이해를 돕는 것입니다.
이 글은 Medium MPP 기획에 처음 게재되었습니다. 미디엄 사용자라면 미디엄에서 저를 팔로우해주세요. 정말 감사합니다.
net/rpc에서 원격으로 함수를 호출하려면 다음 5가지 조건을 충족해야 합니다.
- 메소드 유형을 내보냅니다.
- 방법을 내보냅니다.
- 이 메소드에는 두 개의 인수가 있으며 둘 다 내보낸(또는 내장) 유형입니다.
- 메서드의 두 번째 인수는 포인터입니다.
- 메서드에 오류 반환 유형이 있습니다.
즉, 함수 서명은 다음과 같아야 합니다.
func (t *T) MethodName(argType T1, replyType *T2) error
이 다섯 가지 조건을 바탕으로 간단한 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 서비스 이름과 메소드 이름이고, 두 번째 매개변수는 입력, 세 번째 매개변수는 반환 값인 포인터입니다. 이 예는 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 요청 데이터 개체는 내부적으로 두 가지 구조, 즉 클라이언트 측에서는 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 Action EPGo에서 간단한 RPC 인터페이스 구현의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!