Programming/Go

Go/Golang Protobuf Compile Guide

2021. 8. 16. 23:42
목차
  1. Process
  2. 간단한 gRPC 예제(protobuf 파일 한 개)
  3. Tree 구조의 protobuf 파일

 

 

지난 포스팅에서 gRPC에 대해서 설명했으며

이번에는 go를 이용해 사용하는 방법을 정리합니다

 

gRPC

이번 포스팅은 gRPC입니다. 요즘 업무에 자주 쓰고 있어서 가볍게 정리해두려 합니다. [핵심 용어] 더보기 /* - RPC - IDL(Interface Definition Language) - gRPC - protobuf(프로토버프/프로토콜버퍼) - micros..

syntaxsugar.tistory.com

 

 

gRPC를 사용하기 위해서는 protobuf로 IDL을 정의하고 gRPC 방식으로 컴파일해야 합니다

단순한 protobuf는 go에서 사용하는 방법이 굉장히 단순합니다

하지만 Tree 구조를 가진 protobuf를 go에서 컴파일하는 데는 현재까지 버그가 있습니다

 

 

이번 포스팅에서는

- 하나의 protobuf를 정의하고 go에서 gRPC를 사용하는 간단한 예제

- Tree 구조의 protobuf 정의하고 go에서 gRPC를 사용하는 방법

를 정리해보겠습니다

 

 


Process

 

go에서 gRPC를 사용하기 위해서는 다음과 같은 과정을 거쳐야 합니다

 

1. protobuf를 이용해 IDL 파일 작성(.proto)

  • service, rpc, message 정의

2. protoc 툴을 설치하고 이를 사용하여 protobuf 파일(.proto) 컴파일

  • name.protobuf -> name.pb.go

3. go에서 protobuf go 파일(.pb.go) import하여 라이브러리처럼 사용

 

 


간단한 gRPC 예제(protobuf 파일 한 개)

 

위의 과정대로 간단한 예제를 작성합니다

 

1. protobuf를 이용해 IDL 파일 작성(.proto)

 

helloworld.proto

syntax = "proto3";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}
  • proto3 버전 사용
  • package 이름 정의
  • Restaurant service 및 RPC 정의
  • RPC에서 call, return 인자로 사용할 message 정의

 

2. protoc 툴을 설치하고 이를 사용하여 protobuf 파일(.proto) 컴파일

 

설치

$ go install google.golang.org/protobuf/cmd/protoc-gen-go

 

gRPC 용 protobuf 컴파일 명령

protoc -I . helloworld.proto --go_out=plugins=grpc:.
  • gRPC 용도 말고 단순 protobuf 컴파일 명령 : protoc -I . helloworld.proto --go_out=.

 

 

3. go에서 protobuf go 파일(.pb.go) import하여 라이브러리처럼 사용

 

gRPC Server와 gRPC Client 서버에서 helloworld.pb.go를 import하여 구현 수행

 

server.go : Client에서 연결하여 RPC를 호출할 수 있도록 RPC 구현 및 Server Listen 수행

package main

import (
	"context"
	"log"
	"net"

	"google.golang.org/grpc"
	pb "google.golang.org/grpc/examples/helloworld/helloworld"
)

const (
	port = ":50051"
)

// 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() {
	lis, err := net.Listen("tcp", port)
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	s := grpc.NewServer()
	pb.RegisterGreeterServer(s, &server{})
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}
  • pb를 import
  • pb.UnimplementedGreeterServer를 가진 struct 생성
  • RPC 구현
  • main
    • gRPC server 생성/등록/Serve 수행

 

client.go : Server에 연결하여 RPC 호출 수행

package main

import (
	"context"
	"log"
	"os"
	"time"

	"google.golang.org/grpc"
	pb "google.golang.org/grpc/examples/helloworld/helloworld"
)

const (
	address     = "localhost:50051"
	defaultName = "world"
)

func main() {
	// Set up a connection to the server.
	conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
	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.
	name := defaultName
	if len(os.Args) > 1 {
		name = os.Args[1]
	}
	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())
}
  • pb를 import
  • main
    • grpc 옵션과 함께 Dial 수행하여 Server와 연결
    • pb에 정의된 RPC 호출

 

실행 결과

 

Server

Client

 

간단한 gRPC 예제였습니다

 

이러한 과정으로 어렵지 않게

작성한 protobuf 파일을 pb.go로 컴파일하고

pb.go를 기반으로 gRPC Server와 Client를 구현하면

Client에서 Server에 접속하여 gRPC Call을 수행할 수 있습니다

 

 


 

Tree 구조의 protobuf 파일

 

이번엔 protobuf 파일 한 개가 아닌

아래와 같은 구조를 가진 protobuf를 컴파일해보겠습니다

 

 

- common.proto는 package common

- as.proto는 package as

- gw.proto는 pacakge gw

로 되어 있으며

 

 

gw가 common을 import

as가 common을 import

하여 사용하는 구조입니다

 

 

일반적으로 사용되는 protobuf 구조이고 특이한 구조는 아니지만

이전과 같은 방식으로 protobuf를 compile한 경우,

작성한 방식에 따라 protobuf compile은 될 수 있으나 go compiler에서 에러가 발생합니다

protoc -I common common/common.proto --go_out=plugins=grpc:common
protoc -I as as/as.proto -I common --go_out=plugins=grpc:as
protoc -I gw gw/gw.proto -I common --go_out=plugins=grpc:gw

 

 

컴파일 결과로 생성된 gw.pb.go나 as.pb.go에서 import하는 common package의 위치가

적절히 설정되지 않기 때문입니다

 

 

다른 컴파일 방법을 찾아 수행해 보아도 파이썬 등에서는 문제없이 수행되나

protobuf 정의에 따라 golang에서만 이런 문제가 발생합니다

 

 

이러한 버그? 문제 때문에 찾아보면 아래와 같은 이슈가 있었고,

이를 해결하기 위해서는 grpc 컴파일로 생성된 코드를 직접 수정해야 할 부분이 많았습니다

 

 

Support batch generation with import · Issue #790 · golang/protobuf

Is your feature request related to a problem? Please describe. son.proto import father.proto from different go_package but same dir. Cant execute protoc: x@x-ThinkPad-T460:~/go/src/lilu.red/proto$ ...

github.com

 

proposal: protoc-gen-go should be able to compile multiple packages per run · Issue #39 · golang/protobuf

Passing multiple packages' worth of *.proto files currently fails when using (protoc-gen-go)[https://github.com/golang/protobuf/blob/master/protoc-gen-go/generator/generator.go#L612:L614]. The ...

github.com

 

 

하지만 아래의 2019년 1월 업데이트로 어느 정도 완화시킬 수 있었으며

 

proposal: protoc-gen-go should be able to compile multiple packages per run · Issue #39 · golang/protobuf

Passing multiple packages' worth of *.proto files currently fails when using (protoc-gen-go)[https://github.com/golang/protobuf/blob/master/protoc-gen-go/generator/generator.go#L612:L614]. The ...

github.com

 

 

결론적으로 여러 삽질 끝에 해당 과정을 아래와 같이 스크립트화할 수 있게 되었습니다

*macos에서는 sed 명령 사용 시 backup 옵션이 필요

 

 

[자세한 과정]

더보기

/*

 

위와 같은 디렉터리 구조지만

gw/gw.proto 파일의 import를

import "../common/common.proto";에서

import "common/common.proto";로(문법 에러 발생하지만 무시합니다)

변경해줍니다

 

as/as.proto 파일의 import도 마찬가지로

import "../common/common.proto";에서

import "common/common.proto";로(문법 에러 발생하지만 무시합니다)

변경해줍니다

 

protos 디렉터리에서 아래 명령 수행하여 protobuf 컴파일 정상 수행

for x in **/*.proto; do protoc --go_out=plugins=grpc,paths=source_relative:. $x; done

 

