6강 구현 중심 ⏱ 약 35분

 

0. 학습 목표

→ 이번 글에서 무엇을 이해하고, 무엇을 만들고, 무엇을 확인할지 먼저 정리합니다.

더보기

0.1 이번 글에서 다룰 내용

이번 글은 구현 중심 강의입니다.

 

5강까지는 서버와 클라이언트가 연결되는 것만 확인했습니다. 이번 강의에서는 클라이언트가 문자열 메시지를 서버로 보내고, 서버가 응답 메시지를 다시 클라이언트로 보내는 흐름을 구현합니다.

 

Python의 문자열은 그대로 소켓으로 보낼 수 없습니다. send()는 문자열이 아니라 바이트 데이터를 보내는 함수이기 때문입니다. 보낼 때는 encode(), 받을 때는 decode()가 필요합니다.

문자열 → encode() → 바이트 → send()
    → 네트워크 이동
    → recv() → 바이트 → decode() → 문자열

이번 강의에서는 메시지를 딱 한 번 주고받습니다. 반복 채팅은 다음 7강에서 다룹니다.

클라이언트 → 서버: "안녕하세요, 서버!"
서버 → 클라이언트: "메시지를 받았습니다."

이번 강의에서 배우는 encode()/decode() 패턴은 최종 채팅 프로젝트의 모든 메시지 송수신에서 그대로 사용됩니다. GUI 채팅창에서 전송 버튼을 눌렀을 때 내부에서 일어나는 일이 바로 이 흐름입니다.

구분 내용
이해할 것 소켓은 문자열이 아니라 바이트를 주고받는다는 것
만들 것 서버와 클라이언트의 1회 메시지 송수신 코드
확인할 것 클라이언트 메시지가 서버에 출력되고, 서버 응답이 클라이언트에 출력되는지

 

0.2 이번 강의에서 직접 다루는 구조

이번 강의에서는 5강에서 만든 두 파일을 모두 수정합니다.

chat_server/
└── server.py      ← 이번 강의에서 수정

chat_client/
└── client.py      ← 이번 강의에서 수정

 

1. 실습 준비하기

→ 서버와 클라이언트 역할을 확인하고, 인코딩이 필요한 이유를 이해합니다.

더보기

1.1 서버와 클라이언트 역할 확인

이번 강의에서는 서버와 클라이언트가 메시지를 한 번씩 주고받습니다. 서버를 먼저 실행해야 한다는 점은 5강과 같습니다.

프로그램 이번 강의에서 하는 일
chat_server/server.py 클라이언트 접속을 기다리고, 메시지를 받은 뒤 응답을 보낸다
chat_client/client.py 서버에 접속하고, 메시지를 보낸 뒤 서버 응답을 받는다
터미널 1: python chat_server/server.py
터미널 2: python chat_client/client.py

 

1.2 인코딩이 필요한 이유

초급자가 자주 시도하는 코드는 다음과 같습니다.

client_socket.send("안녕하세요")  # 오류 발생

이 코드는 오류가 납니다. send()는 문자열을 직접 받지 않고 바이트를 받기 때문입니다. 올바른 형태는 다음과 같습니다.

# 보낼 때
client_socket.send(message.encode("utf-8"))

# 받을 때
data = client_socket.recv(1024)
message = data.decode("utf-8")
작업 코드 의미
문자열 → 바이트 encode("utf-8") 보내기 전 변환
바이트 → 문자열 decode("utf-8") 받은 뒤 변환
데이터 보내기 send() 바이트 전송
데이터 받기 recv(1024) 최대 1024바이트 수신

recv(1024)에서 1024는 한 번에 받을 수 있는 최대 바이트 수입니다.

짧은 메시지는 보통 이 안에 들어옵니다. 더 긴 데이터를 다루는 방법은 이후 강의에서 다룹니다.

 

utf-8은 한글을 포함한 다양한 문자를 표현할 수 있는 인코딩 방식입니다.

