지난 포스팅에서 파이썬 소켓 프로그래밍으로 메세지를 주고받는 내용에 대해서 글을 작성하였습니다
이번에는 지난번에 비해 업그레이드? 된 Server와 Client 간의 실시간 채팅 프로그램을 만들어보려고 합니다
코드 설명의 경우 지난번 포스팅과 겹치는 부분은 간략하게 설명하고 넘어가겠습니다 지난번 포스팅을 읽고 이번 포스팅을 읽으시면 이해하는데 훨씬 더 도움이 될 겁니다
우선 아래와 같은 두 가지 조건이 필요합니다
1. 한 번에 여러 개의 메세지를 전달할 수 있어야 한다
2.Client와 Server 간에 비동기 통신으로 메세지를 전송하면서 메세지를 받을 수도 있어야 한다
필요한 모듈
1
2
|
from socket import *
from threading import Thread
|
cs |
Server와 Client 간의 Socket연결이 필요하니 socket함수가 필요하고 멀티 스레드를 구현하기 위해 Thread라는 모듈이 필요합니다
서버(Server) 소켓 설정
1
2
3
4
5
6
7
|
s = socket(AF_INET, SOCK_STREAM)
s.bind(("localhost",7777))
s.listen(5)
print('서버가 연결을 기다리고 있습니다')
s,addr = s.accept()
print(str(addr)+"에서 접속하였습니다\n")
|
cs |
#1
socket() 함수에 AF_INET은 Ipv4을 의미한다고 지난 포스팅에서 소개하였고 SOCK_STREAM은 TCP를 의미합니다
socket의 소켓 타입과 소켓 어드레스가 담긴 socket함수를 s변수에 넣어줍니다 sned와 receive시에 s변수를 사용합니다
#2
bind() 함수로 아이피 주소와 포트번호를 지정하는데 이를 튜플로 묶어줍니다
#3
listen() 함수는 Client의 요청을 기다리는 함수이며 최대 5개의 Client요청을 대기할 수 있습니다
#4
소켓 생성 후에 서버(Server)가 연결을 기다리고 있다고 터미널에 출력해줍니다
#6
accpet() 함수는 Client가 접속할 때까지 대기하다가 클라이언트가 접속하게 되면 새로운 소켓을 리턴합니다
s에 담기는 내용 <socket.socket fd=296, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7777), raddr=('127.0.0.1', 1247)>
addr에 담기는 내용
('127.0.0.1', 1247)
#7
addr에는 클라이언트의 IP와 포트가 담겨있습니다
Client가 접속되면 어디에서 접속하였는지 Server터미널에 출력해줍니다
서버(Server)메세지 송수신 멀티스레드 설정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
def send(s):
while True:
print(Colors.WHITE+">>"+Colors.RESET,end="")
message = input("")
if message == 'exit' or '종료':
break
s.send(str(message).encode("utf-8"))
def receive(s):
while True:
reply = s.recv(1024).decode("utf-8")
print(Colors.GREEN+"Client:" + reply+Colors.RESET)
send = Thread(target = send, args = (s,))
receive = Thread(target = receive, args = (s,))
send.start()
receive.start()
send.join()
s.close()
|
cs |
#1(메세지 전송 기능이 담겨있는 send 함수)과 #12(메세지 수신기능이 담겨있는 receive 함수)를 각각의 함수로 만든 후에 스레드로 호출하여 사용할 수 있습니다
#1~#2
클라이언트와 서버가 메세지를 지속적으로 주고받기 위해서는 각각의 스레드가 지속적으로 실행되어 있어야 하기 때문에 send와 receive함수에 while을 넣었고 send와 receive 프로세스가 종료되지 않는 경우 무한적으로 작업이 수행될 겁니다
지금 생성된 send함수는 #17 부분에서 Thread로 처리됩니다
#3
print문에 Colors함수는 출력 문자 텍스트 색상에 관한 함수입니다 해당 내용은 블로그 본문 하단에서 참고 링크를 남겨놓겠습니다 그리고 ,end=""을 넣어줌으로써 #4라인에서 사용자로부터 내용을 입력받을 때 ">>"문자열의 다음 줄이 아닌 같은 줄에서 입력받을 수 있도록 해줍니다
#4
while문을 사용하여 사용자로부터 지속적으로 메세지를 입력받은 후 message변수에 담습니다
#6~#7
if문을 사용하여 사용자가 exit혹은 종료라는 문자열을 입력하였을 경우에는 break함수를 통해 반복문 밖으로 즉 send함수의 while문을 벗어나도록 합니다
#9
s=socket(AF_INET, SOCK_STREAM) s.send 함수를 통해 사용자로부터 입력받은 내용이 있는 message의 변수의 내용을 인코딩하여 클라이언트로 전송합니다
#12~#15
이번엔 recevie함수를 사용하여 메세지를 받는 부분을 구현해줍니다
마찬가지로 while문으로 지속적으로 클라이언트에서 입력한 데이터가 들어오면 인코딩 된 내용을 디코딩하여 터미널에 출력해줍니다
#17~#18
앞서 만든 함수를 한 번씩 호출하지 않고 메세지의 송수신 순서와 상관없이 메세지를 보낼 수 있도록 Thread함수를 사용할 겁니다 Thread함수를 사용하면 파이썬 실행 프로그램에서 한 번에 한 가지의 일만 처리하는 것이 아니라 동시에 여러 가지 일을 처리할 수 있게 됩니다
위의 코드에서 send함수와 receive함수를 Thread로 처리하였는데 send함수를 가지고 설명을 해보겠습니다
Thread()에는 여러 가지 인자 값이 들어가게 되는데 크게 target과 args를 주의 깊게 보면 됩니다
target에는 실제 스레드가 실행할 함수의 이름을 (sned) 넣어주면 되고 해당 함수에 전달할 인자 값 (s)을 넣어주면 됩니다
sender = Thread(target = send(실행할 함수 이름), args = (s(전달할 인자값),))
*args 인자 값이 한 개일 경우 뒤에 , 를 반드시 붙여줌 그래야 튜플로 인식해서 오류가 발생하지 않음)
코드를 보면 s뒤에 , 이 있는데 args에 인자 값이 하나일 경우에는 () 안에 첫 번째 인자 값뒤에 , 을 반드시 넣어야만 튜플로 인식하여 오류가 발생하지 않습니다
args는 반복 가능한 리스트(List), 튜플(Tuple), 딕셔너리(Dictionary), 셋(Set)와 같은 iterable자료형만 입력이 가능합니다
근데 만약에 인자 값이 하나일 때 괄호로 감싸면 파이썬에서는 튜플이 아니라 변수로 인식하기 때문에 인자 값이 하나라면 (s,) 이런 식으로 , 을 입력해줘야 튜플로 인식하게 됩니다
#20~#21
생성된 Thread는 Tharead명. start()으로 실행할 수 있습니다
#23~#24
join 함수는 코드 상의 send.join로 예를 들면 자식 스레드 즉 send함수를 작동시키는 자식 스레드, 메세지 전송을 하도록 만든 스레드의 종료를 기다려줍니다
#7에서 break함수로 탈출하게 된 경우 스레드가 종료되어 join 바로 아래에 있는 s.close() 함수를 통해 서버를 종료하게 됩니다
클라이언트(Client) 소켓 설정
1
2
3
4
5
|
s=socket(AF_INET, SOCK_STREAM)
s.connect(("127.0.0.1",7777))
print('서버에 접속하였습니다')
|
cs |
#1
주소체계(AdressFamily)와 소켓 타입(SocketTtype)을 입력하여 소켓을 생성해줍니다
#2
connect 함수 안에 Server에서 지정한 주소와 포트번호를 튜플 형식으로 지정해줍니다
#4
Server에 접속한 뒤에 사용자에게 서버에 접속이 되었다는 메세지를 출력해줍니다
클라이언트(Client) 메세지 송수신 멀티스레드 설정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
def send(s):
while True:
print(Colors.WHITE+">>"+Colors.RESET,end="")
msg = input("")
if msg == 'exit' or '종료':
break
s.send(str(msg).encode())
def receive(s):
while True:
reply = s.recv(1024).decode("utf-8")
print(Colors.GREEN+"Server: " + reply+Colors.RESET)
send = Thread(target = send, args = (s,))
receive = Thread(target = receive, args = (s,))
send.start()
receive.start()
send.join()
s.close()
|
cs |
#1~#8
Server 쪽의 전송 수신 함수와 동일하니 똑같은 설명은 생략하겠습니다
위 코드 상의 sned함수는 클라이언트(Client)에서의 메세지 송신 함수입니다
#11~#15
마찬가지로 Server 쪽에서 설명한 내용과 동일한 함수입니다
위 코드에서는 클라이언트(Client)의 메세지 수신 함수입니다
#17~#18
이 부분도 Server 측과 동일합니다 sned함수와 receive함수를 각각의 스레드로 만들어 메세지의 송수신 여부와 상관없이 지속적으로 메세지를 주고받을 수 있도록 만들어줍니다
#20~#21
생성한 스레드를 start() 함수를 통해 실행시켜줍니다
#23~24
join함수로 send스레드가 종료될 때까지(if문으로 탈출할 경우) 기다린 후 스레드가 종료될 경우에는 소켓을 종료합니다
실행결과
이제 서버를 시작한 후 클라이언트로 접속해보겠습니다
서버(Server)와 클라이언트(Client)를 각각 실행시켰더니 서버에서는 연결을 기다리다가 클라이언트가 실행되자 어디에서 접속하였는지 정보를 알려주고 사용자의 메세지를 입력받을 준비를 하고 있습니다
클라이언트에서는 서버에 접속한 후에 사용자의 메세지를 입력받을 준비를 하고 있습니다
이제 각각 메세지를 입력하여 순서와 상관없이 메세지를 주고받아보겠습니다
서버(Server)와 클라이언트(Client)에서 순서와 상관없이 서로 메세지를 주고받고 있습니다
메세지 전송 시에 출력되는 텍스트는 하얀색 텍스트로 구분하였고 메세지 수신 시에 출력되는 내용은 초록색으로 구분하였습니다
출력 텍스트 색상에 관한 내용은 아래 블로그에서 자세히 설명한 내용이 있어서 첨부합니다
저는 아래 블로그에서 2번째 방법인 글로벌과 클래스 방법을 통해 사용하였습니다
https://info-lab.tistory.com/230
그리고 send함수 부분에서 if문으로 스레드를 종료한 후에 소켓을 닫을 수 있게 설정해놨었는데
서버(Server)나 클라이언트(Client)에서 exit를 입력할 경우에 소켓이 닫히면서 연결이 끊기게 됩니다
GITHUB
참고자료