개발언어/Go

[Golang] go에서 RPC 다루기

이상한개발자 2020. 10. 11. 18:47

간단한 RPC

 

1. HelloWorldHandler라는 구조체와 구조체에서 사용하는 메서드

type HelloWorldHandler struct{}

func (h *HelloWorldHandler) HelloWorld(args *contract.HelloWorldRequest, reply *contract.HelloWorldResponse) error {
	reply.Message = "Hello " + args.Name
	return nil
}

=> 빈 구조체를 하나 만들고, 해당 구조체는 HelloWorld라는 메서드를 가지고 있다.

 

2. 서버 시작

func StartServer() {
	helloWorld := &HelloWorldHandler{}
	rpc.Register(helloWorld) // ①

	l, err := net.Listen("tcp", fmt.Sprintf(":%v", port)) // ②
	if err != nil {
		log.Fatal(fmt.Sprintf("Unable to listen on given port: %s", err))
	}
	defer l.Close()

	for { // ③
		conn, _ := l.Accept() // ④ 
		go rpc.ServeConn(conn) // ⑤
	}
}

① helloworld 구조체를 기본 RPC서버에 등록(Register)한다.

② net.Listen을 통해 주어진 프로토콜과 port에 Listen한다. (프로토콜은 tpc뿐만 아니라, tcp4, unix 등과 같이 서버에서

    사용할 프로토콜을 구체적으로 선택할 수 있다.)

    참고로, Listen 함수는 Listener 인터페이스를 구현하는 인스턴스를 리턴한다.

type Listener interface {
    Accept() (Conn, error)
    Close() error
    Addr() addr
}

③ RPC 서버는 각 연결을 개별적으로 처리하며, 처음 연결을 처리하고 나서 Accept를 호출해 후속 연결을 처리하거나

    애플리케이션을 종료하기 때문에 무한루프를 돌림.

 

연결을 수신하기 위해 Accept() 메서드를 호출 

⑤ 주어진 conn을 가지고 DefaultServer 메서드를 실행하고, 클라이언트가 완료될 때 까지 대기한다.

 참고로, 통신 프로토콜 측면에서 ServeConn은 Gob wire 타입을 사용하며, 

 

3. 클라이언트 연결

func CreateClient() *rpc.Client {
	client, err := rpc.Dial("tcp", fmt.Sprintf("localhost:%v", port))
	if err != nil {
		log.Fatal("dialing:", err)
	}

	return client
}

=> Dial() 함수를 사용해서 클라이언트 자체를 생성한다.

func PerformRequest(client *rpc.Client) contract.HelloWorldResponse {
	args := &contract.HelloWorldRequest{Name: "World"}
	var reply contract.HelloWorldResponse

	err := client.Call("HelloWorldHandler.HelloWorld", args, &reply)
	if err != nil {
		log.Fatal("error:", err)
	}

	return reply
}

=> client.Call() 메서드를 사용해서 서버에 붙여진 함수, 인자값, 해당 결과를 싣어서 보낼 응답객체(위에서는 reply)를 각각 입력해준다.

 

 

HTTP를 통한 RPC

func StartServer() {
	helloWorld := &HelloWorldHandler{}
	rpc.Register(helloWorld)
	rpc.HandleHTTP()

	l, err := net.Listen("tcp", fmt.Sprintf(":%v", port))
	if err != nil {
		log.Fatal(fmt.Sprintf("Unable to listen on given port: %s", err))
	}

	log.Printf("Server starting on port %v\n", port)

	http.Serve(l, nil)
}

기본 rpc와 다른 점은,

첫째, 전송 프로토콜로 HTTP를 사용해야 하는 경우, rpc.HandleHTTP() 메서드를 호출한다는 점이고,

둘째로는, http.Serve를 통해 listener를 매개변수로 전달한다는 점이 다르다.

 

HTTP를 통한 JSON-RPC

func StartServer() {
	helloWorld := new(HelloWorldHandler)
	rpc.Register(helloWorld)

	l, err := net.Listen("tcp", fmt.Sprintf(":%v", port))
	if err != nil {
		log.Fatal(fmt.Sprintf("Unable to listen on given port: %s", err))
	}

	http.Serve(l, http.HandlerFunc(httpHandler))
}

func httpHandler(w http.ResponseWriter, r *http.Request) {
	serverCodec := jsonrpc.NewServerCodec(&HttpConn{in: r.Body, out: w})
	err := rpc.ServeRequest(serverCodec)
	if err != nil {
		log.Printf("Error while serving JSON request: %v", err)
		http.Error(w, "Error while serving JSON request, details have been logged.", 500)
		return
	}
}

=> 위의 예제들과의 주요 차이점은 RPC 서버를 시작하는 대신 http 서버를 시작하고, 핸들러와 함께 리스너를 매개변수로 전달한다는 점이다.