오늘은 파이썬으로 TCP/IP 멀티 스레드 소켓 통신을 하는 다중 클라이언트 채팅 프로그램을 구현해 보았다.
[기본 로직]
[서버 코드 설명]
패키지, 모듈 선언
import socket
import threading
from queue import Queue
- import socket : 통신을 위한 파이썬 기본 패키지에 포함된 내장 socket 모듈
- import threading : 쓰레드를 사용하기 위한 모듈
- from queue import Queue : FIFO(First In First Out)기반의 자료구조인 큐(queue) 모듈
if __name__ == '__main__':
- 해당 파일을 직접 실행 한다면 True
큐 생성
send_queue = Queue()
PORT 번호 지정
PORT = 6060
서버 주소 지정
SERVER = socket.gethostbyname(socket.gethostname())
- socket.gethostname() : 로컬 호스트 이름을 반환한다
- socket.gethostbyname(호스트 이름) : 로컬 호스트 IP 주소를 반환한다
최종 서버 설정
ADDR = (SERVER, PORT)
- IP주소와 PORT번호를 튜플로 세팅
소켓 생성
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- socket() : 소켓 생성 함수
- socket(사용할 프로토콜, 데이터 전송 타입)
- AF_INET : 해당 소켓은 IPV4(IP version 4)로 사용을 의미한다
- SOCK_STREAM : 해당 소켓에 TCP 패킷을 받겠다는 의미한다.
서버 생성
server.bind(ADDR)
- bind() : 서버 설정 함수
- bind(소켓 정보)
주의 : bind함수는 인자를 튜플형으로 받는다!
서버 접속 설정
server.listen()
- listen() : 서버에 클라이언트 동시 접속 수 설정 함수
- listen(해당 소켓에 동시 접속 가능 수)
카운트
count = 0
- 스레드 번호 변수
리스트
group = []
- 연결된 클라이언트의 소켓 정보를 저장할 리스트
연결 요청 수락
conn, addr = server.accept()
group.append(conn)
- accept() : 해당 소켓을 열고 대기, 클라이언트 연결 요청을 수락하는 함수
- group.append(conn) : 연결 요청 수락된 클라이언트를 리스트에 추가한다
Send 스레드 생성
if count > 1:
send_queue.put('Group Changed')
sendthread = threading.Thread(target=Send, args=(group, send_queue))
sendthread.start()
pass
else:
sendthread = threading.Thread(target=Send, args=(group, send_queue))
sendthread.start()
- Thread() : 스레드 생성 함수
- Thread(target=함수명, args=(매개변수))
- 소켓에 연결된 모든 클라이언트에게 동일한 메시지를 보내기 위한 스레드(브로드캐스트)
Recv 스레드 생성
recvthread = threading.Thread(target=Recv, args=(conn, count, send_queue))
recvthread.start()
- Thread() : 스레드 생성 함수
- Thread(target=함수명, args=(매개변수))
- 소켓에 연결된 각각의 클라이언트의 메시지를 받을 스레드
Recv() 함수
def Recv(conn, count, send_queue):
...
while True:
message = conn.recv(1024).decode()
...
...
send_queue.put([message, conn, count])
- message = conn.recv(1024).decode() : 클라이언트로 부터 데이터를 받아 message에 대입한다
- send_queue.put([message, conn, count]) : 각각의 클라이언트에 데이터를 보내기 위해 에 데이터를 추가한다
Send() 함수
def Send(group, send_queue):
...
while True:
try:
recv = send_queue.get()
if recv == 'Group Changed':
print('Group Changed')
break
for conn in group:
message = str(recv[0])
if recv[1] != conn:
...
conn.send(bytes(message.encode()))
else:
pass
except:
pass
- recv = send_queue.get() : 큐에 저장된 데이터를 recv에 대입한다
- message = str(recv[0]) : recv배열에 클라이언트로부터 받은 데이터를 추출한다
- conn.send(bytes(message.encode())) : 데이터를 보낸 클라이언트 제외한 모든 클라이언트에게 데이터를 보낸
[server.py]
import socket
import threading
from queue import Queue
def Send(group, send_queue):
print('Thread Send Start')
while True:
try:
#새롭게 추가된 클라이언트가 있을 경우 Send 쓰레드를 새롭게 만들기 위해 루프를 빠져나감
recv = send_queue.get()
if recv == 'Group Changed':
print('Group Changed')
break
#for 문을 돌면서 모든 클라이언트에게 동일한 메시지를 보냄
for conn in group:
message = str(recv[0])
if recv[1] != conn:
#client 본인이 보낸 메시지는 받을 필요가 없기 때문에 제외시킴
print(message)
conn.send(bytes(message.encode()))
else:
pass
except:
pass
def Recv(conn, count, send_queue):
print('Thread Recv(' + str(count) + ') Start\n')
while True:
message = conn.recv(1024).decode()
print(f"RECEIVE([{SERVER}:6060][Thread:{str(count)}]{message})")
send_queue.put([message, conn, count])
#각각의 클라이언트의 메시지, 소켓정보, 쓰레드 번호를 send로 보냄
if __name__ == '__main__':
send_queue = Queue()
# PORT 지정
PORT = 6060
# SERVER 설정
SERVER = socket.gethostbyname(socket.gethostname())
# socket.gethostname() -> PC Name
# socket.gethostbyname(PC Name) -> IP Adress
# Final Server Aress
ADDR = (SERVER, PORT)
# result : ('PC Adress(IPV4)', PORT(6060))
print(f"※STARTING※\nserver is starting......\nserver adress : {SERVER}:{PORT}\n")
# socket 설정
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# AF_INET -> 해당 소켓은 IPV4(IP version 4)로 사용을 의미
# SOCK_STREAM -> 해당 소켓에 TCP 패킷을 받겠다는 의미
# 서버와 PORT 연결
server.bind(ADDR)
# 서버(PC Adress(IPV4))에 PORT(6060)를 연결
# bind는 값을 튜플로 받기에 괄호가 두개이어야한다
# result : server.bind(('PC Adress', 6060))
server.listen()
# server에 새로운 연결을 listen
# 소켓 연결, 여기서 파라미터는 접속수를 의미
print(f"※LISTENING※\nserver is listening on {SERVER}\n")
count = 0
# 쓰레드 번호 카운트
group = []
#연결된 클라이언트의 소켓정보를 리스트로 묶기 위함
while True:
count += 1
conn, addr = server.accept()
# 해당 소켓을 열고 대기
group.append(conn)
#연결된 클라이언트의 소켓정보
print(f"※NEW CONNECTION※\n{str(addr)} connected.")
#소켓에 연결된 모든 클라이언트에게 동일한 메시지를 보내기 위한 쓰레드(브로드캐스트)
#연결된 클라이언트가 1명 이상일 경우 변경된 group 리스트로 반영
if count > 1:
send_queue.put('Group Changed')
sendthread = threading.Thread(target=Send, args=(group, send_queue))
sendthread.start()
pass
else:
sendthread = threading.Thread(target=Send, args=(group, send_queue))
sendthread.start()
#소켓에 연결된 각각의 클라이언트의 메시지를 받을 쓰레드
recvthread = threading.Thread(target=Recv, args=(conn, count, send_queue))
recvthread.start()
[클라이언트 코드 설명]
※서버 코드와 비슷한 부분을 제외하였다
클라이언트 정보 입력
SERVER = input("IP:")
PORT = int(input("PORT:"))
NAME = input("NAME:")
- IP주소, PORT번호, 클라이언트 이름을 입력받는다
서버에 연결 요청
client.connect(ADDR)
- connect() : 서버에 연결 요청하는 함수
- connect(소켓 정보)
Send() 함수
def Send(client):
while True:
str = input()
if str != '':
message = "[" + NAME + "] " + str
client.send(bytes(message.encode()))
- 보낼 데이터의 형식 : "[클라이언트 이름] 보낼 데이터"
Recv() 함수
def Recv(client):
while True:
recv_data = client.recv(1024).decode()
print(recv_data)
- 서버로부터 데이터를 받아 출력한다
[client.py]
import socket
import threading
def Send(client):
while True:
str = input()
if str != '':
message = "[" + NAME + "] " + str
# 사용자 입력
client.send(bytes(message.encode()))
# Client -> Server 데이터 송신
def Recv(client):
while True:
recv_data = client.recv(1024).decode()
# Server -> Client 데이터 수신
print(recv_data)
if __name__ == '__main__':
# client 설정
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# AF_INET -> 해당 소켓은 IPV4(IP version 4)로 사용을 의미
# SOCK_STREAM -> 해당 소켓에 TCP 패킷을 받겠다는 의미
SERVER = input("IP:")
# 통신할 대상의 IP 주소
PORT = int(input("PORT:"))
# 통신할 대상의 Port 주소
NAME = input("NAME:")
# Server Aress
ADDR = (SERVER, PORT)
# result : ('PC Adress(IPV4)', PORT(6060), 'NAME')
# server에 연결
client.connect(ADDR)
# 서버로 연결시도
print(f'Connecting to {SERVER}:{PORT}')
#Client의 메시지를 보낼 쓰레드
sendthread = threading.Thread(target=Send, args=(client, ))
sendthread.start()
#Server로 부터 다른 클라이언트의 메시지를 받을 쓰레드
recvthread = threading.Thread(target=Recv, args=(client, ))
recvthread.start()
[실행 화면]
[소스 코드 링크]
https://github.com/levhyun/python_socket
'Python' 카테고리의 다른 글
Flask WEB Server (0) | 2023.01.18 |
---|---|
Python Database (0) | 2023.01.18 |
Python Translator (0) | 2023.01.18 |