[gRPC] Python gRPC Server/Client 구현

참조


소개

  • 안녕하세요. 오늘은 Python 에서 gRPC Server/Client 코드 구현하는 방법에 대해서 설명 드리려고 합니다.
  • 간단하게 calc.proto 버퍼 하나를 생성하고, Python 코드로 gRPC Server/Cleint 를 구현하여 통신하는 예제 코드를 작성하도록 하겠습니다.

calc.proto 버퍼 작성

  • 제일 먼저, calc.proto 프로토콜 버퍼 하나를 생성하고 아래와 같이 내용을 작성합니다.
  • 간단히 calc.proto 버퍼 파일 안에는 덧셈, 뺄셈, 곱셈, 나눗셈의 Input, Output 결과값을 Server, Client 에서 알 수 있는 Service와 Message 들을 작성하였습니다.
syntax = "proto3";

package calc;

service Calculator {
    rpc Add (AddRequest) returns (AddReply) {}
    rpc Substract (SubstractRequest) returns (SubstractReply) {}
    rpc Multiply (MultiplyRequest) returns (MeuliplyReply) {}
    rpc Divide (DivideRequest) returns (DivideReply) {}
}

message AddRequest {
    int32 n1=1;
    int32 n2=2;
}

message AddReply {
    int32 n1=1;
}

message SubstractRequest {
    int32 n1=1;
    int32 n2=2;
}

message SubstractReply {
    int32 n1=1;
}

message MultiplyRequest {
    int32 n1=1;
    int32 n2=2;
}

message MeuliplyReply {
    int32 n1=1;
}

message DivideRequest {
    int32 n1=1;
    int32 n2=2;
}

message DivideReply {
    float f1=1;
}

proto 컴파일

  • 다음으로는 앞서 생성한 calc.proto 버퍼를 python 에서 직접 grpc.proto 를 이용하여 컴파일 할 수 있도록 해야 합니다.
  • 해당 작업을 통해 Python 코드로 calc.proto 버퍼 내용의 함수들이 자동으로 생성됩니다.
  • calc.proto 컴파일 하는 명령어는 아래와 같습니다.
  • 참고로 저는 파이썬\protos\calc.proto 경로에 calc.proto 파일을 생성해 놓았습니다.
  • 다음 명령어를 실행하게 되면 calc_pb.py, calc_pb2.grpc.py 2개의 파일이 자동 생성됩니다.
python -m grpc_tools.protoc -I./protos --python_out=. --grpc_python_out=. ./protos/calc.proto

calc_pb2.py

# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler.  DO NOT EDIT!
# source: calc.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()




DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\ncalc.proto\x12\x04\x63\x61lc\"$\n\nAddRequest\x12\n\n\x02n1\x18\x01 \x01(\x05\x12\n\n\x02n2\x18\x02 \x01(\x05\"\x16\n\x08\x41\x64\x64Reply\x12\n\n\x02n1\x18\x01 \x01(\x05\"*\n\x10SubstractRequest\x12\n\n\x02n1\x18\x01 \x01(\x05\x12\n\n\x02n2\x18\x02 \x01(\x05\"\x1c\n\x0eSubstractReply\x12\n\n\x02n1\x18\x01 \x01(\x05\")\n\x0fMultiplyRequest\x12\n\n\x02n1\x18\x01 \x01(\x05\x12\n\n\x02n2\x18\x02 \x01(\x05\"\x1b\n\rMeuliplyReply\x12\n\n\x02n1\x18\x01 \x01(\x05\"\'\n\rDivideRequest\x12\n\n\x02n1\x18\x01 \x01(\x05\x12\n\n\x02n2\x18\x02 \x01(\x05\"\x19\n\x0b\x44ivideReply\x12\n\n\x02n1\x18\x01 \x01(\x05\x32\xe2\x01\n\nCalculator\x12)\n\x03\x41\x64\x64\x12\x10.calc.AddRequest\x1a\x0e.calc.AddReply\"\x00\x12;\n\tSubstract\x12\x16.calc.SubstractRequest\x1a\x14.calc.SubstractReply\"\x00\x12\x38\n\x08Multiply\x12\x15.calc.MultiplyRequest\x1a\x13.calc.MeuliplyReply\"\x00\x12\x32\n\x06\x44ivide\x12\x13.calc.DivideRequest\x1a\x11.calc.DivideReply\"\x00\x62\x06proto3')



