Files
grpccanary/docs/GRPC.md
T

11 KiB

3단계: gRPC 통신 구현 상세 가이드

학습 로드맵으로 돌아가기

이 문서는 grpccanary 프로젝트의 3단계: gRPC 통신 구현에 대한 이론적 배경, 스키마 명세, 컴파일 기법 및 구체적인 소스코드 분석을 설명합니다.

gRPC는 HTTP/2를 기반으로 구축된 구글의 고성능 오픈소스 원격 프로시저 호출(RPC) 시스템입니다. 사물인터넷(IoT) 장비나 에이전트 간의 데이터 통신 시 가볍고 구조화된 데이터 통신을 유지하는 데에 가장 적합한 프레임워크입니다.


1. gRPC 개요 및 기술 배경

1.1 gRPC의 핵심 차별점

  • 강력한 스키마 계약: .proto 파일 하나로 서비스 통신 규약을 명확히 선언하고, 컴파일 단계에서 이를 바탕으로 여러 언어의 클라이언트/서버 코드를 자동 생성합니다. 따라서 런타임 단계에서의 통신 필드 누락이나 타입 불일치 버그를 완벽하게 방지합니다.
  • 이진 프로토콜 (바이너리 포맷): 텍스트가 아닌 컴팩트한 이진 형식을 사용하므로 데이터 크기가 매우 작고 네트워크 대역폭 리소스 효율이 뛰어납니다.
  • HTTP/2 기반: 하나의 네트워크 커넥션을 재사용해 다중화(Multiplexing) 전송이 가능하고, 실시간 스트리밍(양방향 스트리밍 포함) 서비스에 탁월한 환경을 제공합니다.

1.2 Protobuf (프로토콜 버퍼)의 장단점

  • 장점

    • 높은 전송 효율성: 데이터 교환 시 텍스트가 아닌 바이너리 인코딩 형식을 사용하므로 JSON에 비해 직렬화/역직렬화 속도가 매우 빠르고 크기도 가볍습니다.
    • 일관성 있는 코드 생성 (Stub): 동일한 정의서로부터 다국어 API 클라이언트를 빌드하여 언어별 클라이언트 코드를 중복 작성해야 하는 오버헤드를 제거합니다.
    • 하위 호환성: 고유 필드 번호 매핑 방식을 사용하므로 스키마가 개정되어도 이전 시스템과의 통신 호환을 보장합니다.
  • 단점

    • 가독성 부재: 패킷이 암호화는 아니지만 바이너리로 전달되므로 사람이 브라우저 개발자 도구 등으로 바로 읽어 디버깅하기 곤란합니다.
    • 빌드 종속성: 명세 변경 시마다 Stub 코드를 컴파일하여 빌드에 바인딩하는 과정이 요구됩니다.

2. 실습 프로젝트 소개: IoT 센싱 데이터 수집 서비스

본 튜토리얼에서는 gRPC 분산 통신 기법을 실증적으로 학습하기 위해 현업에서 가장 범용적으로 쓰이는 가상의 IoT 센싱 데이터 수집 및 기기 관리 서비스 프로젝트를 직접 설계하고 구현해 나갑니다.

2.1 프로젝트 시나리오

사물인터넷(IoT) 센서 노드나 분산 멀티 에이전트 환경에서는 엣지 기기들이 중앙 서버에 접속해 통신 가능 여부를 검증하고 상태 정보(날짜/시간)를 수집하거나, 임시 보안 인증을 위한 비밀번호 발급을 요청하고, 실시간으로 센싱한 환경 정보(온도, 습도 등)를 지속적으로 업데이트해야 하는 현실적인 시나리오가 요구됩니다. 우리가 개발할 IoTService는 이에 대응하는 다음 3가지 핵심 원격 프로시저(RPC)를 수행합니다:

  1. 서버 시간 및 날짜 조회 (GetDate): 기기가 접속 상태를 확인하며 동기화를 위해 서버의 현재 날짜와 시간 포맷 문자열을 반환받습니다.
  2. 센싱 데이터 업데이트 (UpdateSensingData): 센서 노드가 주기적으로 수집한 물리 데이터(온도, 습도) 및 기기 식별자(Device ID)를 전달하면, 서버는 데이터 정합성을 검증한 후 성공 여부를 반환합니다.
  3. 일회성 보안 패스워드 발급 (GetRandomPass): 기기가 임시 통신 세션 수립을 위해 난수 생성 시드와 보안 문자열 길이를 전달하면, 무작위 ASCII 임시 패스워드를 연산하여 응답받습니다.

