이해하기 쉬운 RPC와 Stub 설명

목화·2023년 5월 7일
0

3.8.2 Remote Procedure Calls

RPC(Remote Procedure Call)는 원격 프로시저 호출을 가능하게 해주는 프로토콜입니다. 이를 통해 클라이언트는 원격 호스트에 있는 프로시저를 마치 로컬에서 호출하는 것처럼 사용할 수 있습니다. 이러한 프로세스를 이해하기 쉽게 설명해 드리겠습니다.

먼저, stub이란 프로시저 호출을 추상화하는 작은 코드 조각입니다. RPC에서 stub은 클라이언트와 서버 사이에 원격 프로시저 호출을 처리하기 위해 사용됩니다.

RPC의 작동 과정은 다음과 같습니다:

  1. 클라이언트에서 원격 프로시저를 호출하려면, 클라이언트 측에 stub이 필요합니다. 이 stub은 각각의 원격 프로시저에 대해 별도로 존재합니다.
  2. 클라이언트가 원격 프로시저를 호출하면, RPC 시스템은 해당 stub을 호출하고 원격 프로시저에 제공된 매개변수를 전달합니다.
  3. 클라이언트 측 stub은 서버의 포트를 찾아 매개변수를 정리(marshal)한 다음 메시지를 사용해 서버로 정보를 전송합니다.
  4. 서버 측에도 유사한 stub이 존재하며, 이 stub은 메시지를 받아 서버에서 프로시저를 호출합니다.
  5. 필요한 경우, 반환값은 같은 방법을 사용해 클라이언트로 전달됩니다.

Windows 시스템에서는, Microsoft Interface Definition Language(MIDL)이라는 언어를 사용하여 클라이언트와 서버 프로그램 간의 인터페이스를 정의합니다. 이를 통해 클라이언트와 서버 사이의 원격 프로시저 호출을 처리하는 stub 코드가 생성됩니다.

간단히 말해, RPC를 사용하면 클라이언트는 원격 호스트의 프로시저를 로컬에서 호출하는 것처럼 사용할 수 있으며, 클라이언트와 서버 사이에서 통신을 처리하는 복잡한 과정은 stub을 통해 추상화됩니다.


Stub

Stub은 원격 프로시저 호출(RPC)에서 클라이언트와 서버 간의 통신을 추상화하고 단순화하는 데 사용되는 코드 조각입니다. 이는 클라이언트와 서버 모두에 존재하며, 호출 및 반환 과정을 처리하는 역할을 합니다. Stub을 통해 원격 프로시저 호출의 복잡성을 숨기고 프로그래머가 원격 프로시저를 마치 로컬 프로시저처럼 호출할 수 있도록 해줍니다.

클라이언트 측 stub:
클라이언트 측 stub은 클라이언트에서 원격 프로시저를 호출할 때 사용됩니다. 클라이언트 측 stub의 주요 역할은 다음과 같습니다:

  1. 클라이언트에서 원격 프로시저 호출을 받습니다.
  2. 매개변수를 정리(marshal)하여 원격 서버에 전송하기 적합한 형태로 변환합니다.
  3. 서버와 통신하기 위해 네트워크 프로토콜을 사용해 데이터를 전송합니다.

서버 측 stub:
서버 측 stub은 클라이언트로부터 요청을 받아 처리하는 역할을 합니다. 서버 측 stub의 주요 역할은 다음과 같습니다:

  1. 클라이언트로부터 전송된 데이터를 받습니다.
  2. 데이터를 원래 형태로 복원(unmarshal)합니다.
  3. 원격 프로시저를 호출하고, 결과를 반환합니다.
  4. 반환값을 다시 정리(marshal)하여 클라이언트에 전송하기 적합한 형태로 변환합니다.
  5. 반환값을 클라이언트에 전송합니다.

Stub을 사용하면 개발자들은 네트워크 통신 및 데이터 변환과 관련된 복잡한 과정을 직접 처리할 필요 없이 원격 프로시저를 손쉽게 호출할 수 있습니다. 이를 통해 분산 시스템 개발이 더욱 단순화되고 효율적으로 이루어질 수 있습니다.


Stub에 대한 예시를 들기 위해 간단한 원격 프로시저 호출 구현을 살펴보겠습니다. 이 예시에서는 클라이언트가 서버에 두 숫자의 합을 계산하도록 요청합니다.

먼저, 클라이언트와 서버 간에 사용할 인터페이스를 정의해야 합니다. 여기서는 간단한 인터페이스로 두 숫자의 합을 반환하는 'add' 함수를 정의하겠습니다.

// example_interface.h

int add(int a, int b);

이제 클라이언트 측 및 서버 측에 각각 stub을 작성합니다.

클라이언트 측 stub:

// client_stub.c

#include "example_interface.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int add(int a, int b) {
    // 클라이언트 측 stub이 실제 원격 프로시저 호출을 처리합니다.
    
    // 1. 매개변수를 정리(marshal)합니다.
    char buffer[256];
    sprintf(buffer, "%d %d", a, b);

    // 2. 네트워크를 통해 서버에 데이터를 전송합니다 (여기서는 단순화된 예시입니다).
    send_data_to_server(buffer);

    // 3. 서버로부터 반환값을 받습니다.
    char response[256];
    receive_data_from_server(response);

    // 4. 반환값을 원래 형태로 복원(unmarshal)하고 반환합니다.
    int result;
    sscanf(response, "%d", &result);
    return result;
}

서버 측 stub:

// server_stub.c

#include "example_interface.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void handle_request(const char* request, char* response) {
    // 서버 측 stub이 클라이언트로부터의 요청을 처리합니다.

    // 1. 요청을 원래 형태로 복원(unmarshal)합니다.
    int a, b;
    sscanf(request, "%d %d", &a, &b);

    // 2. 원격 프로시저를 호출하고 결과를 반환합니다.
    int result = add(a, b);

    // 3. 반환값을 정리(marshal)하여 클라이언트에 전송하기 적합한 형태로 변환합니다.
    sprintf(response, "%d", result);
}

이 예시에서 클라이언트 측 및 서버 측 stub은 각각 원격 프로시저 호출에 필요한 과정을 처리합니다. 이를 통해 개발자는 원격 프로시저 호출의 복잡한 내부 구현을 몰라도 간단하게 원격 프로시저를 사용할 수 있습니다.


References

  • GPT4
profile
🧑‍💻 SOFTWARE ENGINEER. 무해를 지향합니다. 편견을 지양합니다.

0개의 댓글