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