보내는 쪽과 받는 쪽 모두 utf-8로 통일합니다.

 

이번 강의에서 새 파일을 만들지 않습니다.
5강에서 만든 두 파일에 메시지 송수신 코드를 추가합니다.

두 파일을 에디터에서 열어 5강 코드가 들어 있는지 확인하세요.

✔ 확인 기준: send() 전에는 encode(), recv() 후에는 decode()가 필요하다고 말할 수 있으면 이 단계는 완료입니다.

 

2. 서버 - 송수신 만들기

→ 서버와 클라이언트 코드에 send(), recv(), encode(), decode()를 추가합니다.

더보기

chat_server/server.py를 다음과 같이 수정합니다. accept() 이후에 수신과 응답 코드를 추가합니다.

import socket

HOST = "127.0.0.1"
PORT = 5000

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:
    server_socket.bind((HOST, PORT))
    server_socket.listen()

    print("서버를 시작합니다.")
    print("클라이언트 접속 대기 중...")

    client_socket, client_address = server_socket.accept()

    with client_socket:
        print(f"클라이언트가 접속했습니다: {client_address}")

        # 클라이언트가 보낸 데이터를 최대 1024바이트까지 받는다
        data = client_socket.recv(1024)
        # 받은 바이트를 문자열로 변환한다
        message = data.decode("utf-8")

        print(f"클라이언트 메시지: {message}")

        response = "메시지를 받았습니다."
        # 응답 문자열을 바이트로 변환해서 보낸다
        client_socket.send(response.encode("utf-8"))

        print("응답 메시지를 보냈습니다.")

print("서버를 종료합니다.")

5강 코드에서 추가된 핵심 줄의 의미는 다음과 같습니다.

코드 의미
client_socket.recv(1024) 클라이언트가 보낸 데이터를 최대 1024바이트까지 받는다
data.decode("utf-8") 받은 바이트를 문자열로 변환한다
response.encode("utf-8") 응답 문자열을 바이트로 변환한다
client_socket.send(...) 변환된 바이트 데이터를 클라이언트에게 보낸다

 

3. 클라이언트 - 송수신 만들기

→ 서버와 클라이언트 코드에 send(), recv(), encode(), decode()를 추가합니다.

더보기

chat_client/client.py를 다음과 같이 수정합니다. connect() 이후에 송신과 응답 수신 코드를 추가합니다.

import socket

HOST = "127.0.0.1"
PORT = 5000

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:
    client_socket.connect((HOST, PORT))
    print("서버에 연결되었습니다.")

    message = "안녕하세요, 서버!"
    # 문자열을 바이트로 변환해서 보낸다
    client_socket.send(message.encode("utf-8"))
    print(f"서버로 보낸 메시지: {message}")

    # 서버 응답을 받는다
    data = client_socket.recv(1024)
    # 받은 바이트를 문자열로 변환한다
    response = data.decode("utf-8")

    print(f"서버 응답: {response}")

print("클라이언트 소켓을 닫았습니다.")

클라이언트의 핵심 흐름은 다음과 같습니다.

서버 접속
↓
encode()로 문자열을 바이트로 변환
↓
send()로 서버에 전송
↓
recv()로 서버 응답 수신
↓
decode()로 바이트를 문자열로 변환
↓
화면에 출력

✔ 확인 기준: send(), recv(), encode("utf-8"), decode("utf-8")가 각 파일에 들어 있으면 완료입니다. send("안녕하세요")처럼 문자열을 바로 보내고 있지 않은지 확인하세요.

 

4. 실행 결과 확인하기

→ 서버를 먼저 실행하고 클라이언트를 실행해 두 터미널의 출력을 확인합니다.

더보기

4.1 송수신 순서 이해

이번 강의의 송수신 순서는 반드시 다음 흐름이어야 합니다. recv()는 상대방이 데이터를 보내지 않으면 계속 기다립니다. 양쪽이 동시에 recv()만 하고 있으면 서로 기다리기만 해서 프로그램이 멈춘 것처럼 보일 수 있습니다.

