본문 바로가기
Framework and Tool/gRPC and Protobuf

gRPC with protobuf

by ocwokocw 2022. 3. 13.

- 출처: https://grpc.io/docs/what-is-grpc/introduction/

- 출처: https://grpc.io/docs/languages/go/quickstart/

- 출처: https://github.com/grpc/grpc-go/tree/master/examples/helloworld

- 개요

google에서 개발한 Remote Procedure Call이다. IDL(Interface Definition Language) 및 기본 메시지 교환형식으로 protocol buffer를 사용할 수 있다. RPC와 비슷하게 서버측에서는 interface를 구현하고 gRPC 서버를 실행하면 클라이언트에서는 서버와 같은 메소드의 stub을 갖는 구조이다. gRPC는 여러 언어를 지원하는데, 해당 언어이기만 하면 서로 다른 언어라도 통신이 가능하다. 예를 들면 Java로 gRPC 서버를 띄우고 Go, Ruby, Python 클라이언트로 gRPC 서버에 요청할 수 있다.

- gRPC with Protocol buffer

기본적으로 gRPC는 구조화된 데이터를 직렬화하는 Google의 오픈소스 매커니즘인 Protocol buffer를 사용한다. 기본적으로라는 말에서 유추할 수 있듯이 JSON과 같은 다른 데이터형식과도 사용이 가능하다.
 
Protocol buffer를 이용하여 gRPC로 어떻게 원격 procedure를 호출하는지 파악해보자. 
 
우선 .proto 파일을 정의해야 한다. 이 파일에는 직렬화 하고 싶은 구조화된 데이터를 정의한다. 구조화된 데이터는 message로 정의하며 name-value 쌍의 field를 갖는다. 또한 gRPC 서비스들도 정의해준다.
 
syntax = "proto3";

option go_package = "seminar/g_rpc/g_rpc/helloworld";

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}
 
위의 proto 파일에서는 HelloRequest와 HelloReply message를 정의했고, 이를 이용하여 인사를 하는 Greeter 서비스를 정의했다. 이 .proto 파일은 실제 컴파일시 이용되는것이 아니라 일종의 정의 파일이라고 보면 된다. 
 
이제 파일을 이용해서 코드를 생성하는 Protocol buffer compiler인 protoc 를 사용한다. protoc가 .proto 파일로 부터 생성한 코드에는 구조화된 데이터를 raw byte로 만들거나 raw byte로 부터 파싱하는 코드 뿐만 아니라 field에 대한 접근자에 대한 코드도 들어있다. 
 
protoc를 사용하기 위해서는 plugin을 설치 해야 한다. 아래 명령어로 install한 후 bin을 환경변수에 노출하도록 하자.
 
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1
 
.proto 파일로 부터 코드를 생성하는 명령어는 아래와 같다.
 
protoc --go_out=. --go_opt=paths=source_relative \
    --go-grpc_out=. --go-grpc_opt=paths=source_relative \
    g_rpc/g_rpc/helloworld/helloworld.proto
 
정상적으로 수행했따면 [파일이름].pb.go와 [파일이름]_grpc.pb.go 코드가 생성된다. 이 코드는 자동 생성되는 코드이기 때문에 세부 내용을 심도있게 해석해야 하는 경우는 거의 없다.
 
생성된 코드로 gRPC server를 작성해보도록 하자.
 
package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"net"

	"google.golang.org/grpc"

	pb "seminar/g_rpc/g_rpc/helloworld"
)

var (
	port = flag.Int("port", 50051, "The server port")
)

// server is used to implement helloworld.GreeterServer.
type server struct {
	pb.UnimplementedGreeterServer
}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
	log.Printf("Received: %v", in.GetName())
	return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}

func main() {
	flag.Parse()
	lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	s := grpc.NewServer()
	pb.RegisterGreeterServer(s, &server{})
	log.Printf("server listening at %v", lis.Addr())
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}
 
우선 RPC와 비슷하게 listen 정보에 port와 프로토콜을 지정해준다. grpc.NewServer를 생성한다. 그리고 생성한 Greeter service를 등록해준다. Greeter service의 SayHello procedure는 이름을 받아 인사를 해주는 동작을 하도록 구현하였다.
 
gRPC 서버로 요청할 때에는 아래처럼 작성해주면 된다.
 
package main

import (
	"context"
	"flag"
	"log"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"

	pb "seminar/g_rpc/g_rpc/helloworld"
)

const (
	defaultName = "world"
)

var (
	addr = flag.String("addr", "localhost:50051", "the address to connect to")
	name = flag.String("name", defaultName, "Name to greet")
)

func main() {
	flag.Parse()
	// Set up a connection to the server.
	conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
	c := pb.NewGreeterClient(conn)

	// Contact the server and print out its response.
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	r, err := c.SayHello(ctx, &pb.HelloRequest{Name: *name})
	if err != nil {
		log.Fatalf("could not greet: %v", err)
	}
	log.Printf("Greeting: %s", r.GetMessage())
}
 
RPC와 비슷하게 Dial connection을 맺고 Greeter client를 생성한다. 위에서는 timeout을 설정하였다. Greeter 서비스는 HelloRequest 메시지를 인자로 받으므로 ctx와 함께 넘겨준다. 위의 코드는 https://github.com/grpc/grpc-go/tree/master/examples/helloworld 에서 확인할 수 있다.
 
 

'Framework and Tool > gRPC and Protobuf' 카테고리의 다른 글

Protocol buffer - Convention  (0) 2022.04.29
Protocol buffer - Proto3  (0) 2022.04.24
Protocol buffer  (0) 2022.03.13
RPC(Remote procedure call)  (0) 2022.03.13

댓글