5강. 첫 번째 클라이언트 만들기

0. 학습 목표
→ 이번 글에서 무엇을 이해하고, 무엇을 만들고, 무엇을 확인할지 먼저 정리합니다.
0.1 이번 글에서 다룰 내용
이번 글은 구현 중심 강의입니다.
4강에서 만든 서버는 아직 혼자 기다리고만 있었습니다. 이번 강의에서는 서버에 접속하는 첫 번째 클라이언트를 만듭니다. 이번 강의의 목표는 메시지를 보내는 것이 아닙니다. 먼저 다음 흐름을 확실히 완성합니다.

① 서버 실행
↓
② 서버가 127.0.0.1:5000에서 대기
↓
③ 클라이언트 실행
↓
④ 클라이언트가 127.0.0.1:5000으로 접속
↓
⑤ 서버가 클라이언트 접속을 감지
↓
⑥ 연결 확인 후 종료
이번 강의에서 가장 중요한 문장은 다음입니다. 서버는 기다리고, 클라이언트는 서버 주소로 찾아간다. 이번 강의에서 만드는 chat_client/client.py는 최종 채팅 프로젝트 클라이언트의 출발점입니다. 이후 강의에서 메시지 송수신, GUI 화면, 귓속말, 파일 전송 기능을 하나씩 추가해 완성합니다.
| 구분 | 내용 |
| 이해할 것 | 클라이언트가 서버에 접속하려면 서버의 IP와 포트가 필요하다는 것 |
| 만들 것 | chat_client/client.py |
| 확인할 것 | 클라이언트 터미널의 연결 성공 메시지와 서버 터미널의 클라이언트 접속 메시지 |
0.2 이번 강의에서 직접 다루는 구조
이번 강의에서는 4강에서 만든 chat_server/server.py는 그대로 두고, 새로 chat_client/client.py를 만듭니다.
chat_server/
└── server.py ← 4강에서 생성, 이번 강의에서 실행만
chat_client/
└── client.py ← 이번 강의에서 생성
client.py 안에서 이번 강의에서 다루는 구조입니다.
chat_client/client.py
├── socket 모듈 가져오기
├── 서버 주소와 포트 정하기 → HOST, PORT
├── 클라이언트 소켓 만들기 → socket()
├── connect()로 서버에 접속
├── 연결 성공 메시지 출력
└── 소켓 닫기 → close()
아직 메시지를 보내거나 받지는 않습니다. 이번 강의의 결과물은 서버와 클라이언트 사이의 첫 연결 확인입니다.
1. 실습 준비하기
→ chat_client/ 폴더와 client.py 파일을 만들고 구조를 확인합니다.
프로젝트 루트 폴더에서 클라이언트 코드를 담을 폴더와 파일을 만듭니다.
mkdir chat_client
touch chat_client/client.py
파일 이름은 반드시 client.py로 맞춥니다. 이번 강의의 실행 명령어가 다음과 같기 때문입니다.
python chat_client/client.py
생성 후 프로젝트 구조를 확인합니다.
chat_server/
└── server.py
chat_client/
└── client.py ← 생성 완료
✔ 확인 기준: 에디터에서 chat_client/client.py 파일을 열 수 있으면 완료입니다. 현재 위치가 프로젝트 루트 폴더인지, 폴더 이름을 다르게 만들지 않았는지 확인하세요.
2. 클라이언트 - 소켓 만들기
→ 클라이언트 소켓을 만들고 connect()로 서버에 접속하는 코드를 작성합니다.
chat_client/client.py 파일을 열고 다음 코드를 작성합니다.
import socket
HOST = "127.0.0.1"
PORT = 5000
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
| 코드 | 의미 |
| HOST = "127.0.0.1" | 접속할 서버의 IP 주소 |
| PORT = 5000 | 접속할 서버의 포트 번호 |
| socket.socket(...) | 클라이언트 소켓 생성 |
4강의 서버 코드와 비교하면 HOST와 PORT의 관점이 다릅니다. 같은 값처럼 보이지만 서버는 그 주소에서 기다리고, 클라이언트는 그 주소로 찾아갑니다.
| 구분 | 서버 | 클라이언트 |
| HOST | 서버가 기다릴 주소 | 클라이언트가 찾아갈 서버 주소 |
| PORT | 서버가 기다릴 포트 | 클라이언트가 접속할 서버 포트 |
3. 클라이언트 - 서버 접속 요청 만들기
→ 클라이언트 소켓을 만들고 connect()로 서버에 접속하는 코드를 작성합니다.
import socket
HOST = "127.0.0.1"
PORT = 5000
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect((HOST, PORT))
print("서버에 연결되었습니다.")
client_socket.close()
print("클라이언트 소켓을 닫았습니다.")
connect()는 클라이언트가 서버 주소로 접속을 시도하는 함수입니다. 괄호 구조에 주의해야 합니다. (HOST, PORT)를 하나의 튜플로 묶어 전달해야 합니다.
# 올바른 형태
client_socket.connect((HOST, PORT))
# 잘못된 형태 — TypeError 발생
client_socket.connect(HOST, PORT)
연결 확인이 끝나면 클라이언트 소켓을 닫습니다. 이번 강의에서는 메시지를 보내지 않기 때문에 연결 확인 후 바로 닫는 것이 정상입니다.
✔ 확인 기준: client.py에 socket.socket(), connect((HOST, PORT)), close() 코드가 들어 있으면 완료입니다. connect(HOST, PORT)처럼 괄호를 잘못 쓰지 않았는지 확인하세요.
3. 실행 결과 확인하기
→ 두 터미널의 출력을 함께 확인하고 127.0.0.1 주소의 의미를 정리합니다.
3.1 정상 실행 확인
실행 순서는 반드시 다음과 같아야 합니다.
1. 서버 실행 (터미널 1)
2. 서버가 접속 대기 상태인지 확인
3. 클라이언트 실행 (터미널 2)
4. 서버와 클라이언트 터미널 출력 확인
터미널을 두 개 열어 둡니다.
터미널 1: 서버 실행용
터미널 2: 클라이언트 실행용
터미널 1에서 서버를 먼저 실행합니다.
python chat_server/server.py
서버가 시작되었습니다. 127.0.0.1:5000
클라이언트 접속을 기다리는 중입니다...
이 상태에서 서버가 멈춘 것처럼 보이는 것은 정상입니다. 클라이언트가 접속하기를 기다리는 중입니다.
터미널 1에서 서버가 실행 중인 상태를 유지한 채로, 터미널 2에서 클라이언트를 실행합니다.
python chat_client/client.py
서버에 연결되었습니다.
클라이언트 소켓을 닫았습니다.
클라이언트를 실행하는 순간 서버 터미널에도 다음 출력이 나타납니다.
클라이언트가 접속했습니다: ('127.0.0.1', 52344)
클라이언트 연결을 닫았습니다.
서버 소켓을 닫았습니다.
뒤쪽 숫자 52344는 예시입니다. 클라이언트가 임시로 사용하는 포트 번호라서 실행할 때마다 달라질 수 있습니다. 4강에서 만든 서버는 클라이언트 하나를 받은 뒤 종료되는 구조이므로, 클라이언트를 다시 실행하려면 서버도 다시 실행해야 합니다.
✔ 확인 기준: 서버와 클라이언트 양쪽 출력에서 연결 성공을 확인할 수 있으면 완료입니다. 서버가 먼저 실행되었는지, HOST와 PORT 값이 서버 코드와 같은지 확인하세요.
3.2 정상 실행 결과 해석하기
정상적으로 실행되면 두 터미널은 다음처럼 보입니다.
서버 터미널
서버가 시작되었습니다. 127.0.0.1:5000
클라이언트 접속을 기다리는 중입니다...
클라이언트가 접속했습니다: ('127.0.0.1', 52344)
클라이언트 연결을 닫았습니다.
서버 소켓을 닫았습니다.
클라이언트 터미널
서버에 연결되었습니다.
클라이언트 소켓을 닫았습니다.
이 결과가 의미하는 것은 다음과 같습니다. 클라이언트가 서버 주소를 찾아갔고, 서버가 클라이언트 접속을 받아들였습니다. 서버와 클라이언트 사이에 TCP 연결이 만들어졌고, 클라이언트가 연결 확인 후 소켓을 닫았습니다. 아직 채팅 메시지는 오가지 않았지만 채팅 프로그램의 가장 기본 조건인 서버-클라이언트 연결은 성공한 것입니다.
3.3 IP 주소 127.0.0.1 다시 정리하기
이번 실습에서는 서버와 클라이언트를 같은 컴퓨터에서 실행하므로 클라이언트의 HOST가 127.0.0.1입니다. 나중에 다른 컴퓨터에서 서버에 접속하려면 이 값을 서버 컴퓨터의 내부 IP로 바꿔야 합니다.
# 다른 컴퓨터의 서버에 접속할 때
HOST = "192.168.0.10" # 서버 컴퓨터의 실제 내부 IP
PORT = 5000
127.0.0.1은 자기 자신을 가리키는 주소입니다. B 컴퓨터에서 A 컴퓨터의 서버에 접속할 때 B 컴퓨터의 클라이언트 코드에 127.0.0.1을 쓰면, A가 아니라 B 자신에게 접속을 시도하게 됩니다.
✔ 확인 기준: 127.0.0.1이 같은 컴퓨터 테스트용 주소라는 것을 설명할 수 있으면 완료입니다. 다른 컴퓨터에서 접속하면서 127.0.0.1을 그대로 사용하고 있지 않은지 확인하세요.
3.4 흔한 오류 확인
상황 1 — 같은 내부망, 포트 일치
서버 PC 내부 IP: 192.168.0.10
서버 포트: 5000
클라이언트 접속 대상: 192.168.0.10:5000
클라이언트 위치: 같은 와이파이
→ 접속 가능
상황 2 — 포트 번호 불일치
서버 포트: 5000
클라이언트 접속 대상: 192.168.0.10:6000
→ IP는 맞지만 포트가 다름 → 연결 실패
상황 3 — 다른 외부망에서 내부 IP로 접속
서버 PC: 집 와이파이
클라이언트 PC: 다른 장소의 와이파이
접속 대상: 서버 PC의 내부 IP
→ 내부 IP는 같은 내부망 안에서만 동작 → 접속 안 됨
외부 접속은 이후 Docker와 AWS EC2 배포 파트에서 다룹니다.
1. 서버를 먼저 실행했는가?
2. 서버 터미널에 "클라이언트 접속을 기다리는 중입니다..."가 보이는가?
3. 클라이언트의 HOST와 PORT가 서버와 같은가?
4. 서버가 이전 실행에서 이미 종료된 것은 아닌가?
✔ 확인 기준: IP, 포트, 내부망 조건을 따로 나누어 판단할 수 있으면 완료입니다. 내부 IP를 인터넷 어디서나 접속 가능한 주소로 오해하고 있지 않은지 확인하세요.
4. 최종 코드 정리하기
→ 이번 강의에서 생성한 chat_client/client.py의 최종 코드를 확인합니다.
4.1 기본 버전
chat_client/client.py
import socket
HOST = "127.0.0.1"
PORT = 5000
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect((HOST, PORT))
print("서버에 연결되었습니다.")
client_socket.close()
print("클라이언트 소켓을 닫았습니다.")
| 확인할 코드 | 의미 |
import socket |
소켓 모듈 사용 |
HOST = "127.0.0.1" |
접속할 서버 IP |
PORT = 5000 |
접속할 서버 포트 |
socket.socket(...) |
클라이언트 소켓 생성 |
connect((HOST, PORT)) |
서버 주소로 접속 |
close() |
클라이언트 소켓 닫기 |
4.2 참고: with 문을 사용하면
with를 사용하면 블록이 끝날 때 소켓이 자동으로 닫혀 close()를 빼먹을 가능성이 줄어듭니다. 지금은 기본 버전으로 흐름을 먼저 익히고, 이후 강의에서 with 구조가 등장할 때 참고용으로 확인하세요. 두 버전의 실행 결과는 같습니다.
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("서버에 연결되었습니다.")
print("클라이언트 소켓을 닫았습니다.")
→ 다음 강의 (6강): 지금 만든 연결 위에서 encode()·decode()·send()·recv()로 실제 메시지를 주고받습니다. 클라이언트가 메시지를 보내고 서버가 응답하는 1회 송수신을 완성합니다.