11.2 네트워크

11.2.1 패턴

몇가지 기본 패턴으로 네트워킹 애플리케이션을 만들 수 있다.

  • 가장 일반적인 패턴은 요청-응답 패턴으로 클라이언트- 서버 패턴으로 알려짐. 이패턴은 동기적이며 클라이언트는 서버의 응답이 올 떄까지 기다린다.
  • 또다른 일반적인 패턴은 푸시 또는 팬아웃 패턴이다. 데이터를 프로세스 풀에 있는 사용가능한 워커로 전송한다. 예를 들어 로드 밸런서 뒤에는 웹 서버가 있다.
  • 푸시의 반대는 풀 또는 팬인 이다. 하나 이상의 소스로 부터 데이터를 받는다. 예를 들어 멀티 프로세스에서 텍스트 메시지를 받아서 하나의 로그파일에 작성하는 로거가 있다.
  • 패턴은 발행-구독 하는 라디오나 텔레비전 방송과 유사. 발행자가 데이터를 전송한다. 모든 구독자는 데이터의 복사본을 받는다. 구독자는 특정타입에 대한 데이터에 관삼을 표할수 있다. 발행자는 단지 데이터를 전송한다. 푸시 패턴과는 달리 여러 구독자는 주어진 데이터 조각을 받는다. 토픽에 대한 구독자가 없는 경우 데이터는 무시 된다.

11.2.2 발행-구독 모델

  • 발행-구독은 큐가 아닌 브로드캐스트다. 하나이상의 프로세스가 메시지를 발행한다.
  • 각 구독자 프로세스는 수신하고자 하는 메시지의 타입을 표시한다.
  • 각 메시지의 복사본은 타입과 일치하는 구독자에 전송된다.

Redis

  • Redis를 사용하여 발행-구독 시스템을 빠르게 구현할 수 있다.
  • 구독자는 토픽, 값과 함께 메시지를 전달하고, 구독자는 수신받고자 하는 토픽을 말한다.

  • 구독자의 redis.pub.py
import redis
import random

conn = redis.Redis()
cats = ['siamese', 'persian','maine coon', 'norwegian forest']
hats = ['stovepipe', 'bowler', 'tam-o-shanter', 'fedora']
for msg in range(10):
    cat = random.choice(cats)
    hat = random.choice(hats)
    print('Publish: {cat} wears a {hat}'.format(cat=cat, hat=hat))
    conn.publish(cat, hat)
  • 다음은 한 구독자의 redis_sub.py
import redis
conn = redis.Redis()

topics = ['maine conn', 'persian']
sub = conn.pubsub()
sub.subscribe(topics)
for msg in sub.listen():
    if msg['type'] == 'message':
        cat = msg['channel']
        hat = msg['data']
        print('Subscribe: {cat} wears a {hat}'.format(cat=cat, hat=hat))

ZeroMQ

  • ZeroMQ는 중앙 서버가 없으므로, 각 발행자는 모든 구독자에 메시지를 전달한다.
import zmq
import random
import time
host = '*'
port = 8080
ctx = zmq.Context()
pub = ctx.socket(zmq.PUB)
pub.bind('tcp://{host}:{port}'.format(host=host, port=port))
cats = ['siamese', 'persian','maine coon', 'norwegian forest']
hats = ['stovepipe', 'bowler', 'tam-o-shanter', 'fedora']
time.sleep(1)
for msg in range(10):
    cat = random.choice(cats)
    cat_bytes = cat.encode('utf-8')
    hat = random.choice(hats)
    hat_bytes = hat.encode('utf-8')
    print('Publish:{cat} wears a {hat}'.format(cat=cat, hat=hat))
    pub.send_multipart([cat_bytes, hat_bytes])
import zmq
host = '127.0.0.1'
port = 8080
ctx = zmq.Context()
sub = ctx.socket(zmq.SUB)
sub.connect('tcp://{host}:{port}'.format(host=host, port=port))
topics = ['maine coon', 'persian']
for topic in topics:
    sub.setsockopt(zmq.SUBSCRIBE, topic.encode('utf-8'))
