ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Golang] go에서 RPC 다루기
    개발언어/Go 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 서버를 시작하고, 핸들러와 함께 리스너를 매개변수로 전달한다는 점이다.

     

     

    '개발언어 > Go' 카테고리의 다른 글

    [Golang] 마이크로서비스 공통패턴  (0) 2020.10.14
    [Golang] RPC API  (0) 2020.10.12
    [Golang] net/http 패키지  (0) 2020.09.30
    [golang] 마샬링(Marshaling)  (0) 2020.09.26
    [golang] 인터페이스(Interface)  (0) 2020.09.24
Designed by Tistory.