• 전화 통신과 소켓 통신 비교



| 특징 | Socket | WebSocket |
|---|---|---|
| 프로토콜 레벨 | TCP/IP 저수준 | HTTP 기반 고수준 |
| 라이브러리 | 파이썬 기본 내장 (socket) | 외부 라이브러리 필요 (websockets) |
| 연결 방식 | 양방향 통신 (Full-duplex) | 양방향 통신 (Full-duplex) |
| 주요 용도 | 일반 네트워크 통신, 서버-서버 통신 | 웹 기반 실시간 통신 |
| 구현 방식 | 동기/비동기 모두 가능 | 주로 비동기(async/await) |
| 보안 | 기본적인 TCP/IP 보안 | TLS/SSL 지원 (WSS) |
| 브라우저 지원 | 직접 사용 불가 | 대부분의 현대 브라우저 지원 |
| 초기 연결 | TCP 핸드셰이크 | HTTP 업그레이드 (WS 핸드셰이크) |
| 사용 사례 | - 데이터베이스 연결 - 네트워크 서비스 - 시스템 간 통신 | - 웹 채팅 - 실시간 게임 - 실시간 데이터 스트리밍 |
| 설치 필요성 | 불필요 (내장) | 필요 (pip install websockets) |
| 포트 사용 | 모든 포트 사용 가능 | 주로 80(WS)/443(WSS) 포트 사용 |
| 방화벽 통과 | 추가 설정 필요할 수 있음 | 일반적으로 용이 (HTTP 기반) |
| 에러 처리 | 수동 구현 필요 | 내장 에러 처리 메커니즘 |

import socket # socket 모듈 임포트 (저수준 네트워크 통신을 위한 표준 라이브러리)
#### 1번째 방법 ####
# 소켓 객체 생성 (IPv4 주소 체계, TCP 통신 방식)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 구글 서버의 80번 포트에 연결 (HTTP 기본 포트)
sock.connect(("www.google.com", 80))
# HTTP GET 요청 전송 (간단히 "GET \n"만 보냄 — 실제 요청은 더 복잡함)
sock.send("GET \n".encode())
# 응답 데이터 1024바이트 수신
data = sock.recv(1024)
# 수신한 데이터 출력 (바이트 -> 문자열로 디코딩)
print(data.decode())
# 소켓 연결 종료
sock.close()
#### 2번째 방법 ####
# with문을 사용하면 블록이 끝날 때 소켓이 자동으로 닫힘 (자원 해제 자동 처리)
with socket.socket() as sock:
# 서버 주소 및 포트 정의
addr = ("www.google.com", 80)
# 서버 연결
sock.connect(addr)
# 요청 전송
sock.send("GET \n".encode())
# 응답 수신
data = sock.recv(1024)
# 응답 출력
print(data.decode())
socket.AF_INET : IPv4socket.AF_INET6 : IPv6socket.SOCK_STREAM : TCPsocket.SOCK_DGRAM : UDPwith 블록을 벗어나면 자동으로 close() 실행with socket.socket() as sock는 sock = socket.socket() + sock.close()와 동일 효과b"..." 또는 b'...'bytes("ABC", "utf-8") ≈ "ABC".encode()str(문자열)과 bytes(바이트)는 다름
아래는 업로드한 코드를 타이핑하고, 각 단계별로 주석을 추가한 버전입니다:
import socket # socket 모듈 임포트
# 1. 소켓 생성 (IPv4, TCP)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 바인딩 (호스트 주소는 공란으로 모든 인터페이스 허용, 포트는 9999로 설정)
sock.bind(("", 9999))
# 3. 클라이언트 접속 대기
sock.listen()
print("The echo server start.....")
# 4. 접속 수락 (클라이언트가 접속하면 conn과 addr에 저장됨)
conn, addr = sock.accept()
# 5. 데이터 수신 (최대 1024바이트 읽기)
read_data = conn.recv(1024)
print("recv >>>>>>>> {}".format(read_data))
# 6. 접속 종료
conn.close() # 클라이언트 연결 종료
sock.close() # 서버 소켓 종료
bind((IP, PORT))
"" 또는 "0.0.0.0"을 입력하면 모든 네트워크 인터페이스에서 접속 수신 가능listen(1)
1이면 한 개의 클라이언트 접속만 허용accept()
conn은 해당 클라이언트와 통신할 소켓import socket # socket 모듈 임포트
# 1. 소켓 생성 (IPv4, TCP)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 서버에 접속 시도 (127.0.0.1: 로컬호스트, 포트 9999)
sock.connect(("127.0.0.1", 9999))
# 3. 데이터 송신 (사용자 입력 → bytes 변환 후 전송)
send_data = input("입력: ")
sock.sendall(bytes(send_data, "utf-8"))
# 4. 접속 종료
print("클라이언트 종료!")
sock.close()
send() vs sendall()send()
sendall()
✅ 일반적으로
sendall()사용 권장

