소켓 통신의 이해를 위해 Python으로 간단한 소켓 통신을 구현해본다.
1. 서버 측
from socket import socket, AF_INET, SOCK_STREAM
from threading import Thread
BUF_SIZ = 1024
user_list = dict()
# 수신 핸들러
def receive_handler(client_socket, user_name):
# 클라이언트 소켓으로부터 메시지 수신대기
while True:
try:
data = client_socket.recv(BUF_SIZ).decode()
# 클라이언트와 연결이 끊어진 경우 반복 종료
except:
print(user_name, '와의 연결이 끊어졌습니다.')
break
# 클라이언트가 종료 명령어를 입력한 경우 반복 종료
if data == '/종료':
break
# 유저명: 메시지 형태로 다이얼로그 생성
dialog = f'{user_name}: {data}'
# 자신과 연결된 다른 클라이언트들에게 메시지 출력
print(dialog)
for user in user_list:
# 메시지를 보낸 유저에겐 출력하지 않음
if user == user_name:
continue
user_list[user].sendall(dialog.encode())
# 클라이언트 연결 종료
# 유저 리스트에서 해당 유저 제거
del user_list[user_name]
# 자신과 연결된 다른 클라이언트들에게 해당 유저의 퇴장알림 메시지 출력
msg = user_name+'님이 퇴장하셨습니다.'
print(msg)
for user in user_list:
user_list[user].sendall(msg.encode())
# 클라이언트 소켓 close
client_socket.close()
if __name__ == '__main__':
# 서버측 소켓 생성(socket)
# 주소 체계와 소켓 타입을 입력하여 생성한다.
# 인터넷 IPv4 주소체계를 사용하기 위해 AF_INET 을 사용
# TCP 소켓을 사용하기 위해 SOCK_STREAM 을 사용
server_socket = socket(AF_INET, SOCK_STREAM)
# 바인딩에 성공할 때 까지 입력을 다시받음
while True:
try:
print('HOST IP: ', end='')
HOST = input()
print('PORT: ', end='')
PORT = int(input())
# 연결할 서버의 주소, 포트를 입력하여 연결 요청
server_socket.bind(('localhost', PORT))
break
except:
continue
# 수신 대기열 생성
# 입력한 숫자만큼 대기열을 생성
server_socket.listen(5)
print('>> 채팅방 개설')
# 무한루프
while True:
try:
# 클라이언트로부터 로그인 요청을 대기
connection_socket, addr = server_socket.accept()
# 유저 등록
user_name = connection_socket.recv(BUF_SIZ).decode()
# 이미 존재하는 이름으로 로그인 시도시 재입력 요구
while user_name in user_list:
connection_socket.send('exist username'.encode())
user_name = connection_socket.recv(BUF_SIZ).decode()
# 로그인 성공시 성공메시지를 송신하고 유저리스트에 추가
user_list[user_name] = connection_socket
connection_socket.send('ok'.encode())
# 클라이언트로부터 메시지를 수신하기 위한 스레드 실행
receive_thread = Thread(target=receive_handler, args=(connection_socket, user_name))
receive_thread.daemon = True
receive_thread.start()
# 해당 유저의 입장알림 메시지를 자신과 다른 연결된 클라이언트들에게 출력
msg = user_name+'님이 입장하셨습니다.'
print(msg)
for user in user_list:
user_list[user].sendall(msg.encode())
# 서버 종료시 모든 클라이언트 소켓 close
except KeyboardInterrupt:
for user in user_list:
user_list[user].close()
- host 주소와 port 번호를 입력받아 채팅방 개설
- 수신 대기열을 만들고 클라이언트로부터 연결요청을 대기
- 연결요청이 들어오고 user_name이 전송되면 중복검사
- 중복 user_name인 경우 재입력 요구
- 중복되지 않는 user_name을 입력받을 경우 user_list에 user_name : connection_socket 의 형태로 저장
- 해당 클라이언트로부터 메시지를 전송받기 위한 스레드를 daemon으로 생성
=> daemon 스레드는 메인 프로세스가 종료시 자신의 실행여부에 관계없이 종료된다. - 메시지 수신 핸들러는 클라이언트로부터 메시지를 전송받아 일반 메시지라면 유저명: 메시지 의 형태로
포매팅하여 연결된 전 클라이언트에게 전송. 종료 명령어라면 해당 클라이언트의 소켓을 close 하고
해당 클라이언트의 퇴장을 연결중인 전 클라이언트에게 알림 - 해당 유저의 입장을 연결중인 전 클라이언트에게 알림
- 서버 종료시 모든 클라이언트 소켓 close
2. 클라이언트 측
from socket import socket, AF_INET, SOCK_STREAM
from threading import Thread
BUF_SIZ = 1024
# 수신 핸들러
def receive_handler(client_socket):
while True:
try:
data = client_socket.recv(BUF_SIZ)
data = data.decode()
print(data)
except:
print('연결 종료')
break
# 송신 핸들러
def send_handler(client_socket):
# 사용자가 '/종료' 를 입력하기 전까지 입력을 받아 서버에 전송
while True:
data = input()
client_socket.sendall(data.encode())
if data == '/종료':
break
# 소켓 close
client_socket.close()
if __name__ == '__main__':
# 클라이언트측 소켓 생성(socket)
# 주소 체계와 소켓 타입을 입력하여 생성한다.
# 인터넷 IPv4 주소체계를 사용하기 위해 AF_INET 을 사용
# TCP 소켓을 사용하기 위해 SOCK_STREAM 을 사용
client_socket = socket(AF_INET, SOCK_STREAM)
# 연결에 성공할 때 까지 입력을 다시받음
while True:
try:
print('HOST IP: ', end='')
HOST = input()
print('PORT: ', end='')
PORT = int(input())
# 연결할 서버의 주소, 포트를 입력하여 연결 요청
client_socket.connect(('localhost', PORT))
break
except:
continue
# 로그인
while True:
print('USER_ID: ', end='')
user = input()
client_socket.send(user.encode())
result = client_socket.recv(BUF_SIZ).decode()
if result == 'ok':
break
else:
print('이미 존재하는 유저명입니다.')
# 로그인 성공시
# 수신 대기 스레드
receive_thread = Thread(target=receive_handler, args=(client_socket,))
receive_thread.daemon = True
receive_thread.start()
# 송신 대기 스레드
send_thread = Thread(target=send_handler, args=(client_socket,))
send_thread.daemon = True
send_thread.start()
# 수신 대기 스레드의 종료를 대기
# 송신 대기 스레드가 종료시 소켓이 닫혀 수신 대기 스레드도 자동종료
# 수신 대기 스레드가 종료시 메인 프로세스가 종료되며 송신 대기 스레드도 자동종료
receive_thread.join()
- 서버의 host 주소와 port 번호를 입력받아 연결요청
- 연결 성공시 유저명을 입력하여 채팅방 입장 시도
- 중복되는 유저명일 경우 재입력
- 입장 성공시 수신 대기 스레드와 송신 대기 스레드를 daemon으로 생성
- 수신 대기 스레드는 서버와의 연결이 끊어지기 전까지 서버로부터 전송되는 메시지를 받아 출력
- 송신 대기 스레드는 입력된 메시지를 서버에 전송 종료 명령어가 입력될 경우 반복문을 빠져나와 소켓을 close
'CS > 네트워크' 카테고리의 다른 글
#6 Transport Layer (0) | 2021.11.16 |
---|---|
#5 Application Layer 2 - HTTP (0) | 2021.11.01 |
#3 Application Layer 1 - 소켓(Socket) (0) | 2021.10.28 |
#2 OSI 7계층 (0) | 2021.10.28 |
#1 네트워크 구성 2 - Network Core (0) | 2021.10.28 |