마지막으로

protobuf 컴파일 결과로 나온 gw/gw.pb.go와 as/as.pb.go 파일의

common import를 실제 패키지 경로에 맞게 직접 수정합니다

저의 경우엔 "uangel.com/skbib/protos/common" 입니다

이후, 전체 go build를 수행하여 정상적으로 컴파일되는 것을 확인할 수 있습니다

 

이와 같은 과정을 거쳐야 golang에서는 정상적으로 tree 구조의 protobuf를 사용할 수 있습니다

굉장히 번거로운데 한 번만 세팅해두면 이후 사용에 문제는 없습니다

*/

 

이후 생성된 pb.go 파일을 이용해

앞의 예제와 마찬가지로 방식으로 server와 client를 구현하면 문제없이 동작합니다

저작자표시 (새창열림)

'Programming > Go' 카테고리의 다른 글

Go/Golang Test  (2) 2021.08.26
Go/Golang Memory Management  (16) 2021.08.22
Go/Golang Configuration  (0) 2021.08.16
Go/Golang awesome-go  (0) 2021.08.11
Go/Golang Long Time Performance Test - Memory Leak 해결 과정  (0) 2021.07.11
  1. Process
  2. 간단한 gRPC 예제(protobuf 파일 한 개)
  3. Tree 구조의 protobuf 파일
'Programming/Go' 카테고리의 다른 글
  • Go/Golang Test
  • Go/Golang Memory Management
  • Go/Golang Configuration
  • Go/Golang awesome-go
Syntax Sugar
Syntax Sugar
달면 삼키고 써도 삼키는 개발자 블로그
Syntax Sugar
{ DEV SWEETER ; }
Syntax Sugar
전체
오늘
어제
  • 분류 전체보기 (51)
    • Cloud (6)
      • Cloud Native (4)
      • Docker (0)
      • Kubernetes (2)
    • Programming (17)
      • Go (15)
      • Scala (0)
      • NodeJS (0)
      • Framework & Tool (2)
    • Tech (10)
      • Trend (4)
      • AI (1)
      • 5G (0)
      • IoT (0)
      • Other (5)
    • Cooperation (12)
    • Inspiration (5)

블로그 메뉴

  • GitHub

공지사항

  • WIRE-JACKET
  • START

인기 글

태그

  • wire
  • 12Factors
  • go
  • Copilot
  • Agile
  • Cloud Native
  • Access Token
  • Mac M1
  • github authentication
  • github
  • Clean code
  • CleanCode
  • 애자일
  • The Twelve Factors
  • 클린 코드
  • 깨끗한 코드
  • 클린코드
  • Golang
  • CloudNative
  • 깃헙

최근 댓글

최근 글

hELLO · Designed By 정상우.
Syntax Sugar
Go/Golang Protobuf Compile Guide
상단으로

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.