From b2304aa53ed755bd19ac05e8577217342ecccc55 Mon Sep 17 00:00:00 2001 From: Godopu Date: Fri, 17 Jul 2026 15:57:11 +0900 Subject: [PATCH] docs: rewrite GRPC.md remaining sections in conversational tutorial tone --- docs/GRPC.md | 94 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 54 insertions(+), 40 deletions(-) diff --git a/docs/GRPC.md b/docs/GRPC.md index be095a2..52356a0 100644 --- a/docs/GRPC.md +++ b/docs/GRPC.md @@ -93,94 +93,108 @@ message RandomPass { --- -## 4. Go Stub 컴파일 및 도구 체인 +## 4. 말귀를 알아듣는 코드 변환기 준비 (Go Stub 컴파일 및 도구 체인) -`.proto` 파일을 Go 파일로 출력하기 위해 프로토콜 버퍼 컴파일러(`protoc`)와 플러그인이 로컬에 갖추어져야 합니다. +우리가 열심히 기획해서 적은 `.proto` 약속 파일을 컴퓨터(Go 언어)가 알아듣는 소스코드로 변환해 줄 **번역기(`protoc`)**와 **Go 전용 번역 플러그인**들을 로컬 개발 환경에 설치하고 구동하는 방법입니다. -### 4.1 OS별 설치 가이드 -* **macOS**: +### 4.1 번역 도구 설치하기 +* **macOS (Homebrew 사용)**: + 터미널에 아래 명령어를 입력해 컴파일러와 Go 언어 통신용 변환 플러그인을 설치합니다. ```bash 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**: +* **Linux (Ubuntu 기준)**: + 패키지 관리자를 통해 컴파일러를 다운로드하고 마찬가지로 Go 플러그인을 환경에 바인딩합니다. ```bash 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 컴파일 실행 명령어 +### 4.2 실제로 번역하기 (컴파일 명령어) +아래 명령어는 컴퓨터에게 **"내가 작성한 `protoapi.proto` 약속 장부를 Go 언어로 번역해서 `protoapi/` 폴더에 가지런히 넣어줘!"** 하고 지시하는 마법의 명령어입니다: ```bash -protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. \ - --go-grpc_opt=paths=source_relative protoapi.proto +protoc --go_out=. --go-grpc_out=. protoapi.proto ``` -실행 결과로 `protoapi/` 아래에 `protoapi.pb.go`(메시지 정의)와 `protoapi_grpc.pb.go`(인터페이스 및 원격 호출)가 자동 생성됩니다. +명령을 실행하고 나면 `protoapi/` 폴더 하위에 다음 두 파일이 기분 좋게 생성됩니다: +* `protoapi.pb.go`: 약속 문서에 적은 데이터(Message) 규격들을 Go 구조체로 변환해 놓은 파일입니다. +* `protoapi_grpc.pb.go`: 기기와 서버가 실제로 요청을 주고받을 수 있게 하는 통신 창구(Service) 함수가 자동 완성된 파일입니다. --- -## 5. 실습 코드 구현 상세 분석 +## 5. 실습 소스코드의 속살 들여다보기 (구현 상세 분석) -### 5.1 gRPC 서버 구현 ([server.go](../examples/grpcentity/server.go)) +번역기가 뼈대 코드를 만들어 주었으니, 이제 기기와 서버가 나눌 구체적인 대화의 내용을 채워 넣어 봅시다. -* **구조체 정의**: +### 5.1 요청에 대답하는 서버 구현 ([server.go](../examples/grpcentity/server.go)) + +* **대답 행동 대장 구조체 (`IoTServer`)**: ```go type IoTServer struct { protoapi.UnimplementedIoTServiceServer } ``` - `UnimplementedIoTServiceServer`를 임베딩하여, 향후 메서드가 새로 추가되더라도 기존 서버가 빌드 에러 없이 최소한의 호환(unimplemented 에러 응답)을 가지게 강제합니다. -* **서버 기동 흐름**: + `UnimplementedIoTServiceServer`를 품에 안은 구조체를 만듭니다. 이 친구는 **"혹시 기기가 아직 구현되지 않은 통신 창구를 두드리더라도 서버가 뻗지 않고 조용히 '아직 준비 중입니다' 에러 대답을 돌려주도록"** 든든하게 받쳐주는 안전 보디가드 역할을 해 줍니다. +* **대답 채워 넣기 (`UpdateSensingData`)**: + ```go + func (IoTServer) UpdateSensingData(ctx context.Context, r *protoapi.SensingData) (*protoapi.SensingResponse, error) { + fmt.Printf("Received sensing data - Device: %s, Temp: %.2f°C, Humid: %.2f%%\n", r.GetDeviceId(), r.GetTemperature(), r.GetHumidity()) + return &protoapi.SensingResponse{Success: true, Message: "Sensing data updated successfully!"}, nil + } + ``` + 기기가 온/습도 패킷을 들고 찾아오면, 서버 화면에 그 정보를 정답게 출력한 뒤 "이상 없이 잘 받았습니다!"라는 성공 영수증(`SensingResponse`)을 발급해 주는 역할을 기특하게 해내고 있습니다. +* **서버 문 열기 (`ServerRun`)**: ```go - server := grpc.NewServer() - var iotServer IoTServer - protoapi.RegisterIoTServiceServer(server, iotServer) - reflection.Register(server) // grpcurl 등 외부 디버깅 목적 listen, _ := net.Listen("tcp", port) server.Serve(listen) ``` + 지정된 문 번호(포트 `:8080`)의 문을 활짝 열고, 기기들의 접속 요청을 다정히 기다리는 시작 지점입니다. -### 5.2 gRPC 클라이언트 구현 ([client.go](../examples/grpcentity/client.go)) +### 5.2 요청을 보내는 클라이언트 구현 ([client.go](../examples/grpcentity/client.go)) -* **연결 수립**: +* **전송 전용 기기 만들기 (`NewIoTServiceClient`)**: ```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()`를 차례로 호출하여 매개변수와 결과를 콘솔로 확인합니다. + 서버로 통하는 통로(`conn`)를 안전하게 확보하고, 그 길을 타고 데이터를 실어 보낼 **전송 전용 클라이언트 기기**를 조립해 내는 핵심 과정입니다. +* **데이터 포장해서 보내기 (`AskUpdateSensingData`)**: + ```go + request := &protoapi.SensingData{DeviceId: deviceId, Temperature: temp, Humidity: humid} + return m.UpdateSensingData(ctx, request) + ``` + 센서가 수집한 온/습도 정보를 예쁘게 상자에 담아 포장한 뒤 서버의 `UpdateSensingData` 창구로 쏘아 올립니다. -### 5.3 gRPC 실습 예제 동작 흐름 -예제가 구동되면 서버와 클라이언트 간에 다음과 같은 호출 시퀀스가 순차적으로 실행됩니다: -1. **날짜 조회 (`GetDate`)**: 클라이언트가 서버에 날짜 조회를 요청하고, 서버는 자체의 현재 날짜와 시간 문자열을 포맷하여 반환합니다. -2. **비밀번호 생성 (`GetRandomPass`)**: 클라이언트가 생성할 무작위 비밀번호의 길이(기본 8자)와 난수 생성 시드값을 전달하면, 서버는 지정된 사양의 임의 문자열을 작성해 반환합니다. -3. **센싱 데이터 업데이트 (`UpdateSensingData`)**: 클라이언트가 기기 ID("sensor-room-01")와 수집한 온/습도 환경 변수를 전달하면, 서버는 이를 수신하여 화면에 상세 센싱 값을 출력하고 업데이트 완료 성공 응답을 전송합니다. +### 5.3 기기와 서버의 핑퐁 대화 흐름 +실습 예제를 실행하면 서버와 클라이언트가 다음과 같이 대화를 나눕니다: +1. **서버 시간 물어보기**: 기기가 *"지금 몇 시인가요?"* 하고 문을 두드리면, 서버는 시스템의 현재 날짜와 시간 정보를 보기 좋게 반환해 줍니다. +2. **비밀번호 생성**: 기기가 시드값과 길이를 주면, 서버는 불규칙하게 글자들을 마구 섞어 일회용 보안 패스워드를 발급해 줍니다. +3. **온습도 데이터 전송**: 기기가 *"현재 방 안 온도는 24.50°C이고 습도는 52.30%입니다!"* 하고 소리치면, 서버는 이를 받아 화면에 출력하고 *"데이터가 무사히 갱신되었습니다"* 라고 기분 좋게 응답해 줍니다. --- -## 6. 트러블슈팅 (Troubleshooting) +## 6. 개발하다 막혔을 때 찾아보는 해결사 가이드 (트러블슈팅) -gRPC 서버 및 클라이언트 실습 과정에서 직면할 수 있는 대표적인 문제와 해결 방안입니다. +실습을 진행하다가 갑작스레 에러를 마주했을 때 당황하지 않고 해결할 수 있는 가이드입니다. -### 6.1 `listen tcp :8080: bind: address already in use` -* **원인**: 포트 `8080`이 이미 다른 백그라운드 프로세스나 기존 기동된 서버에 의해 점유되어 충돌이 난 상태입니다. +### 6.1 `bind: address already in use` (문 번호가 꽉 막혔을 때) +* **원인**: gRPC 서버를 켜려고 하는데 이미 다른 백그라운드 프로그램(혹은 덜 꺼진 이전 실습 서버)이 포트 번호 `:8080`을 꽉 쥐고 있어 문을 열지 못하는 상황입니다. * **해결 방법**: - - `examples/main.go` 의 `port` 변수값과 [server.go](../examples/grpcentity/server.go)의 `port` 전역 변수값을 동시에 다른 포트(예: `:9090`)로 변경하고 재시도해야 합니다. - - **주의**: `server.go` 내부의 `ServerRun(addr string)` 함수는 외부 진입점으로부터 인자 `addr`을 인가받지만, 실제 포트 리슨 코드에서는 이를 무시하고 패키지 전역 변수 `port = ":8080"`를 직접 읽어 처리하도록 하드코딩되어 있습니다. 따라서 정상적으로 포트를 바꾸기 위해서는 반드시 `server.go` 내부 전역 변수인 `port` 값을 수정해 주어야 포트 바인딩이 성공합니다. + - `examples/main.go`의 `port` 변수값과 [server.go](../examples/grpcentity/server.go)의 `port` 전역 변수값을 동시에 다른 번호(예: `:9090`)로 변경하고 다시 실행해 보십시오. + - **🚨 미묘한 함정**: `server.go` 내부의 `ServerRun(addr string)` 함수는 외부 진입점으로부터 인자 `addr`을 인가받지만, 실제 포트 리슨 코드에서는 이를 슬쩍 무시하고 자체 패키지 전역 변수 `port = ":8080"`를 직접 읽어 처리하도록 하드코딩되어 있습니다. 따라서 정상적으로 포트를 바꾸기 위해서는 반드시 `server.go` 내부 전역 변수인 `port` 값을 수정해 주어야 포트 바인딩이 성공합니다. --- -## 7. 다음 단계 (Next Steps) +## 7. 한 걸음 더 나아가기 (다음 단계) -gRPC 통신 방식을 한층 더 깊이 탐구해 보려면 다음과 같은 후속 실습을 추천합니다: -1. **메시지 스펙 확장**: 루트 디렉토리의 [protoapi.proto](../protoapi.proto)에 새로운 필드를 추가하거나 메서드를 정의한 뒤, Stub을 재컴파일([examples/grpcentity/README.md](../examples/grpcentity/README.md) 컴파일 가이드 참고)해 보십시오. -2. **인터페이스 모듈 연계**: 생성된 gRPC 클라이언트 및 서버 stub 인터페이스를 활용하여, 향후 분산 AIoT 환경에서의 센서 데이터 수집이나 에이전트 간 제어 메시지 전송 로직을 설계해 보십시오. +본 기초 실습을 끝마치셨다면, 함께 학습하는 후배나 동료분들에게 아래와 같은 도전 과제들을 제안해 보십시오: +1. **약속 스펙 확장해 보기**: [protoapi.proto](../protoapi.proto) 파일에 새로운 환경 데이터(예: 미세먼지 수치 `double Dust = 4;`)를 슬쩍 얹어본 뒤, 직접 번역기를 새로 돌리고 Go 소스코드를 고치며 확장해 봅니다. +2. **실전 분산 환경 상상하기**: 수많은 자율 에이전트나 IoT 센서 단말이 하나의 gRPC 중앙 관제 서버로 동시에 데이터를 주고받는 분산 AIoT 멀티 에이전트 인프라로의 아이디어를 고민해 봅니다. --- ## 8. 참고 자료 -* [gRPC와 REST의 차이점 (AWS)](https://aws.amazon.com/ko/compare/the-difference-between-grpc-and-rest/) +* [gRPC와 REST의 차이점 (AWS)](https://aws.amazon.com/ko/compare/the-difference-between-grpc-and-rest/): 두 방식의 특징과 언제 어떤 기술을 선택해야 하는지 친절하게 정리된 공식 블로그 자료입니다.