클라이언트 send()
↓
서버 recv()
↓
서버 send()
↓
클라이언트 recv()

 

4.2 실행 순서와 기대 출력

터미널 1에서 서버를 먼저 실행합니다.

python chat_server/server.py
서버를 시작합니다.
클라이언트 접속 대기 중...

 

서버가 클라이언트 접속을 기다리는 상태입니다. 이어서 터미널 2에서 클라이언트를 실행합니다.

python chat_client/client.py
서버에 연결되었습니다.
서버로 보낸 메시지: 안녕하세요, 서버!
서버 응답: 메시지를 받았습니다.
클라이언트 소켓을 닫았습니다.

 

클라이언트 실행 후 서버 터미널에는 다음이 추가됩니다.

클라이언트가 접속했습니다: ('127.0.0.1', 52344)
클라이언트 메시지: 안녕하세요, 서버!
응답 메시지를 보냈습니다.
서버를 종료합니다.

뒤쪽 포트 번호 52344는 클라이언트가 임시로 사용하는 포트입니다. 실행할 때마다 달라질 수 있습니다.

✔ 확인 기준: 서버 터미널에 "안녕하세요, 서버!"가 출력되고, 클라이언트 터미널에 "메시지를 받았습니다."가 출력되면 이번 강의는 성공입니다.

 

5. 최종 코드 정리하기

→ 이번 강의에서 완성한 두 파일의 전체 코드를 한곳에 정리합니다.

더보기

5.1 chat_server/server.py

import socket

HOST = "127.0.0.1"
PORT = 5000

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:
    server_socket.bind((HOST, PORT))
    server_socket.listen()

    print("서버를 시작합니다.")
    print("클라이언트 접속 대기 중...")

    client_socket, client_address = server_socket.accept()

    with client_socket:
        print(f"클라이언트가 접속했습니다: {client_address}")

        # 클라이언트가 보낸 데이터를 최대 1024바이트까지 받는다
        data = client_socket.recv(1024)
        # 받은 바이트를 문자열로 변환한다
        message = data.decode("utf-8")

        print(f"클라이언트 메시지: {message}")

        response = "메시지를 받았습니다."
        # 응답 문자열을 바이트로 변환해서 보낸다
        client_socket.send(response.encode("utf-8"))

        print("응답 메시지를 보냈습니다.")

print("서버를 종료합니다.")

 

5.2 chat_client/client.py

import socket

HOST = "127.0.0.1"
PORT = 5000

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:
    client_socket.connect((HOST, PORT))
    print("서버에 연결되었습니다.")

    message = "안녕하세요, 서버!"
    # 문자열을 바이트로 변환해서 보낸다
    client_socket.send(message.encode("utf-8"))
    print(f"서버로 보낸 메시지: {message}")

    # 서버 응답을 받는다
    data = client_socket.recv(1024)
    # 받은 바이트를 문자열로 변환한다
    response = data.decode("utf-8")

    print(f"서버 응답: {response}")

print("클라이언트 소켓을 닫았습니다.")

 

4.3 최종 확인 표

확인할 코드 위치 의미
message.encode("utf-8") 클라이언트 보낼 문자열을 바이트로 변환
client_socket.send(...) 클라이언트 서버로 바이트 데이터 전송
client_socket.recv(1024) 서버 클라이언트가 보낸 데이터 수신
data.decode("utf-8") 서버 받은 바이트를 문자열로 변환
response.encode("utf-8") 서버 응답 문자열을 바이트로 변환
client_socket.recv(1024) 클라이언트 서버 응답 수신
response = data.decode("utf-8") 클라이언트 서버 응답 바이트를 문자열로 변환

→ 다음 강의 (7강): while문으로 반복 송수신 구조로 확장합니다. 사용자가 직접 입력한 메시지를 계속 주고받고, exit을 입력하면 연결을 종료하는 채팅 흐름으로 발전시킵니다.