Rust gRPC 사용하기

2023. 3. 6. 15:24Language/Rust

gRPC란?

gRPC는 Google에서 개발한 고성능 오픈소스 범용 RPC(Remote Procedure Call) 시스템입니다. RPC는 Client가 Server로 어떠한 요청을 하면 요청 받은 함수가 실행 되며 결과값을 리턴해주는 것을 말합니다.

gRPC는 거의 모든 언어를 지원하기 때문에 MSA에서 많이 사용됩니다.

gRPC는 protobuf라는 파일이 존재하며 이 파일을 직렬화하여 주고 받기 때문에 JSON이나 XML에 비해 굉장히 전송 속도가 빠릅니다.

하지만 Type을 알아야하기 때문에 Server측, Client측 모두 같은 protobuf를 가지고 있어야하는 번거러움이 있습니다.

Rust에서 사용해보기

protoc 설치

protobuf를 컴파일하여 사용하기 위해서는 OS에 protoc가 설치되어 있어야 합니다.

https://github.com/protocolbuffers/protobuf/releases 자신에게 맞는 OS에 맞춰 설치를 진행하면 됩니다.

저의 경우 Windows11 이기 때문에 protoc-22.0-win64.zip을 다운받았습니다.

압축을 풀게 되면 아래와 같이 bin 폴더 안에 protoc.exe가 존재합니다.

위 파일은 rust를 빌드 할 때 protobuf를 컴파일하기 위해 필요합니다.

그래서 전역에서 사용 할 수 있도록 시스템 변수에 protoc.exe를 추가해 줍니다.

저는 해당 파일은 C:\\Program Files\ 에 풀어 사용하였습니다.

터미널을 띄워 아래 처럼 버전 정보가 출력 되면 됩니다.

PS C:\Users\ys.park> protoc --version
libprotoc 22.0
PS C:\Users\ys.park>

프로젝트 생성

PS C:\Users\ys.park\Desktop\Rust> cargo new grpc-test
     Created binary (application) `grpc-test` package
PS C:\Users\ys.park\Desktop\Rust> cd .\grpc-demo\
PS C:\Users\ys.park\Desktop\Rust\grpc-demo> code .

cargo를 통해 grpc-test라는 프로젝트를 생성하고 code .으로 Visual Studio Code를 실행 시켰습니다.

protobuf 파일 추가하기

src/proto라는 폴더를 생성하고 안에 hello.proto 파일을 생성합니다.

hello.proto로 server와 client가 주고 받을 때 사용 됩니다.

// version of protocol buffer used
syntax = "proto3";

// package name for the buffer will be used later
package hello;

// service which can be executed
service Say {
// function which can be called
    rpc Send (SayRequest) returns (SayResponse);
}

// argument
message SayRequest {
// data type and position of data
    string name = 1;
}

// return value
message SayResponse {
// data type and position of data
    string message = 1;
}

위와 같이 작성합니다.

package는 이 파일의 namespace와 같은 역할을 합니다.

service는 주고 받는 grpc의 서비스 부분으로 Rust에서 Request를 받았을 때 로직을 작성 할 수 있습니다.

message는 주고 받는 메시지입니다.

Server.rs와 Client.rs 추가

main.rs는 사용하지 않기 때문에 삭제하고 server.rsclient.rs 파일을 생성합니다.

의존성 추가

Cargo.toml에 아래와 같이 의존성을 추가해 줍니다.

[package]
name = "grpc-test"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
# server binary
tonic = "0.8.3"
prost = "0.11.8"
tokio = {version = "1.0", features = ["macros", "rt-multi-thread"]}

[build-dependencies]
tonic-build = "0.8.3"

[[bin]]
name = "server"
path = "src/server.rs"

# client binary
[[bin]]
name = "client"
path = "src/client.rs"

build.rs 추가

build 시 protobuf를 같이 빌드하여 사용하기 위해서는 build.rs가 필요합니다.

아래와 같이 build.rs를 작성합니다.

fn main() -> Result<(), Box<dyn std::error::Error>> {
    tonic_build::compile_protos("src/proto/hello.proto")?;
    Ok(())
}

compile_protos의 경로는 현재 build.rs가 있는 곳에서 상대 경로로 적어주면 됩니다.

server.rs

use hello::say_server::{Say, SayServer};
use hello::{SayRequest, SayResponse};
use tonic::{transport::Server, Request, Response, Status};

pub mod hello {
    tonic::include_proto!("hello");
}

#[derive(Debug, Default)]
pub struct SayService {}

#[tonic::async_trait]
impl Say for SayService {
    async fn send(
        &self,
        request: Request<SayRequest>,
    ) -> Result<Response<SayResponse>, Status> {
            println!("request : {}", request.get_ref().name);
        Ok(Response::new(SayResponse {
            message:format!("hello {}", request.get_ref().name),
        }))
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let addr = "[::1]:50051".parse()?;
    let hello_service = SayService::default();

    println!("Server listening on {}", addr);

    Server::builder()
        .add_service(SayServer::new(hello_service))
        .serve(addr)
        .await?;

    Ok(())
}

client.rs

use hello::say_client::SayClient;
use hello::SayRequest;

pub mod hello {
    tonic::include_proto!("hello");
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let channel = tonic::transport::Channel::from_static("http://[::1]:50051")
                            .connect().await?;


    let mut client = SayClient::new(channel);
    let request = tonic::Request::new(SayRequest {
        name:String::from("Client!!"),
    });

    let response = client.send(request).await?;

    println!("RESPONSE={:?}", response);

    Ok(())
}

빌드 및 실행

아래 명령어로 build를 실행합니다. 여러 라이브러리를 설치하고 빌드됩니다.

> cargo build

Finished dev [unoptimized + debuginfo] target(s) in 3.62s

서버만 먼저 실행합니다. 50051 포트로 grpc request를 기다리고 있습니다.

> cargo run --bin server

    Finished dev [unoptimized + debuginfo] target(s) in 0.24s
     Running `target\debug\server.exe`
Server listening on [::1]:50051

아래 명령으로 클라이언트를 실행해봅니다.

> cargo run --bin client
    Finished dev [unoptimized + debuginfo] target(s) in 0.25s
     Running `target\debug\client.exe`
RESPONSE=Response { metadata: MetadataMap { headers: {"content-type": "application/grpc", "date": "Mon, 06 Mar 2023 06:23:11 GMT", "grpc-status": "0"} }, message: SayResponse { message: "hello Client!!" }, extensions: Extensions }

Server로 SayReqeust를 날리고 SayRespone를 받은 것을 볼 수 있습니다.

 

'Language > Rust' 카테고리의 다른 글

Rust Struct(구조체)  (1) 2023.03.12
Rust 매크로  (0) 2023.03.11
Rust로 폴더 감시자 만들기  (0) 2023.03.05
Rust 마스코트로 콘솔 메시지 출력  (1) 2023.03.05
Rust Dangling Pointer  (0) 2023.02.25