2.2 학습 목표 및 진행 방법

이 유기적인 IoT 데이터 통신 모듈을 구축하는 실습을 통해 우리는 다음과 같은 gRPC의 핵심 개발 과정을 아주 쉽게 단계별로 마스터하게 됩니다:

  • 1단계 - 통신 약속 문서 작성하기 (스키마 설계): 기기와 서버가 서로 어떤 형태로 데이터를 주고받을지 .proto라는 명세서 파일에 미리 정의해 둡니다. 이 약속을 바탕으로 데이터의 이름과 형태(예: 기기 ID는 문자열, 온도는 실수 등)를 컴파일 전에 확실하게 강제합니다.
  • 2단계 - 말을 알아듣는 코드 자동으로 만들기 (Stub 컴파일): 작성한 약속 문서를 컴파일러(protoc)에 넣어주면, 네트워크 통신과 데이터 변환 처리를 담당하는 Go 언어 코드를 컴퓨터가 자동으로 만들어 줍니다. 이를 통해 개발자가 직접 복잡한 JSON 변환이나 소켓 통신 코드를 일일이 짤 필요가 없어집니다.
  • 3단계 - 실제로 서버와 기기를 연결하여 대화하기 (네트워크 구현): 자동으로 만들어진 코드를 바탕으로 실제 gRPC 서버를 실행하고, 클라이언트 기기가 서버에 접속하여 데이터(날짜 요청, 센싱 값 전송 등)를 실시간으로 주고받는 엔드투엔드(End-to-End) 통신 흐름을 완성합니다.

3. 인터페이스 명세서 (protoapi.proto)

저장소 루트의 protoapi.proto 파일은 앞서 설계한 IoTService를 구축하기 위해 아래와 같이 사양을 선언해 둡니다.

syntax = "proto3";

option go_package = "./protoapi/;protoapi";

service IoTService {
    rpc GetDate (RequestDateTime) returns (DateTime);
    rpc UpdateSensingData (SensingData) returns (SensingResponse);
    rpc GetRandomPass (RequestPass) returns (RandomPass);
}

message SensingData {
    string DeviceId = 1;
    double Temperature = 2;
    double Humidity = 3;
}

message SensingResponse {
    bool Success = 1;
    string Message = 2;
}

message DateTime {
    string Value = 1;
}

message RequestDateTime {
    string Value = 2;
}

message RequestPass {
    int64 Seed = 1;
    int64 Length = 8;
}

message RandomPass {
    string Password = 1;
}

4. Go Stub 컴파일 및 도구 체인

.proto 파일을 Go 파일로 출력하기 위해 프로토콜 버퍼 컴파일러(protoc)와 플러그인이 로컬에 갖추어져야 합니다.

4.1 OS별 설치 가이드

  • macOS:
    brew install protobuf
    go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
    go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
    
  • Linux:
    sudo apt install -y protobuf-compiler
    go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
    go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
    

4.2 컴파일 실행 명령어

protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. \
  --go-grpc_opt=paths=source_relative protoapi.proto

실행 결과로 protoapi/ 아래에 protoapi.pb.go(메시지 정의)와 protoapi_grpc.pb.go(인터페이스 및 원격 호출)가 자동 생성됩니다.


5. 실습 코드 구현 상세 분석

