본문으로 건너뛰기
Background Image
  1. 프로젝트/
  2. WindRPC/

WindRPC 개발기 3 - RPC 서버 구현 및 생성

·5 분· loading · loading ·
WindRPC - 이 글은 시리즈의 일부입니다.
부분 : 이 글

RPC 서버 구현 및 생성
#

마이크로시스템과 상위 애플리케이션 간의 원격 프로시저 호출(RPC)을 지원하기 위한 경량 프레임워크 WindRPC 개발 이야기의 세 번째 편입니다.


RPC 수동 구현과 모듈 분리
#

NanoPB가 가진 복잡한 디코딩 콜백의 한계를 극복하고 펌웨어에서 실질적으로 동작하는 서버를 만들기 위해, 우선 C 언어로 서버 엔진의 핵심 모듈을 직접 손으로 구현해 나가기 시작했습니다.

당시 설계했던 C 서버 엔진의 핵심 모듈 구조는 다음과 같았습니다.

  • windrpc.c (메인 디스패처): 수신된 바이너리 패킷의 헤더를 해석하고 이벤트 루프를 순회하는 프레임워크의 진입점
  • 핸들러 모듈 (Handler): 요청 메시지가 도착했을 때 패킷을 디코딩하고, 비즈니스 로직을 호출한 뒤, 그 결과를 응답 메시지로 인코딩하는 처리기
  • 사용자 콜백 모듈 (Callbacks): 펌웨어 개발자가 실제 비즈니스 로직(예: LED 색상 변경, 전원 상태 조회 등)을 작성하여 등록할 수 있는 인터페이스
  • 비동기 알림 모듈 (Notify): 클라이언트의 직접적인 요청 없이도 센서 값 변경이나 디바이스 이벤트를 서버가 자발적으로 푸시(Push)할 수 있는 전용 모듈

이렇게 모듈을 구성한 뒤 실제 테스트 코드를 작성하여 기본 동작성을 검증했습니다. 패킷 전송과 수신, 분기 자체는 정상적으로 동작함을 확인할 수 있었습니다.

반복 패턴의 추출과 C 매크로 도입
#

수동 구현한 C 서버 코드의 동작성을 검증하고 난 뒤, 코드 전체를 돌아보며 구조화 작업을 진행했습니다.

서버 코드 내부를 살펴보니 각 프로시저(RPC)마다 메시지를 읽어오고 태그를 비교하며 응답을 만드는 과정에서 엄청나게 많은 보일러플레이트 코드가 반복되고 있었습니다.