import socket # socket 모듈 임포트
# 1. 소켓 생성 (IPv4, TCP)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 바인딩 (모든 IP 허용, 포트 9999)
sock.bind(("", 9999))
# 3. 클라이언트 접속 대기
sock.listen()
print("The echo server start.....")
# 4. 클라이언트 접속 수락
conn, addr = sock.accept()
# 5-1. 데이터 수신
read_data = conn.recv(1024)
print("recv >>>>>>>> {}".format(read_data))
# 5-2. 데이터 송신 (수신한 데이터를 그대로 다시 전송 - 에코)
conn.sendall(read_data)
print("send >>>>>>>> {}".format(read_data))
# 6. 접속 종료
conn.close()
sock.close()
conn.sendall(read_data)는 수신한 데이터를 그대로 되돌려보내는 동작입니다.sendall() 사용으로 전송 누락 없이 안정적인 송신이 가능합니다.import socket # socket 모듈 임포트 (네트워크 통신 기능 제공)
# 1. 소켓 생성 (IPv4 주소 체계, TCP 프로토콜)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 접속 시도 (127.0.0.1은 로컬호스트, 포트 9999는 서버가 열어둔 포트)
sock.connect(("127.0.0.1", 9999))
# 3-1. 데이터 송신
send_data = input("입력: ") # 사용자로부터 메시지 입력받기
sock.sendall(bytes(send_data, "utf-8")) # 입력값을 UTF-8로 인코딩하여 서버로 전송
# 3-2. 데이터 수신
recv_data = sock.recv(1024) # 서버로부터 최대 1024바이트 데이터 수신
print(f"recv : {recv_data}") # 수신한 데이터 출력 (바이트 형태)
# 4. 접속 종료
print("클라이언트 종료!") # 종료 메시지 출력
sock.close() # 소켓 종료 (연결 해제)
sendall()과 recv()는 각각 안전한 송신과 수신을 위한 기본 메소드입니다.import socket # socket 모듈 임포트 (네트워크 통신용)
# with문 사용: 블록이 끝나면 자동으로 소켓 close됨
with socket.socket() as s:
s.bind(("", 9999)) # 모든 IP에서 오는 연결을 9999 포트로 바인딩
s.listen() # 클라이언트 접속 대기 상태 진입
print("The echo server start.....")
conn, addr = s.accept() # 클라이언트 접속 수락, conn은 통신용 소켓, addr은 주소
# 무한 반복: 클라이언트 메시지를 계속 수신하고 다시 전송
while True:
recv_data = conn.recv(1024) # 데이터 수신 (최대 1024바이트)
print("recv >>>>>>>> {}".format(recv_data.decode())) # 수신 데이터 출력
# "end"라는 문자열이 수신되면 반복 종료
if recv_data.decode() == "end":
break
conn.sendall(recv_data) # 수신 데이터를 클라이언트에게 다시 전송 (에코)
print("send >>>>>>>> {}".format(recv_data)) # 전송 데이터 출력
print("echo complete..") # 서버 종료 메시지
"end"를 보낼 때까지 에코 서버 기능 반복 수행recv() → 수신, sendall() → 에코 응답with문 덕분에 소켓 자동 정리conn.close()는 생략되었지만, with가 끝나면 서버 소켓은 안전하게 닫힘.import socket # socket 모듈 임포트 (네트워크 통신 기능 제공)
# 1. 소켓 생성 (IPv4 주소 체계, TCP 프로토콜 사용)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 서버 접속 시도 (로컬호스트 127.0.0.1, 포트 9999)
sock.connect(("127.0.0.1", 9999))
# 무한 루프를 통해 사용자 입력을 반복적으로 전송
while True:
# 3-1. 데이터 송신
send_data = input("입력: ") # 사용자로부터 입력받기
sock.sendall(bytes(send_data, "utf-8")) # 입력값을 UTF-8로 인코딩하여 서버로 전송
# 사용자가 "end"를 입력하면 루프 종료
if send_data == "end":
break
# 3-2. 데이터 수신 (서버로부터 에코 응답 받기)
recv_data = sock.recv(1024) # 최대 1024바이트 수신
print(f"recv : {recv_data}") # 수신한 데이터 출력 (바이트형)
# 4. 접속 종료
print("클라이언트 종료!") # 종료 메시지 출력
sock.close() # 소켓 종료 (연결 해제)
"end"를 입력하면 통신 종료 및 소켓 닫힘.
Lock 사용 권장import threading # 파이썬 쓰레딩 모듈 임포트
import time # 시간 지연을 위한 모듈
# 첫 번째 쓰레드가 실행할 함수
def thread1():
for i in range(5):
print("thread1 ++++++++")
time.sleep(0.1) # 0.1초 대기
# 두 번째 쓰레드가 실행할 함수
def thread2():
for i in range(5):
print("thread2 ========")
time.sleep(0.1) # 0.1초 대기
# 쓰레드 객체 생성 (각 함수 지정)
th1 = threading.Thread(target=thread1)
th2 = threading.Thread(target=thread2)
# 쓰레드 시작
th1.start()
th2.start()
# 쓰레드가 끝날 때까지 대기 (join 없으면 main END 먼저 출력될 수 있음)
th1.join()
th2.join()
# 모든 쓰레드가 종료된 후 출력
print("main END!") # 주 쓰레드 종료 메시지
join() 함수 역할join(): 해당 쓰레드가 완전히 끝날 때까지 메인 쓰레드가 기다리게 함thread1()과 thread2()는 각각 별도의 쓰레드에서 실행됨start()로 쓰레드 실행, join()으로 완료 대기join()이 없으면 main END!가 먼저 출력될 수 있음# 부모 클래스 dad 정의
class dad():
def dad_look(self):
print("handsome") # 아빠 외모 출력
def doYouKonw(self, lec): # 일반적인 질문 응답 메소드
print(f"I don't know {lec}")
# 부모 클래스 mom 정의
class mom():
def mom_look(self):
print("pretty") # 엄마 외모 출력
# 자식 클래스 child1: dad와 mom을 다중 상속
class child1(dad, mom): # 상속만 하고 내용 없음
pass
# 자식 클래스 child2: dad와 mom을 다중 상속 + 메서드 오버라이딩
class child2(dad, mom):
def doYouKonw(self, lec): # 오버라이딩 (부모 메소드 재정의)
print(f"I love {lec}")
# child1 객체 생성 및 부모 메소드 사용
c1 = child1()
c1.dad_look() # → "handsome"
c1.mom_look() # → "pretty"
# child2 객체 생성 및 오버라이딩된 메소드 호출
c2 = child2()
c2.doYouKonw("python") # → "I love python"
child1은 dad와 mom의 기능을 모두 상속child2는 dad의 doYouKonw() 메서드를 오버라이딩하여 새로 정의thread_server.py)import socket
import threading
# 수신 처리 함수 (클라이언트별로 실행될 스레드 함수)
def thread_recv(client_socket, addr):
while True:
recv_data = client_socket.recv(1024)
print(f"{addr}에서 받은 메시지 : {recv_data.decode()}")
# 받은 데이터를 다시 클라이언트에게 전송 (에코)
client_socket.sendall(recv_data)
# "end" 수신 시 연결 종료
if recv_data.decode() == "end":
print(f"{addr}의 접속을 종료합니다.")
client_socket.close()
break
# 1. 소켓 생성 및 바인딩
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(("", 9999))
sock.listen()
print("The echo server start.....")
# 2. 클라이언트 접속 대기 및 처리
while True:
conn, addr = sock.accept()
print(f"{addr} 연결됨")
# 클라이언트마다 별도 스레드 생성
recv_handler = threading.Thread(target=thread_recv, args=(conn, addr))
recv_handler.start()
thread_client.py)import socket
import threading
import time
# 송신 쓰레드 함수
def send_thread(sock):
while True:
send_data = input("입력: ")
sock.sendall(bytes(send_data, "utf-8"))
if send_data == "end":
print("서버에 종료 요청 보냄")
break
time.sleep(0.3) # 약간의 딜레이 (수신 전에 반복 방지)
# 수신 쓰레드 함수
def recv_thread(sock):
while True:
recv_data = sock.recv(1024)
print(f"recv : {recv_data.decode()}")
if recv_data.decode() == "end":
print("서버에서 종료 응답 받음")
break
# 1. 소켓 생성 및 서버 접속
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("127.0.0.1", 9999))
# 2. 송신/수신 쓰레드 동시 실행
sth = threading.Thread(target=send_thread, args=(sock,))
rth = threading.Thread(target=recv_thread, args=(sock,))
sth.start()
rth.start()
| 항목 | 설명 |
|---|---|
| 서버 | 접속한 클라이언트마다 thread_recv() 쓰레드 생성, 에코 처리 |
| 클라이언트 | 송신과 수신을 각각 별도의 쓰레드로 처리해 동시 통신 가능 |
| "end" | 종료 신호 역할 – 전송되면 해당 쓰레드 종료 및 소켓 닫힘 |
thread_server2.py)import socket
import threading
import os
numClient = 0 # 현재 접속 클라이언트 수
# 클라이언트 수신 처리용 스레드 함수
def thread_recv(client_socket, addr):
global numClient
while True:
recv_data = client_socket.recv(1024)
# 어떤 스레드(클라이언트)인지 확인
print(f"Thread Name [{threading.current_thread().name}]에서 보낸 메시지 : {recv_data.decode()}")
# 'end' 수신 시 연결 종료
if recv_data.decode() == "end":
numClient -= 1
print(f"{addr}의 접속을 종료합니다.")
print(f"현재 서버에 접속된 클라이언트 수 : {numClient}")
client_socket.close()
# 모든 클라이언트 접속 종료 시 서버도 종료
if numClient == 0:
print("서버를 종료합니다.")
os._exit(0)
break
# 에코 메시지 전송
client_socket.sendall(recv_data)
# =========================
# 메인 서버 코드 시작 부분
# =========================
# 1. 소켓 생성
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 바인딩
sock.bind(("", 9999))
# 3. 클라이언트 접속 대기
sock.listen()
print("The echo server start.....")
# 4. 접속 수락 및 스레드 생성 반복
while True:
conn, addr = sock.accept()
numClient += 1 # 접속자 수 증가
print(f"현재 {addr}이 접속했습니다.")
print(f"현재 서버에 접속된 클라이언트 수 : {numClient}")
# 클라이언트마다 별도 스레드로 수신 처리
recv_handler = threading.Thread(target=thread_recv, args=(conn, addr), name=str(addr))
recv_handler.start()
| 항목 | 설명 |
|---|---|
thread_recv() | 각 클라이언트 통신을 담당할 쓰레드 함수 |
recv_handler = threading.Thread(...) | 클라이언트마다 독립 쓰레드 생성 |
name=addr | 스레드 이름을 접속 주소로 설정해 추적 가능 |
os._exit(0) | 모든 접속 종료 시 서버 강제 종료 |
아래는 업로드한 두 이미지를 통합하여 chat_server2.py 전체 코드를 완전하게 정리하고 주석을 덧붙인 버전입니다:
import socketserver # 고수준 TCP 서버 구축용 모듈
# =============================
# ▶ 사용자 관리용 핸들러 클래스
# =============================
class MyHandler(socketserver.BaseRequestHandler):
users = {} # {username: (socket, addr)} 형태로 사용자 정보 저장
# 전체 사용자에게 메시지 전송
def broadcast(self, msg):
for sock, addr in self.users.values():
sock.send(msg.encode()) # 문자열 → 바이트로 전송
# 사용자 등록
def addUser(self, username, conn, addr):
if username in self.users:
conn.send("이미 등록되어 있습니다.\n".encode())
return None
self.users[username] = (conn, addr) # 등록
self.broadcast("★{}님이 참여했습니다.\n".format(username))
print("채팅 참여 인원 {}명".format(len(self.users)))
return username
# 사용자 제거
def delUser(self, username):
del self.users[username]
self.broadcast("★{}님이 퇴장했습니다.\n".format(username))
print("채팅 참여 인원 {}명".format(len(self.users)))
# =============================
# ▶ 클라이언트 통신 처리 함수
# =============================
def handle(self):
print(self.client_address[0]) # 접속 IP 출력
# 사용자 이름 등록 루프
while True:
self.request.send("이름을 입력하세요: ".encode())
username = self.request.recv(1024).decode()
if self.addUser(username, self.request, self.client_address):
break # 등록 성공 시 탈출
# 채팅 메시지 수신/송신 루프
while True:
data = self.request.recv(1024)
print("[{}] {}".format(username, data.decode()))
if data.decode() == "end":
self.request.close()
break
self.broadcast("[{}] {}\n".format(username, data.decode()))
print("[{}] 접속 종료".format(username))
self.delUser(username)
# =============================
# ▶ 쓰레드 기반 TCP 서버 클래스
# =============================
class ChatServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass
# =============================
# ▶ 서버 실행
# =============================
print("chat server start...")
chat_serv = ChatServer(("", 9999), MyHandler)
chat_serv.serve_forever() # 서버 실행
chat_serv.shutdown() # 서버 중단 요청 시
chat_serv.server_close() # 소켓 자원 해제
| 구성 요소 | 설명 |
|---|---|
MyHandler | 클라이언트 요청을 처리하는 클래스 |
users 딕셔너리 | 접속한 모든 유저의 소켓, 주소 정보 저장 |
broadcast() | 모든 유저에게 메시지 전송 |
addUser() / delUser() | 유저 등록 및 삭제 관리 |
handle() | 클라이언트 연결 시 실행, 이름 받고 채팅 처리 |
ChatServer | ThreadingMixIn 상속 → 다중 접속 처리 가능 |