_ADDREQUEST = DESCRIPTOR.message_types_by_name['AddRequest']
_ADDREPLY = DESCRIPTOR.message_types_by_name['AddReply']
_SUBSTRACTREQUEST = DESCRIPTOR.message_types_by_name['SubstractRequest']
_SUBSTRACTREPLY = DESCRIPTOR.message_types_by_name['SubstractReply']
_MULTIPLYREQUEST = DESCRIPTOR.message_types_by_name['MultiplyRequest']
_MEULIPLYREPLY = DESCRIPTOR.message_types_by_name['MeuliplyReply']
_DIVIDEREQUEST = DESCRIPTOR.message_types_by_name['DivideRequest']
_DIVIDEREPLY = DESCRIPTOR.message_types_by_name['DivideReply']
AddRequest = _reflection.GeneratedProtocolMessageType('AddRequest', (_message.Message,), {
  'DESCRIPTOR' : _ADDREQUEST,
  '__module__' : 'calc_pb2'
  # @@protoc_insertion_point(class_scope:calc.AddRequest)
  })
_sym_db.RegisterMessage(AddRequest)

AddReply = _reflection.GeneratedProtocolMessageType('AddReply', (_message.Message,), {
  'DESCRIPTOR' : _ADDREPLY,
  '__module__' : 'calc_pb2'
  # @@protoc_insertion_point(class_scope:calc.AddReply)
  })
_sym_db.RegisterMessage(AddReply)

SubstractRequest = _reflection.GeneratedProtocolMessageType('SubstractRequest', (_message.Message,), {
  'DESCRIPTOR' : _SUBSTRACTREQUEST,
  '__module__' : 'calc_pb2'
  # @@protoc_insertion_point(class_scope:calc.SubstractRequest)
  })
_sym_db.RegisterMessage(SubstractRequest)

SubstractReply = _reflection.GeneratedProtocolMessageType('SubstractReply', (_message.Message,), {
  'DESCRIPTOR' : _SUBSTRACTREPLY,
  '__module__' : 'calc_pb2'
  # @@protoc_insertion_point(class_scope:calc.SubstractReply)
  })
_sym_db.RegisterMessage(SubstractReply)

MultiplyRequest = _reflection.GeneratedProtocolMessageType('MultiplyRequest', (_message.Message,), {
  'DESCRIPTOR' : _MULTIPLYREQUEST,
  '__module__' : 'calc_pb2'
  # @@protoc_insertion_point(class_scope:calc.MultiplyRequest)
  })
_sym_db.RegisterMessage(MultiplyRequest)

MeuliplyReply = _reflection.GeneratedProtocolMessageType('MeuliplyReply', (_message.Message,), {
  'DESCRIPTOR' : _MEULIPLYREPLY,
  '__module__' : 'calc_pb2'
  # @@protoc_insertion_point(class_scope:calc.MeuliplyReply)
  })
_sym_db.RegisterMessage(MeuliplyReply)

DivideRequest = _reflection.GeneratedProtocolMessageType('DivideRequest', (_message.Message,), {
  'DESCRIPTOR' : _DIVIDEREQUEST,
  '__module__' : 'calc_pb2'
  # @@protoc_insertion_point(class_scope:calc.DivideRequest)
  })
_sym_db.RegisterMessage(DivideRequest)

DivideReply = _reflection.GeneratedProtocolMessageType('DivideReply', (_message.Message,), {
  'DESCRIPTOR' : _DIVIDEREPLY,
  '__module__' : 'calc_pb2'
  # @@protoc_insertion_point(class_scope:calc.DivideReply)
  })
_sym_db.RegisterMessage(DivideReply)