while True:
    cat_bytes, hat_bytes = sub.recv_multipart()
    cat = cat_bytes.decode('utf-8')
    hat = hat_bytes.decode('utf-8')
    print('Subscribe: {cat} wears a {hat}'.format(cat=cat, hat=hat))

11.2.3 TCP/IP

  • UDP(사용자 데이터그램 프로토콜) - 이 프로토콜은 짧은 데이터 교환에 사용된다. 데이터그램은 엽서의 짧은 글처럼, 한 단위로 전송되는 작은 메시지다.
  • TCP(전송 제어 프로토콜) - 이 프로토콜은 수명이 긴 커넥션에 사용된다. TCP는 바이트 스트림이 중복없이 순서대로 도착하는 것을 보장한다.

  • UDP - 일단 송신하고 송신 처리에 대한것을 받지 않음
  • TCP - 송신후 잘 송신 되었는지 다시 수신 받음.

11.2.4 소켓

udp server.py

from datetime import datetime
import socket

server_address = ('localhost', 6789)
max_size = 4096

print('Starting the server at', datetime.now())
print('Waiting for a client to call.')
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server.bind(server_address)

data, client = server.recvfrom(max_size)

print('At', datetime.now(), client, 'said', data)
server.sendto(b'Are tou taling to me?', client)
server.close()

udp client.py

import socket
from datetime import datetime

server_address = ('localhost', 6789)
max_size = 4096

print('Starting the client at', datetime.now())
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
client.sendto(b'Hey!', server_address)

data, server = client.recvfrom(max_size)
print('At', datetime.now(), server, 'said', data)
client.close()

tcp clinet.py

import socket
from datetime import datetime

address = ('localhost', 6789)
max_size = 1000

print('Starting the client at', datetime.now())
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(address)
client.sendall(b'Hey!')
data = client.recv(max_size)
print('At', datetime.now(), 'someone replied', data)
client.close()

tcp server.py

from datetime import datetime
import socket

address = ('localhost', 6789)
max_size = 1000

print('Starting the server at', datetime.now())
print('Wating for a client to call.')
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(address)
server.listen(5)

client, addr = server.accept()
data = client.recv(max_size)

print('At', datetime.now(), client, 'said', data)
client.sendall(b'Are tou talking to me?')
client.close()
server.close()
Starting the server at 2018-03-22 10:19:56.682701
Wating for a client to call.

11.2.5 ZeroMQ

  • 가장 간단한 패턴은 단일 요청 - 응답 패턴. 한 소켓이 요청하면 다른소켓에서 응답하는 동기적인 방식

zmq server.py

import zmq

host = '127.0.0.1'
port = 6789
context = zmq.Context()
server = context.socket(zmq.REP)
server.bind("tcp://{host}:{port}".format(host=host, port=port))
while True:
    # 클라이언트에서 다음 요청을 기다린다.
    request_bytes = server.recv()
    request_str = request_bytes.decode('utf-8')
    print('Hat voice in my head says: {message}'.format(message=request_str))
    reply_str  = "stop saying: {message}".format(message=request_str)
    reply_bytes = bytes(reply_str, 'utf-8')
    server.sendall(reply_bytes)

zmq client.py

import zmq

host = '127.0.0.1'
port = 6789
context = zmq.Context()
client = context.socket(zmq.REQ)
client.connect("tcp://{host}:{port}".format(host=host, port=port))

for num in range(1, 6):
    request_str = "message #{num}".format(num = num)
    request_bytes = request_str.encode('utf-8')
    client.send(request_bytes)
    reply_bytes = client.recv()
    reply_str = reply_bytes.decode('utf-8')
    print('Sent {message}, received {reply}'.format(message=request_str, reply=reply_str))

메시지는 바이트로 변환해야 한다. 메시지가 다른 타입이라면 MessagePack과 같은 라이브러리를 사용할 수 있다.

Comments