이 반복되는 요소를 발라내어 C 선처리 매크로(C Preprocessor Macro)로 정제하기 시작했습니다.

  • 메시지 태그 및 타입 축약: NanoPB가 생성한 지나치게 긴 메시지 타입 명칭(hlt_windrpc_service_led_Request_display_pixels_tag 등)을 매크로 합치기 연산자(##)를 활용해 짧고 직관적으로 작성할 수 있도록 래핑
  • 보일러플레이트 자동 생성: 반복되는 디코딩 스위치-케이스 구문과 메시지 초기화 루틴이 매크로 선언만으로 자동으로 만들어지도록 구조화
// 당시에 작성했던 C 매크로 파이프라인 예시 (windrpc_defs.h)
#define WINDRPC_COMMAND_ID(SERVICE, COMMAND) \
    WINDRPC_CAT(WINDRPC_PACKAGE_NAME, _windrpc_core_RpcId_RPC_ID_, SERVICE, _, COMMAND)

#define WINDRPC_DECODE_SERVICE_ENTRY(service) \
    case WINDRPC_SERVICE_REQUEST_TAG(service): { \
        WINDRPC_SERVICE_REQUEST_TYPE(service) *msg = (WINDRPC_SERVICE_REQUEST_TYPE(service) *)field->pData; \
        msg->cb_command.funcs.decode = decode_service_##service; \
        msg->cb_command.arg = ctx; \
        return pb_decode(stream, WINDRPC_SERVICE_REQUEST_FIELDS(service), msg); \
    }

템플릿 구조와 패턴 코드 주입 방식에서 코드 생성
#

반복되는 파싱 및 디코딩 패턴을 발라내고 난 뒤, 변하지 않는 프레임워크 뼈대 코드는 템플릿(Template)으로 고정해 두고, 정의된 서비스나 프로시저 명세에 따라 달라지는 패턴 코드 조각만 주입(Injection)하도록 코드 생성기를 구성했습니다.

  • 공통 템플릿 코드: 이벤트 루프, 트랜잭션 관리, 기본 디스패치 루틴 등 변하지 않는 프레임워크 핵심 뼈대는 템플릿으로 유지
  • 패턴 코드 동적 주입: user_spec.yml 명세에 정의된 서비스 및 프로시저 정보에 따라 필요한 매크로와 디코딩/인코딩 패턴 코드만 생성하여 템플릿의 치환 위치에 주입

이렇게 템플릿 뼈대와 정제된 패턴 코드 주입 구조를 결합하면서, 새로운 서비스나 프로시저가 계속 추가되더라도 매번 서버 전체 코드를 수정하거나 복잡하게 새로 작성할 필요 없이, 지정된 패턴 코드만 깔끔하게 자동으로 합성되는 생산성 높은 자동 생성기 구조를 갖추게 되었습니다.

프로시저 처리 파이프라인의 구조화 (Decode -> Execute -> Encode)
#

매크로를 도입함과 동시에, 모든 RPC 호출이 일관된 흐름으로 실행되도록 프로시저 처리 파이프라인을 3단계로 명확히 구조화했습니다.

  1. Decode (디코딩 단계): 수신된 바이너리 패킷을 파싱하여 C 구조체로 변환하고 인자 값을 검증
  2. Execute (실행 단계): 개발자가 등록해 둔 비즈니스 로직 콜백 함수를 호출하여 실제 동작 수행
  3. Encode (인코딩 단계): 콜백 함수의 리턴 값 및 상태 코드를 묶어 응답 패킷으로 직렬화 후 전송

프로시저마다 이 3단계 파이프라인이 정해진 순서에 맞게 호출되도록 가동 환경을 다듬었습니다.

이를 통해 펌웨어 개발자는 수신 패킷을 어떻게 풀고 인코딩할지 고민할 필요 없이, 파이프라인 중간의 Execute 단계에 해당하는 콜백 함수만 구현하면 되도록 C 서버 프레임워크의 틀을 갖추어 나갔습니다.

// 1. 프로시저 처리 3단계 파이프라인 구조체 (windrpc.h)
struct windrpc_procedure {
    bool (*decode_cmd)(pb_istream_t *stream, const pb_field_t *field, void **arg); // 1) Decode
    int32_t (*execute)(struct windrpc_operation *operation, void *context);      // 2) Execute (콜백)
    void (*encode_res)(windrpc_response_msg_t *message, void *context);          // 3) Encode
};

// 2. 서비스별 프로시저 구조체 및 사용자 서비스 바인딩
struct windrpc_service_power {
    struct windrpc_procedure read_power_info;
    struct windrpc_procedure subscribe_power_info;
};

struct windrpc_user_service {
    struct windrpc_service_led *led;
    struct windrpc_service_power *power;
};
// 3. Execute 단계에서 호출되는 사용자 비즈니스 로직 콜백 구현 예시
int32_t windrpc_on_power_read_power_info(struct windrpc_operation *op, void *context) {
    // 펌웨어 개발자가 작성하는 순수 비즈니스 로직
    op->server_msg.response.power.voltage_mv = 3300;
    op->server_msg.response.power.is_charging = true;
    return 0; // 성공 리턴 시 Encode 단계로 전달되어 응답 패킷 자동 전송
}

코드 생성기 도입과 남아있던 한계
#

이처럼 템플릿 기반 코드 생성기를 도입하면서 서버 초안(Skeleton) 코드를 작성하는 것 자체는 훨씬 수월해졌습니다.

그러나 여전히 개발 초기의 유지보수가 쉽지 않은 문제가 있었습니다. 개발 초기에는 RPC 메시지 타입 내부의 필드나 데이터 구조를 조금씩 변경할 때마다 서버 로직도 함께 변경되어야 했기 때문에 관리가 꽤 까다로웠습니다. 그럼에도 불구하고 시간이 지나 RPC 메시지 구조가 어느 정도 안정화되고 확립된 이후에는 서버 생성기 역시 매우 안정적인 형태로 자리를 잡게 되었습니다.

다만 가변 길이를 갖는 동적 데이터(문자열, 가변 배열 등)를 다룰 때에는 여전히 NanoPB의 디코딩 콜백이나 익숙하지 않은 데이터 처리 방식을 사용자가 감안해야 한다는 단점이 있었습니다. 하지만 이러한 단점에도 불구하고, 이 초기 버전의 WindRPC를 기반으로 당시 진행하던 빛나리(Bitnari) 프로젝트에 적용하여 실제로 활용하기 시작했습니다.


정리하며
#

이후 개인적인 사정으로 인해 빛나리 프로젝트와 WindRPC 개발을 오랜 시간 동안 더 이상 진행하지 못하고, 프로젝트는 긴 시간 동안 계류된 상태로 머물게 되었습니다.

그리고 오랜 시간이 흘러 최근에 들어서야 프로젝트를 다시 재개하게 되었는데, 이 재개 과정에서 WindRPC 프레임워크에도 아주 커다란 변화점이 생겨나기 시작했습니다.

다음 포스팅에서는 장기 계류 이후 최근 프로젝트를 재개하며 일어난 커다란 변화점들과, 완전 자동화를 이루어 낸 이야기를 정리해 보겠습니다.

WindRPC - 이 글은 시리즈의 일부입니다.
부분 : 이 글

관련 글

WindRPC 개발기 2 - Proto 초기 설계와 C 서버 코드 자동화
·5 분· loading · loading
Proto 초기 설계와 C 서버 코드 자동화 # 마이크로시스템과 상위 애플리케이션 간의 원격 프로시저 호출(RPC)을 지원하기 위한 경량 프레임워크 WindRPC 개발 이야기의 두 번째 편입니다.
WindRPC 개발기 1 - 프로젝트의 시작
·8 분· loading · loading
프로젝트의 시작 # 마이크로시스템과 상위 애플리케이션 간의 원격 프로시저 호출(RPC)을 지원하기 위한 경량 프레임워크 WindRPC (Micro Interconnect & Network Dispatch)의 개발 이야기입니다.
[빛나리] 주간 작업 일지
·15 분· loading · loading
주차별 진행했던 작업 또는 고찰에 대해서 간략하게 정리 합니다. 처음부터 보시면 의식의 흐름대로 정처없이 흘러가는 작업 내용을 보실 수 있습니다.