_CALCULATOR = DESCRIPTOR.services_by_name['Calculator']
if _descriptor._USE_C_DESCRIPTORS == False:

  DESCRIPTOR._options = None
  _ADDREQUEST._serialized_start=20
  _ADDREQUEST._serialized_end=56
  _ADDREPLY._serialized_start=58
  _ADDREPLY._serialized_end=80
  _SUBSTRACTREQUEST._serialized_start=82
  _SUBSTRACTREQUEST._serialized_end=124
  _SUBSTRACTREPLY._serialized_start=126
  _SUBSTRACTREPLY._serialized_end=154
  _MULTIPLYREQUEST._serialized_start=156
  _MULTIPLYREQUEST._serialized_end=197
  _MEULIPLYREPLY._serialized_start=199
  _MEULIPLYREPLY._serialized_end=226
  _DIVIDEREQUEST._serialized_start=228
  _DIVIDEREQUEST._serialized_end=267
  _DIVIDEREPLY._serialized_start=269
  _DIVIDEREPLY._serialized_end=294
  _CALCULATOR._serialized_start=297
  _CALCULATOR._serialized_end=523
# @@protoc_insertion_point(module_scope)

calc_pb2_grpc.py

# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc

import calc_pb2 as calc__pb2


class CalculatorStub(object):
    """Missing associated documentation comment in .proto file."""

    def __init__(self, channel):
        """Constructor.

        Args:
            channel: A grpc.Channel.
        """
        self.Add = channel.unary_unary(
                '/calc.Calculator/Add',
                request_serializer=calc__pb2.AddRequest.SerializeToString,
                response_deserializer=calc__pb2.AddReply.FromString,
                )
        self.Substract = channel.unary_unary(
                '/calc.Calculator/Substract',
                request_serializer=calc__pb2.SubstractRequest.SerializeToString,
                response_deserializer=calc__pb2.SubstractReply.FromString,
                )
        self.Multiply = channel.unary_unary(
                '/calc.Calculator/Multiply',
                request_serializer=calc__pb2.MultiplyRequest.SerializeToString,
                response_deserializer=calc__pb2.MeuliplyReply.FromString,
                )
        self.Divide = channel.unary_unary(
                '/calc.Calculator/Divide',
                request_serializer=calc__pb2.DivideRequest.SerializeToString,
                response_deserializer=calc__pb2.DivideReply.FromString,
                )


class CalculatorServicer(object):
    """Missing associated documentation comment in .proto file."""

    def Add(self, request, context):
        """Missing associated documentation comment in .proto file."""
        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
        context.set_details('Method not implemented!')
        raise NotImplementedError('Method not implemented!')

    def Substract(self, request, context):
        """Missing associated documentation comment in .proto file."""
        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
        context.set_details('Method not implemented!')
        raise NotImplementedError('Method not implemented!')

    def Multiply(self, request, context):
        """Missing associated documentation comment in .proto file."""
        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
        context.set_details('Method not implemented!')
        raise NotImplementedError('Method not implemented!')

    def Divide(self, request, context):
        """Missing associated documentation comment in .proto file."""
        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
        context.set_details('Method not implemented!')
        raise NotImplementedError('Method not implemented!')


def add_CalculatorServicer_to_server(servicer, server):
    rpc_method_handlers = {
            'Add': grpc.unary_unary_rpc_method_handler(
                    servicer.Add,
                    request_deserializer=calc__pb2.AddRequest.FromString,
                    response_serializer=calc__pb2.AddReply.SerializeToString,
            ),
            'Substract': grpc.unary_unary_rpc_method_handler(
                    servicer.Substract,
                    request_deserializer=calc__pb2.SubstractRequest.FromString,
                    response_serializer=calc__pb2.SubstractReply.SerializeToString,
            ),
            'Multiply': grpc.unary_unary_rpc_method_handler(
                    servicer.Multiply,
                    request_deserializer=calc__pb2.MultiplyRequest.FromString,
                    response_serializer=calc__pb2.MeuliplyReply.SerializeToString,
            ),
            'Divide': grpc.unary_unary_rpc_method_handler(
                    servicer.Divide,
                    request_deserializer=calc__pb2.DivideRequest.FromString,
                    response_serializer=calc__pb2.DivideReply.SerializeToString,
            ),
    }
    generic_handler = grpc.method_handlers_generic_handler(
            'calc.Calculator', rpc_method_handlers)
    server.add_generic_rpc_handlers((generic_handler,))


 # This class is part of an EXPERIMENTAL API.
