안녕하세요!
오늘은 서버와 소켓 통신 하여 클라이언트 간 채팅을 구현해보도록 하겠습니다.
테스트 환경
- Ubuntu 18.04.5 LTS
- Python 3.6.9
1. 소켓(Socket)이란?
소켓(Socket)이란 네트워크상에서 동작하는 프로그램 간 통신의 종착점(Endpoint)입니다. 즉, 프로그램이 네트워크에서 데이터를 통신할 수 있도록 연결해주는 연결부라고 할 수 있습니다.
채팅이나 게임 등 클라이언트와 서버 간 양방향 통신이 필요한 프로그램에 사용되고 있습니다.
2. 소켓 프로그래밍의 흐름
소켓 프로그램의 흐름은 아래 그림과 같이 진행이 됩니다.
3. 구현
소켓에 구현 시 IP, Port, Protocol 등 옵션을 설정 해줄 수 있습니다.
본 글에서는 간단히 집고 넘어가니 자세히 알고 싶은 부분이 있다면 아래 링크를 참고해주세요.
https://docs.python.org/ko/3/library/socket.html
3-1. 서버 소켓
파이썬 라이브러리 중 socket, _thread 라이브러리를 추가해준 뒤
- 소켓 생성(socket(family, type))
- family : IP4v, IP6v...
- type: 소켓 타입 / raw 소켓, 스트림소켓, 데이터그램 소켓...
- 소켓 옵션 설정(setsockopt(socket, level, optname, optval, optlen))
- socket : 소켓의 번호
- level : 옵션의 종류. 보통 SOL_SOCKET와 IPPROTO_TCP 중 하나를 사용
- optname : 설정을 위한 소켓 옵션의 번호
- optval : 설정 값이 저장된 주소값.
- optlen : optval 버퍼의 크기
- IP/Port 할당(bind(host, port))
- host : IP 주소
- port : 열어줄 포트 번호
- 연결 대기(listen())
코드를 작성해줍니다.
import socket
from _thread import *
client_sockets = [] # 서버에 접속한 클라이언트 목록
# 서버 IP 및 열어줄 포트
HOST = '127.0.0.1'
PORT = 9999
# 서버 소켓 생성
print('>> Server Start')
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind((HOST, PORT))
server_socket.listen()
추가로 클라이언트 간 채팅이 가능하기 위해 전역 변수로 클라이언트 소켓 정보를 담을 리스트(client_sockets)를 만들어 줍니다.
클라이언트가 연결 요청 후 새로운 스레드를 생성해주고 해당 클라이언트와 통신을 담당할 소켓을 배정하고
위에서 만든 소켓 정보 리스트(client_sockets)에 추가해 줍니다.
try:
while True:
print('>> Wait')
client_socket, addr = server_socket.accept()
client_sockets.append(client_socket)
start_new_thread(threaded, (client_socket, addr))
print("참가자 수 : ", len(client_sockets))
except Exception as e :
print ('에러는? : ',e)
finally:
server_socket.close()
담당 소켓의 코드는 다음과 같이 구성합니다.
# 쓰레드에서 실행되는 코드입니다.
# 접속한 클라이언트마다 새로운 쓰레드가 생성되어 통신을 하게 됩니다.
def threaded(client_socket, addr):
print('>> Connected by :', addr[0], ':', addr[1])
# 클라이언트가 접속을 끊을 때 까지 반복합니다.
while True:
try:
# 데이터가 수신되면 클라이언트에 다시 전송합니다.(에코)
data = client_socket.recv(1024)
if not data:
print('>> Disconnected by ' + addr[0], ':', addr[1])
break
print('>> Received from ' + addr[0], ':', addr[1], data.decode())
# 서버에 접속한 클라이언트들에게 채팅 보내기
# 메세지를 보낸 본인을 제외한 서버에 접속한 클라이언트에게 메세지 보내기
for client in client_sockets :
if client != client_socket :
client.send(data)
except ConnectionResetError as e:
print('>> Disconnected by ' + addr[0], ':', addr[1])
break
if client_socket in client_sockets :
client_sockets.remove(client_socket)
print('remove client list : ',len(client_sockets))
client_socket.close()
담당 클라이언트에게 받은 데이터를 다른 클라이언트에게 보내기 위해 소켓 정보 리스트를 가져와 자신 외 다른 클라이언트 소켓으로 데이터를 전송 하게 끔 구현 해줍니다.
클라이언트와 지속적으로 정보를 주고 받아야하기에 recv()/send() 부분을 반복문으로 반복시켜줍니다.
3-2. 클라이언트 소켓
소켓 생성 후, connect() 메소드에 접속하고자 하는 서버의 IP와 포트 번호를 적어줍니다.
import socket
from _thread import *
HOST = '127.0.0.1'
PORT = 9999
client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client_socket.connect((HOST, PORT))
서버에서 보내오는 데이터에 대한 메소드를 만든 후
send()와 recv() 가 별개로 작동하도록 받는 부분을 스레드로 작동 시켜줍니다.
# 서버로부터 메세지를 받는 메소드
# 스레드로 구동 시켜, 메세지를 보내는 코드와 별개로 작동하도록 처리
def recv_data(client_socket) :
while True :
data = client_socket.recv(1024)
print("recive : ",repr(data.decode()))
start_new_thread(recv_data, (client_socket,))
print ('>> Connect Server')
while True:
message = input('')
if message == 'quit':
close_data = message
break
client_socket.send(message.encode())
client_socket.close()
서버와 동일하게 접속이 유지 되는 동안에는 코드가 끝나지 않도록 반복문을 통해 계속 구동하도록 유지 시켜줍니다.
4. 실행
실행 창 구분
서버 / 클라이언트1 / 클라이언트2
참고 포스팅
* 파이썬 소켓 통신 채팅 예제
https://stickode.com/detail.html?no=2428
'Python' 카테고리의 다른 글
[Python] 음성파일을 텍스트로 전환하기 (0) | 2022.01.06 |
---|---|
[Python][Pycharm] 이미지 모자이크 처리 (0) | 2021.09.13 |
[Python] matplotlib 을 사용하여 원형 차트 그리기 (0) | 2021.09.02 |
[Python] Logging 모듈로 로그 남기기 (0) | 2021.08.26 |
[Python] OpenCV를 활용하여 얼굴 인식하기 (2) | 2021.08.14 |