cf. 예제코드는 go 개발환경과 protoc-gen-go 디펜던시가 갖추어졌다는 전제로 작성되었다.
Unary
일반적으로 생각하는 Client - Server간 일대일 메시지 통신.
Hands on
실습환경은 아래와 같은 디렉토리에서 이루어졌다.
go를 설치하고 환경변수로 GOPATH를 등록한 상태로,
모든 명령어는 ~/go/src/ch4 에서 이루어졌다.
1. proto 파일 생성
greetpb 디렉토리에 greetpb.proto 파일을 생성하고 아래와 같이 입력해준다.
syntax = "proto3";
package greet;
option go_package="greet/greetpb";
message Greeting {
string first_name = 1;
string last_name = 2;
}
message GreetRequest {
Greeting greeting = 1;
}
message GreetResponse {
string result = 1;
}
service GreetService {
// unary API
// request / response 입력, 추후 scalable한 작업을 위해 {}는 추가해두는 게 편하다.
rpc Greet(GreetRequest) returns (GreetResponse) {};
}
서버와 클라이언트에서 사용할 Request / Response 구조체 (Struct)를 message라는 이름으로 정의하고,
rpc 통신에 사용할 Service와 메소드를 정의한다. 메소드의 경우 파라미터는 input, returns 뒤에는 output을 넣어준다.
이후protoc greet/greetpb/greet.proto --go\_out=plugins=grpc:.
명령어를 입력하면 proto 파일을 토대로 go파일을 생성해준다 (greet.pb.go 파일이 생성된다)
2. server.go
package main
import (
"ch4/greet/greetpb"
"context"
"fmt"
"log"
"net"
"google.golang.org/grpc"
)
type server struct{}
func (*server) Greet(ctx context.Context, req *greetpb.GreetRequest) (*greetpb.GreetResponse, error) {
// return hello + first name 로직.
firstName := req.GetGreeting().GetFirstName()
result := "Hello " + firstName
// 리턴할 Response 객체 생성. (포인터 response이므로 &를 객체 앞에 붙여준다.)
res := &greetpb.GreetResponse{
Result: result,
}
return res, nil
}
func main() {
fmt.Println("Hello world")
// 50051: default port for gRPC. 1. tcp 연결을 위한 port binding
lis, err := net.Listen("tcp", "0.0.0.0:50051")
if err != nil {
log.Fatalf("Failed to listen", err)
}
// 2. grpc server open
s := grpc.NewServer()
greetpb.RegisterGreetServiceServer(s, &server{})
// port와 grpc server 연결 후 serving
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to server: %v", err)
}
}
Service로 생성한 Greet 메소드는 서버 코드에서 직접 구현해야 한다. proto로 생성한 go 코드를 보면
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type GreetServiceClient interface {
// unary API
// request / response 입력, 추후 scalable한 작업을 위해 {}는 추가해두는 게 편하다.
Greet(ctx context.Context, in *GreetRequest, opts ...grpc.CallOption) (*GreetResponse, error)
}
GreetServiceClient 인터페이스에 Greet 메소드가 있다. 이 메소드를 Server에서 구현하면 된다.
Unary는 클라이언트의 요청 한 번과 서버의 응답 한 번으로 이루어져 있기 때문에,
request로 넘어온 struct의 필드명을 객체의 attribute 접근하듯이 사용할 수 있다.
3. Client.go
package main
import (
"ch4/greet/greetpb"
"context"
"fmt"
"log"
"google.golang.org/grpc"
)
func main() {
fmt.Println("Hello, this is client")
// 기본적으로는 ssl init을 지원하지만, ssl 작업 없이 시작하기 위해 insecure 옵션 사용
// 1. create the connection
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
if err != nil {
log.Fatalf("could not connect %v", err)
}
// conn 커넥션이 프로그램 종료 시 같이 끊기도록 설정함. defer는 프로그램 말미에 실행되도록 하는 명령어
// 3. close connection. (end of program)
defer conn.Close()
// 2. create a client
c := greetpb.NewGreetServiceClient(conn)
fmt.Printf("created client %f", c)
doUnary(c)
}
func doUnary(c greetpb.GreetServiceClient) {
fmt.Println("starting to do a Unary RPC... ")
req := &greetpb.GreetRequest{
Greeting: &greetpb.Greeting{
FirstName: "firstName",
LastName: "lastName",
},
}
res, err := c.Greet(context.Background(), req)
if err != nil {
log.Fatalln("Error while Calling Greet RPC : ", err)
}
log.Println(res.Result)
}
GreetServiceClient 객체를 생성하고, Request 구조체를 파라미터로 넣어 서버에 요청을 보낸다.
ServiceClient 객체의 메소드를 호출하는 것처럼 사용할 수 있다는 것이 특징.
'학습일지 > 네트워크' 카테고리의 다른 글
gRPC (4) - Client Streaming 개념 및 예제코드 (0) | 2021.08.16 |
---|---|
gRPC (3) - Server Streaming 개념 및 예제코드 (0) | 2021.08.15 |
gRPC (1) - gRPC의 특징 및 성능확인 (0) | 2021.06.11 |
WebRTC - 개념과 통신방식, 프로토콜 (0) | 2021.02.17 |
HTTP (0) | 2020.12.04 |