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 지식 체계를 단계별로 마스터하게 됩니다:

  • 스키마 설계: .proto IDL 문법을 활용해 데이터 형식(Message)과 원격 함수(Service RPC) 계약을 강제하는 법을 습득합니다.
  • Stub 컴파일: protoc 도구 체인을 가동하여 Go 프로그래밍 언어 소스코드를 안전하게 생성하고 프로젝트 빌드에 바인딩하는 기법을 배웁니다.
  • 네트워크 구현: 실제로 TCP 소켓을 확보하여 gRPC 서버를 실행하고, 클라이언트가 평문 커넥션을 수립하여 실시간 동기식 원격 프로시저를 직접 기동하는 엔드투엔드 구동 구조를 체득합니다.

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. 참고 자료