class Calculator(object):
    """Missing associated documentation comment in .proto file."""

    @staticmethod
    def Add(request,
            target,
            options=(),
            channel_credentials=None,
            call_credentials=None,
            insecure=False,
            compression=None,
            wait_for_ready=None,
            timeout=None,
            metadata=None):
        return grpc.experimental.unary_unary(request, target, '/calc.Calculator/Add',
            calc__pb2.AddRequest.SerializeToString,
            calc__pb2.AddReply.FromString,
            options, channel_credentials,
            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)

    @staticmethod
    def Substract(request,
            target,
            options=(),
            channel_credentials=None,
            call_credentials=None,
            insecure=False,
            compression=None,
            wait_for_ready=None,
            timeout=None,
            metadata=None):
        return grpc.experimental.unary_unary(request, target, '/calc.Calculator/Substract',
            calc__pb2.SubstractRequest.SerializeToString,
            calc__pb2.SubstractReply.FromString,
            options, channel_credentials,
            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)

    @staticmethod
    def Multiply(request,
            target,
            options=(),
            channel_credentials=None,
            call_credentials=None,
            insecure=False,
            compression=None,
            wait_for_ready=None,
            timeout=None,
            metadata=None):
        return grpc.experimental.unary_unary(request, target, '/calc.Calculator/Multiply',
            calc__pb2.MultiplyRequest.SerializeToString,
            calc__pb2.MeuliplyReply.FromString,
            options, channel_credentials,
            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)

    @staticmethod
    def Divide(request,
            target,
            options=(),
            channel_credentials=None,
            call_credentials=None,
            insecure=False,
            compression=None,
            wait_for_ready=None,
            timeout=None,
            metadata=None):
        return grpc.experimental.unary_unary(request, target, '/calc.Calculator/Divide',
            calc__pb2.DivideRequest.SerializeToString,
            calc__pb2.DivideReply.FromString,
            options, channel_credentials,
            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
  • 이제 Python 으로 gRPC Server/Client 코드를 작성해 보도록 하겠습니다.

Server

from concurrent import futures
import time

import grpc

import calc_pb2
import calc_pb2_grpc

class Calculator(calc_pb2_grpc.CalculatorServicer):

    def Add(self, request, context):
        return calc_pb2.AddReply(n1=request.n1 + request.n2)

    def Substract(self, request, context):
        return calc_pb2.SubstractReply(n1=request.n1 - request.n2)

    def Multiply(self, request, context):
        return calc_pb2.MeuliplyReply(n1=request.n1 * request.n2)

    def Divide(self, request, context):
        return calc_pb2.DivideReply(f1=request.n1 / request.n2)

def serve():
    print("Server Start..")

    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    calc_pb2_grpc.add_CalculatorServicer_to_server(Calculator(), server)

    server.add_insecure_port('[::]:50050')

    server.start()

    server.wait_for_termination()

if __name__ == '__main__':
    serve()

Client

import grpc

import calc_pb2
import calc_pb2_grpc

def run():
    channel = grpc.insecure_channel('localhost:50050')
    stub = calc_pb2_grpc.CalculatorStub(channel)

    respones = stub.Add(calc_pb2.AddRequest(n1=20, n2=10))
    print(respones.n1)

    respones = stub.Substract(calc_pb2.SubstractRequest(n1=20, n2=10))
    print(respones.n1)

    respones = stub.Multiply(calc_pb2.MultiplyRequest(n1=20, n2=10))
    print(respones.n1)

    respones = stub.Divide(calc_pb2.DivideRequest(n1=10, n2=2))
    print(respones.f1)

if __name__ == '__main__':
    run()

서버 실행

  • 서버는 cmd 로 실행하도록 하겠습니다.
  • calc_server.py 가 있는 경로로 cmd 실행하고, python calc_server.py 명령어를 입력하여 Server 를 실행합니다.
C:\Users\Desktop\파이썬>python calc_server.py
Server Start..

클라이언트 실행

  • 다음과 같이 덧셈, 뺄셈, 곱셈, 나눗셈의 값이 서버를 통해 계산되어 결과값이 반환되어 출려된 것을 확인할 수 있습니다.
30
10
200
5.0
728x90

이 글을 공유하기

댓글

Designed by JB FACTORY