주제

  • UDP Packet
  • 로또 번호 추천 서비스

요구사항

  • 강의 자료와 같이 로또 번호를 추천하는 Server, Client 구현

asgn_server.py

코드 설명

라이브러리

  • socket은 UDP소켓을 만들어 통신을 하기 위해, random은 무작위 번호 추출을 위해 import했다.

main

  • 실습 코드를 참고하여 작성했다. 넣은 주소가 IPv4 주소 체계이니 이를 사용하도록 socket.AF_INET을, UDP 소켓을 만들어야 하니 socket.SOCK_DGRAM을 같이 인자로 사용한다.
  • bind()로 서버가 수신 대기할 주소와 포트를 지정해주고 서버가 잘 실행됐는지 로그를 출력한다.

  • While True로 계속 클라이언트의 요청을 처리하도록 했다. UDP서버에서 클라이언트의 데이터를 수신하기 위해 recvfrom()을 사용한다.
  • 클라이언트로부터 받은 바이트 데이터를 UTF-8로 변환해야 하니 decode하고, 잘 받았는지 확인하기 위해 한 번 출력해준다.
  • 혹시 모를 에러를 살펴보기 위해 try-except로 처리했다.
  • 로또 번호는 1~45까지이고, 최대 6개의 입력을 받을 수 있으며 중복을 허용하지 않는다. 따라서 사용자의 입력값을 if문으로 전부 검증하고 raise문으로 exception을 발생시켰다. 여기서 문장을 한글로 작성하고 싶었는데 Ubuntu VM으로 서버를 돌릴 생각이었고, 거기서 환경변수를 바꾸고 뭘 깔고 해도 한글 부분이 깨지길래 영어로 작성했다.
  • bag은 아직 선택되지 않은 번호 목록이다. 로또 추천 시스템이니 랜덤으로 나머지 번호를 선택해서 6개를 채워 사용자에게 보내야 한다. 아까 import한 random 라이브러리로 중복 없이 번호를 선택하고, result에 사용자 입력값과 추천값 6개가 정렬되어 저장되게 했다.
  • 이것을 문자열로 변환하여 UTF-8 인코딩 후 클라이언트에게 전송한다.

entry point block

  • 인자로 address와 port를 입력하니 argparse를 import에서 커맨드라인 인자를 처리하도록 한다.
  • ArgumentParser()는 인자를 등록 및 파싱 가능하게 한다. UDP서버를 띄우기 위해 ip주소와 포트 번호가 필요하니 이를 사용해야한다.
  • 주소를 –address로 입력받고 문자열로 처리, 반드시 입력하게 한다.
  • 포트 번호를 –port로 입력받고 int로 처리, 값 입력 없을 시 실습 코드처럼 기본값으로 3034를 사용하도록 한다.
  • 커맨드라인에서 받은 인자를 분석해서 address와 port를 저장하고 이를 main()에 넘겨 서버를 실행한다.

코드 전문

# lotto server

import socket
import random

def main(server_address, server_port):
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind((server_address, server_port))
    print(f'Listening on {server_address}:{server_port}')

    while True:
        data, client = sock.recvfrom(20)
        user_input = data.decode('utf-8')
        print(f"Received {user_input} from {client}")

        try:
            numbers = list(map(int, user_input.strip().split()))
            if not all(1 <= n <= 45 for n in numbers):
                raise ValueError("Each number must be between 1 and 45.")
            if len(set(numbers)) != len(numbers):
                raise ValueError("Duplicate numbers are not allowed.")
            if len(numbers) > 6:
                raise ValueError("You can enter up to 6 numbers only.")

            bag = sorted(list(set(range(1, 46)) - set(numbers)))
            print(f"Remaining candidates: {set(bag)}")

            needed = 6 - len(numbers)
            additional = random.sample(bag, needed)
            result = sorted(numbers + additional)

            for num in additional:
                print(f"Randomly selected: {num}")

            response = ' '.join(map(str, result))
        except Exception as e:
            response = f"Error: {e}"

        sock.sendto(response.encode('utf-8'), client)
        print(f"Send {response} to {client}")

if __name__ == '__main__':
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument('--address', type=str, required=True, help='Server IP address to bind')
    parser.add_argument('--port', type=int, default=3034, help='Port to listen on')
    args = parser.parse_args()

    main(args.address, args.port)

asgn_client.py

코드 설명

라이브러리

  • 서버에 UDP로 전송해야 하므로 socket이 필요하다.

main

  • 실습 코드를 참고하여 작성했다. 넣은 주소가 IPv4 주소 체계이니 이를 사용하도록 socket.AF_INET을, UDP 소켓을 만들어야 하니 socket.SOCK_DGRAM을 같이 인자로 사용한다. 로그로 전송 준비 상황을 확인할 수 있게 했다.
  • 사용자는 공백으로 구분된 숫자를 0에서 6개까지 입력한다. (e.g., 1 2 3 4 5 6)
  • 문자열을 UTF-8로 인코딩해 바이트로 바꾸고 인자로 입력한 서버 ip와 포트번호를 쌍으로 전송한다.
  • 서버로부터 최대 1024바이트 데이터를 수신할 수 있고, 이 바이트 데이터를 UTF-8로 문자열 디코딩후 출력하여 사용자는 최종 로또 번호를 확인할 수 있다.

entry point block

  • 커맨드라인에서 –address와 –port로 서버 ip와 포트번호를 마음대로 지정할 수 있다. 이를 받아 main함수에 전달한다.

코드 전문

# lotto client

import socket

def main(server_address, server_port):
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    print(f'Ready to send using {sock}')

    user_input = input('1~45 사이의 숫자 0에서 6개를 입력해주세요.: ').strip()

    sock.sendto(user_input.encode('utf-8'), (server_address, server_port))
    print(f'{user_input}를 ({server_address}, {server_port})로 보냈습니다.')
    data, server = sock.recvfrom(1024)
    response = data.decode('utf-8')
    print(f'{server}에서 생성된 번호는 \'{response}\'입니다.')

if __name__ == '__main__':
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument('--address', type=str, required=True, help='서버의 IP 주소')
    parser.add_argument('--port', type=int, default=3034, help='서버의 포트 번호')
    args = parser.parse_args()

    main(args.address, args.port)

실행 결과

서버를 VM으로 돌리고 로컬에서 client를 돌린 결과와 로컬에서 둘 다 돌린 결과를 각각 확인했다.

Server

Client

⇒ 통신이 잘 됐다.

⇒ 로컬끼리도 잘 실행됨을 확인했다.

Leave a comment