5.1 gRPC 서버 구현 (server.go)

  • 구조체 정의:
    type IoTServer struct {
    	protoapi.UnimplementedIoTServiceServer
    }
    
    UnimplementedIoTServiceServer를 임베딩하여, 향후 메서드가 새로 추가되더라도 기존 서버가 빌드 에러 없이 최소한의 호환(unimplemented 에러 응답)을 가지게 강제합니다.
  • 서버 기동 흐름:
    server := grpc.NewServer()
    var iotServer IoTServer
    protoapi.RegisterIoTServiceServer(server, iotServer)
    reflection.Register(server) // grpcurl 등 외부 디버깅 목적
    listen, _ := net.Listen("tcp", port)
    server.Serve(listen)
    

5.2 gRPC 클라이언트 구현 (client.go)

  • 연결 수립:
    conn, _ := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
    defer conn.Close()
    client := protoapi.NewIoTServiceClient(conn)
    
    insecure.NewCredentials()를 전달하여 TLS를 건너뛴 채 평문으로 빠르고 간단한 로컬 테스트 환경을 구축합니다.
  • 원격 호출: client.GetDate(), client.GetRandomPass(), client.UpdateSensingData()를 차례로 호출하여 매개변수와 결과를 콘솔로 확인합니다.

5.3 gRPC 실습 예제 동작 흐름

예제가 구동되면 서버와 클라이언트 간에 다음과 같은 호출 시퀀스가 순차적으로 실행됩니다:

  1. 날짜 조회 (GetDate): 클라이언트가 서버에 날짜 조회를 요청하고, 서버는 자체의 현재 날짜와 시간 문자열을 포맷하여 반환합니다.
  2. 비밀번호 생성 (GetRandomPass): 클라이언트가 생성할 무작위 비밀번호의 길이(기본 8자)와 난수 생성 시드값을 전달하면, 서버는 지정된 사양의 임의 문자열을 작성해 반환합니다.
  3. 센싱 데이터 업데이트 (UpdateSensingData): 클라이언트가 기기 ID("sensor-room-01")와 수집한 온/습도 환경 변수를 전달하면, 서버는 이를 수신하여 화면에 상세 센싱 값을 출력하고 업데이트 완료 성공 응답을 전송합니다.

6. 트러블슈팅 (Troubleshooting)

gRPC 서버 및 클라이언트 실습 과정에서 직면할 수 있는 대표적인 문제와 해결 방안입니다.

6.1 listen tcp :8080: bind: address already in use

  • 원인: 포트 8080이 이미 다른 백그라운드 프로세스나 기존 기동된 서버에 의해 점유되어 충돌이 난 상태입니다.
  • 해결 방법:
    • examples/main.goport 변수값과 server.goport 전역 변수값을 동시에 다른 포트(예: :9090)로 변경하고 재시도해야 합니다.
    • 주의: server.go 내부의 ServerRun(addr string) 함수는 외부 진입점으로부터 인자 addr을 인가받지만, 실제 포트 리슨 코드에서는 이를 무시하고 패키지 전역 변수 port = ":8080"를 직접 읽어 처리하도록 하드코딩되어 있습니다. 따라서 정상적으로 포트를 바꾸기 위해서는 반드시 server.go 내부 전역 변수인 port 값을 수정해 주어야 포트 바인딩이 성공합니다.

7. 다음 단계 (Next Steps)

gRPC 통신 방식을 한층 더 깊이 탐구해 보려면 다음과 같은 후속 실습을 추천합니다:

  1. 메시지 스펙 확장: 루트 디렉토리의 protoapi.proto에 새로운 필드를 추가하거나 메서드를 정의한 뒤, Stub을 재컴파일(examples/grpcentity/README.md 컴파일 가이드 참고)해 보십시오.
  2. 인터페이스 모듈 연계: 생성된 gRPC 클라이언트 및 서버 stub 인터페이스를 활용하여, 향후 분산 AIoT 환경에서의 센서 데이터 수집이나 에이전트 간 제어 메시지 전송 로직을 설계해 보십시오.

8. 참고 자료