콘텐츠로 건너뛰기
Home » ZMQ 멀티파트메시지

ZMQ 멀티파트메시지

멀티파트 메시지는 하나의 메시지 프레임 내부에 여러 개의 독립적인 메시지 프레임이 들어 있는 것을 말한다. 이는 하나의 프레임에서 처리하기 힘든 데이터 조각들을 모아서 처리할 때 유용하다. 예를 들어 바이너리 파일 데이터를 전송하려는 경우에는 보내는 쪽이나 받는 쪽이나 전송하는 데이터가 이진데이터라는 것을 알고 있다 가정하여 바이트 스트림을 전송할 수 있다. 하지만 이렇게 하면 실제 데이터 외부에 있었던 정보, 이를 테면 파일 이름이나 생성한 날짜 같은 메타 정보를 전달하기가 어려워진다. 이런 경우 여러 정보들을 멀티 파트 메시지로 묶어서 하나의 프레임으로 전송하면 필요한 모든 정보를 같이 전달해 줄 수 있다.

이러한 멀티파트 메시지 전송 개념은 ZMQ만의 것은 아니다. 우리가 웹에서 폼을 채워서 submit 할 때에도 이 데이터들은 하나의 HTTP 요청을 이루게 되고 이 때 이 요청내에 폼 데이터는 multipart-formdata 형식으로 전달되는데, 각각의 필드가 구분자를 사이에두고 합쳐져서 전달된다는 점에서 비슷하다 하겠다.

ZMQ 에서도 여러 개의 메시지 혹은 프레임을 하나의 메시지로 묶어서 멀티파트로 전송할 수 있다. pyzmq를 사용하는 경우 이는 소켓 객체의 send_multipart() / recv_multipart()  두 개의 메소드로 간단히 구현된다.이 때 전달되는 내용은 항상 바이트스트림의 배열이라는 측면에서 차이가 있다뿐, 주고 받는 방법 자체가 크게 달라지지는 않는다.

참고로, send_multipart(), recv_multipart()는 각 회차의 통신에서 주고 받는 방법만 맞추면 된다. 아래 예에서도 하나의 소켓에서 클라이언트 -> 서버 방향의 통신에서는 멀티파트로 데이터를 전송하지만, 서버 –> 클라이언트 방향에서는 멀티파트 전송을 사용하지 않는다.

## multimessage-server.py
import zmq
ctx = zmq.Context()
def run_server(port=5555):
  sock = ctx.socket(zmq.REP)
  sock.bind(f'tcp://*:{port}')
  keys = 'abc'
  while True:
    msg = dict(zip(keys, (x.decode() for x in sock.recv_multipart())))
    for k, v in msg.items():
      print(f'{k}: {v}')
    ## 대문자화하고 하나의 문자열로 만들어서 되돌려준다.
    sock.send_string(' '.join(msg[k].upper() for k in keys))
if __name__ == '__main__':
  run_server()

recv_multipart() 에 의해 리턴되는 값이 바이트배열의 시퀀스임을 알고 있다면 위 코드는 충분히 이해되리라 본다. REQ-REP 패턴에서 클라이언트에서 서버로 멀티 파트 메시지를 전송했다 하더라도, 반드시 응답이 멀티파트여야 할 필요도 없다. 다음은 위 서버 코드와 짝을 맞출 클라이언트 코드이다. 세 번의 키보드 입력 후 해당 문자열들을 한 번에 전송한다. 그리고 서버가 보내준 단일 메시지를 출력하는 것을 반복하는 간단한 코드이다.

import zmq
ctx = zmq.Context()
def run_client(port=5555):
  sock = ctx.socket(zmq.REQ)
  sock.connect(f'tcp://localhost:{port}')
  while True:
    data = [input().encode() for _ in range(3)]
    sock.send_multipart(data)
    res = sock.recv_string()
    print(res)
if __name__ == '__main__':
  run_client()