기존 프로젝트 1차 작업을 끝내고, 본격적으로 메인 프로젝트에 해당 기능을 붙이려고 하는데, 내가 맡았던 포지션이 애매해져서 메인 프로젝트의 일부 기능을 맡는 식으로 업무가 배정되었다.
기존에는 해당 기능이 쓸만한지 테스트하는 용도였기에 프론트엔드 개발자가 필요하지 않아서, 백엔드 인턴 중에서 프론트를 내가 맡아서 개발했는데,
이제 그 기능이 본격화 되면서 프론트엔드 개발자가 프론트를 맡기도 했다. 그래서 나는 팀장님께 말씀드려서 메인 프로젝트의 다른 부분을 맡기로 했다.
내가 맡은 부분이 gRPC를 이용한 소켓?? DB 임베딩?? 이쪽이여서 먼저 자료조사와 공부를 할 것이다.
메인 프로젝트를 담당하시던 담당자분 입장에선 갑자기 인턴이 배정된 것이라 나에게 넘겨줄 자료나 작업을 미리 준비하지 못해서 준비되면 보내주신다고 했다. 그럼 gRPC에 대해서 알아보자.
‼️ 여기는 필요한 부분만 잡고 넘어가려고 합니다. gRPC를 공부하기 위한 배경지식에 필요한 정보를 얻기 위함입니다.
IP 프로토콜
TCP 프로토콜
, UDP 프로토콜
HTTP
, SMTP
… 등API는 별도의 Thread
에서 진행된다.참고: RPC 는 제어 흐림이 호출자와 수신자 간에 교대로 이루어진다. 즉, 클라이언트와 서버를 동시에 실행하지 않고 실행 스레드가 호출자로부터 수신자에게 점프했다가 다시 돌아온다.
- 함수: 인풋에 대비한 아웃풋의 발생을 목적으로 한다.
- 프로시저: 결과 값에 집중하기 보단 명령 단위가 수행하는 절차를 목적으로 한다.
gRPC는 모든 환경에서 실행할 수 있는 최신 오픈 소스 고성능 RPC(원격 프로시저 호출) 프레임워크이다.
Unary: 클라이언트에서 서버로 단일 요청 -> 서버에서 클라이언트로 단일 응답.
Server Streaming: 클라이언트에서 서버로 단일 요청 -> 서버에서 클라이언트로 여러 응답.
Client Streaming: 클라이언트에서 서버로 여러 요청 -> 서버에서 클라이언트로 단일 응답.
Bi-directional Streaming: 클라이언트와 서버가 동시에 여러 요청과 응답을 스트리밍.
// 터미널 열고 가상환경 새로 생성 -> grpcvenv 라는 가상환경 생성
python3 -m venv grpcenv
source grpcvenv/bin/activate
pip install grpcio
pip install grpcio-tools
// helloworld.proto
syntax = "proto3";
package helloworld;
// 1번째 서비스
service Greeter {
// Unary 패턴
rpc SayHello (HelloRequest) returns (HelloReply) {}
// Streaming 패턴
rpc StreamHello (stream HelloRequest) returns (stream HelloReply) {}
}
// 2번째 서비스
service Farewell {
// Unary 패턴
rpc SayGoodbye (GoodbyeRequest) returns (GoodbyeReply) {}
// Streaming 패턴
rpc StreamGoodbye (stream GoodbyeRequest) returns (stream GoodbyeReply) {}
}
// 3번째 서비스: 일부러 Input 과 Output 에러 발생
service RaiseError {
// Input Type 에러 발생
rpc WrongInput (WrongRequest) returns (RightReply) {}
// Output Type 에러 발생
rpc WrongOutput (RightRequest) returns (WrongReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string name = 1;
}
message GoodbyeRequest {
string name = 1;
}
message GoodbyeReply {
string name = 1;
}
message WrongRequest {
int32 name = 1;
}
message RightRequest {
string name = 1;
}
message WrongReply {
int32 name = 1;
}
message RightReply {
string name = 1;
}
// 아래 명령어를 .proto 파일이 있는 디렉토리에서 입력
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. helloworld.proto
from concurrent import futures
import grpc
import helloworld_pb2
import helloworld_pb2_grpc
# 첫 번째 서비스 구현
class Greeter(helloworld_pb2_grpc.GreeterServicer):
def SayHello(self, request, context):
return helloworld_pb2.HelloReply(name=request.name)
def StreamHello(self, request_iterator, context):
for request in request_iterator:
print(f"Greeter-StreamHello Server Received: "+ request.name)
yield helloworld_pb2.HelloReply(name=request.name)
# 두 번째 서비스 구현
class Farewell(helloworld_pb2_grpc.FarewellServicer):
def SayGoodbye(self, request, context):
print(f"Farewell-StreamGoodbye Server Received: "+ request.name)
return helloworld_pb2.GoodbyeReply(name=request.name)
def StreamGoodbye(self, request_iterator, context):
for request in request_iterator:
print(f"Farewell-StreamGoodbye Server Received: "+ request.name)
yield helloworld_pb2.GoodbyeReply(name=request.name)
# 세 번째 서비스 구현: 에러 발생 서비스
class RaiseError(helloworld_pb2_grpc.RaiseErrorServicer):
def WrongInput(self, request, context):
return helloworld_pb2.RightReply(name='RightReply & client parameter: ' + request.name)
def WrongOutput(self, request, context):
return helloworld_pb2.WrongReply(name='WrongRepluy & client parameter: ' + request.name)
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
helloworld_pb2_grpc.add_FarewellServicer_to_server(Farewell(), server)
helloworld_pb2_grpc.add_RaiseErrorServicer_to_server(RaiseError(), server)
server.add_insecure_port('[::]:50051')
server.start()
server.wait_for_termination()
if __name__ == '__main__':
serve()
import grpc
import helloworld_pb2
import helloworld_pb2_grpc
channel = grpc.insecure_channel('localhost:50051')
greeterStub = helloworld_pb2_grpc.GreeterStub(channel=channel)
farewellStub = helloworld_pb2_grpc.FarewellStub(channel=channel)
raiseerrorStub = helloworld_pb2_grpc.RaiseErrorStub(channel=channel)
# 첫 번째 서비스
def run_greeter():
choice = input("Enter '1' to SayHello or '2' to StreamHello")
if choice == '1':
response = greeterStub.SayHello(helloworld_pb2.HelloRequest(name='정수환'))
print(response)
print("Greeter-SayHello received: " + response.name)
else:
responses = greeterStub.StreamHello(generate_requests(RequestModel=helloworld_pb2.HelloRequest))
for response in responses:
print("Greeter-StreamHello received: " + response.name)
# 두 번째 서비스
def run_farewell():
choice = input("Enter '1' to SayGoodbye or '2' to StreamGoodbye")
if choice == '1':
response = farewellStub.SayGoodbye(helloworld_pb2.GoodbyeRequest(name='정수환'))
print(response)
print("Farewell-Goodbye received: " + response.name)
else:
responses = farewellStub.StreamGoodbye(generate_requests(RequestModel=helloworld_pb2.GoodbyeRequest))
for response in responses:
print("Farewell-StreamGoodbye received: " + response.name)
# 세 번째 서비스
def run_raiseerror():
choice = input("Enter '1' to WrongInput or '2' to WrongOutput")
if choice == '1':
response = raiseerrorStub.WrongInput(helloworld_pb2.WrongRequest(name="raise error"))
print(response)
print("RaiseError-WrongInput received: " + response.name)
else:
response = raiseerrorStub.WrongOutput(helloworld_pb2.RightRequest(name="raise error"))
print(response)
print("RaiseError-WrongOutput received: " + response.name)
# 스트리밍 서비스인 경우 사용
def generate_requests(RequestModel):
names = ['정수환', '종수환', '장수환', '중수환']
for name in names:
yield RequestModel(name=name)
if __name__ == '__main__':
while True:
choice = input("Enter '1' to greet or '2' to say goodbye or '3' to raise error")
if choice == '1':
run_greeter()
elif choice == '2':
run_farewell()
elif choice == '3':
run_raiseerror()
else:
print("Invalid choice")