From 600a95080630cdff054de98163c9ce7be103ed95 Mon Sep 17 00:00:00 2001 From: Godopu Date: Fri, 17 Jul 2026 20:09:51 +0900 Subject: [PATCH] docs: remove Section 8 (Next Steps), move Troubleshooting to Section 7, and renumber Streaming to Section 6 --- docs/GRPC.md | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/docs/GRPC.md b/docs/GRPC.md index 0c50950..8b88bc6 100644 --- a/docs/GRPC.md +++ b/docs/GRPC.md @@ -272,24 +272,13 @@ protoc --go_out=. --go-grpc_out=. protoapi.proto --- -## 6. 트러블슈팅 (Troubleshooting) - -실습 구동 과정에서 마주할 수 있는 전형적인 에러 현상과 대처 방안입니다. - -### 6.1 `bind: address already in use` (네트워크 소켓 포트 충돌) -* **발생 원인**: gRPC 서버 기동 시 설정한 통신 포트 `:8080`이 이미 다른 네트워크 프로세스나 이전 실습 서버의 비정상 종료 등으로 인해 점유되어 바인딩에 실패한 상태입니다. -* **조치 방법**: - - `lib/main.go`의 `port` 변수 값을 다른 유휴 포트(예: `:9090`)로 변경한 뒤 다시 실행하십시오. `ServerRun(addr)` 함수가 이 값을 메인 진입점으로부터 인자로 전달받아 TCP 리스너를 생성하므로, `main.go` 한 곳만 수정하면 서버와 클라이언트의 통신 포트가 동시에 성공적으로 변경됩니다. - ---- - -## 7. 대용량 데이터 전송을 위한 스트리밍(Streaming) 구현 +## 6. 대용량 데이터 전송을 위한 스트리밍(Streaming) 구현 쉽게 말해, 지금까지 배운 방식(Unary)이 '궁금한 게 있을 때마다 전화를 걸어 질문 하나씩 하고 바로 끊는 것'이라면, 스트리밍은 **'전화를 한 번 걸어 끊지 않은 상태로, 준비된 데이터 조각(Chunk)들을 긴 띠처럼 끊임없이 흘려보내는 것'**입니다. 큰 파일을 한 번에 통째로 보내면 메모리가 가득 차서 전송하기 어려우므로, 전화선은 그대로 유지한 채 데이터를 작게 나누어 연속으로 전달하는 방법을 배웁니다. 일반적인 단발성 요청/응답(Unary) 통신은 전송할 전체 데이터를 단일 메모리에 전부 올려 적재한 상태에서 동작하므로, 펌웨어나 대형 이미지 같은 대용량 데이터를 다룰 때 메모리 고갈(OOM)이나 네트워크 대역폭 병목을 초래하기 쉽습니다. gRPC는 HTTP/2 프로토콜의 스트림(Stream) 채널을 기본 가용하므로, 데이터를 일정 크기(Chunk) 단위로 쪼개 연속적으로 전송할 수 있는 강력한 **스트리밍(Streaming)** 기법을 지원합니다. 본 예제에서는 클라이언트가 파일을 조각내어 보내는 **클라이언트 스트리밍(Client Streaming)**, 업로드된 파일 메타 정보를 모아 한 번에 내려주는 **단일 조회(Unary RPC)**, 그리고 서버가 데이터를 쪼개어 클라이언트에게 보내는 **서버 스트리밍(Server Streaming)**까지 모두 종합 설계하여 탑재했습니다. -### 7.1 스키마 설계 (`protoapi.proto`) +### 6.1 스키마 설계 (`protoapi.proto`) 업로드와 리스트 조회, 그리고 다운로드를 위한 gRPC 메시지 규격을 명세합니다: ```proto service IoTService { @@ -328,7 +317,7 @@ message DownloadRequest { ``` * **설계 포인트**: `stream` 키워드가 들어간 위치에 주목합니다. `UploadFile`은 입력에 `stream`이 붙어 클라이언트 스트리밍을, `DownloadFile`은 반환(returns)에 `stream`이 붙어 서버 스트리밍 채널을 개설합니다. -### 7.2 서버 저장 및 송수신 구현 ([server.go](../lib/grpcentity/server.go)) +### 6.2 서버 저장 및 송수신 구현 ([server.go](../lib/grpcentity/server.go)) 인메모리 파일 저장소(`fileStore`)를 구현하고 목록 조회(`ListFiles`) 및 서버 다운로드 스트리밍(`DownloadFile`) 핸들러를 정의합니다: ```go @@ -438,7 +427,7 @@ func (IoTServer) DownloadFile(r *protoapi.DownloadRequest, stream protoapi.IoTSe * **목록 조회**: 동시 접근 보호(Race condition 방지)를 위해 읽기 전용 락(`RLock`)을 획득한 후 인메모리 맵을 순회하며 메타데이터 구조체 목록을 집계해 반환합니다. * **파일 다운로드**: 대상 파일 쿼리 실패 시 gRPC 표준 에러(`codes.NotFound`)를 반환합니다. 검증 통과 시 루프 내에서 가상 윈도우 슬라이싱을 집행해 청크 구조체를 구성하고, `stream.Send()`로 직렬화 패킷을 클라이언트 버퍼 큐에 기입합니다. -### 7.3 클라이언트 송수신 기동 ([client.go](../lib/grpcentity/client.go)) +### 6.3 클라이언트 송수신 기동 ([client.go](../lib/grpcentity/client.go)) 클라이언트는 업로드에 성공한 뒤, 서버에 파일 목록 조회를 요구하고, 다운로드 스트림을 개설해 조각 데이터를 재조립하여 무결성을 검사합니다. ```go @@ -505,7 +494,7 @@ func AskDownloadFile(ctx context.Context, m protoapi.IoTServiceClient, fileName * **목록 조회**: 빈 메시지(`EmptyRequest`)를 동봉해 Unary RPC 채널을 트리거하고 메타데이터 배열 결과를 동기 획득합니다. * **파일 다운로드**: 서버 스트리밍 엔드포인트 기동 후, `stream.Recv()` 블로킹 수신 루프에 진입합니다. 채널 해제 지점(`io.EOF`)에 도달할 때까지 메모리 버퍼 슬라이스에 청크 바이트 배열을 병합 누적하여 재조립(Reassembly)을 마친 후 반환합니다. -### 7.4 실시간 알림을 위한 Pub/Sub (발행/구독) 브로드캐스팅 구현 +### 6.4 실시간 알림을 위한 Pub/Sub (발행/구독) 브로드캐스팅 구현 쉽게 말해 Pub/Sub은 '신문 구독'과 같습니다. 구독자(클라이언트)가 한 번 신청해 두면, 발행자(서버)는 새로운 소식(경보)이 생길 때마다 모든 구독자에게 알아서 배달해 줍니다. 클라이언트가 매번 '무슨 일 없어요?'라고 다시 물어볼 필요가 없다는 것이 핵심입니다. @@ -630,15 +619,17 @@ func AskSubscribeAlerts(ctx context.Context, m protoapi.IoTServiceClient, client --- -## 8. 한 걸음 더 나아가기 (다음 단계) +## 7. 트러블슈팅 (Troubleshooting) -본 기초 실습을 끝마치셨다면, 아래 과제를 해결해보세요: +실습 구동 과정에서 마주할 수 있는 전형적인 에러 현상과 대처 방안입니다. -1. **약속 스펙 확장해 보기**: [protoapi.proto](../lib/grpcentity/protoapi.proto) 파일에 새로운 환경 데이터(예: 미세먼지 수치 `double Dust = 4;`)를 슬쩍 얹어본 뒤, 직접 번역기를 새로 돌리고 Go 소스코드를 고치며 확장해 봅니다. -2. **proto 명세서 물리 분할하기 (실전 모듈화)**: 현재 단일 파일로 구성된 `protoapi.proto`를 기능 성격에 맞춰 `iot_service.proto`(Unary 제어 메시지)와 `file_transfer.proto`(스트리밍 전송 메시지) 2개의 파일로 쪼개어 구성해 봅니다. 이때 두 파일의 `option go_package` 공유 방식과 번역기(`protoc`) 명령어를 다중 타겟(또는 `*.proto`)으로 조율하여 정상 빌드해 보세요. +### 7.1 `bind: address already in use` (네트워크 소켓 포트 충돌) +* **발생 원인**: gRPC 서버 기동 시 설정한 통신 포트 `:8080`이 이미 다른 네트워크 프로세스나 이전 실습 서버의 비정상 종료 등으로 인해 점유되어 바인딩에 실패한 상태입니다. +* **조치 방법**: + - `lib/main.go`의 `port` 변수 값을 다른 유휴 포트(예: `:9090`)로 변경한 뒤 다시 실행하십시오. `ServerRun(addr)` 함수가 이 값을 메인 진입점으로부터 인자로 전달받아 TCP 리스너를 생성하므로, `main.go` 한 곳만 수정하면 서버와 클라이언트의 통신 포트가 동시에 성공적으로 변경됩니다. --- -## 9. 참고 자료 +## 8. 참고 자료 * [gRPC와 REST의 차이점 (AWS)](https://aws.amazon.com/ko/compare/the-difference-between-grpc-and-rest/): 두 방식의 특징과 언제 어떤 기술을 선택해야 하는지 친절하게 정리된 공식 블로그 자료입니다.