미루고 미루고 또 미루다 해가 바뀌고 나서야 채팅 프로그램 포스팅을 하게 되었다.
작년 5월경, 자대 배치받고 환경에 슬슬 적응해나갈 때쯤 한 가지 불편한 점이 생겼다. 사무실에서 일하다 보면 다른 사무실 사람들과의 소통이 필수적이다. 하지만 각자 다른 사무실에 있고 사무실 간 거리가 있기 때문에 소통수단이 필요하다. 내선 전화가 있긴 하지만 교장 관리나 작업 등으로 한쪽이 자리를 비워 엇갈리는 경우가 정말 빈번하다. 부재중 전화가 와있어 다시 전화하면 80% 확률로 상대방이 부재중이다... 결국 상대 사무실까지 걸어가서 쪽지를 남긴다. 상당히 비효율적이고 불편했다.
그래서 채팅 프로그램을 만들었다. 소켓 통신 관련 이론 지식을 얻기 위해 도서관에 있던 '윤성우의 TCP/IP 열혈 프로그래밍' 책을 읽고 사무실에서 남는 시간에 파이썬으로 코딩을 했다. 책에서는 c언어 기반으로 설명하지만 사무실 컴퓨터로는 Jupyter Notebook만 사용할 수 있어서 파이썬 메소드를 다시 찾아보며 만들었다. 파이썬이 2년 만이라 어색했다. 어찌저찌 구현을 다 하고 배포하여 다른 사람들과 실제로 사용하였다. 꽤 유용했다.
기능은 간단하다. 채팅, 말 그대로 메시지를 주고 받는 프로그램이다. 여러 명의 클라이언트들과 채팅할 수 있는 단톡이다. 한 컴퓨터에서 server를 실행하고 해당 서버 ip에 클라이언트들이 연결하는 구조이며 필요한 기능과 요소만을 구현한 기본에 충실한 프로그램이다.
------------------------------------------------------------
2022/04/07 코드 수정 및 보완했습니다
+ 가장 먼저 접속한 클라이언트에게 메시지 전송 안되는 에러 해결
+ 특정 상황에서 연결 끊어지는 에러 해결
+ 귓속말 기능 추가
+ 프로그램 정상 작동 확인
+ 코드 수정 및 보완
+ 주석 추가
서버, 클라이언트 코드
# Server
from socket import *
from threading import *
from queue import *
import sys
import datetime
#------------- 서버 세팅 -------------
HOST = '127.0.0.1' # 서버 ip 주소 .
PORT = 9190 # 사용할 포트 번호.
#------------------------------------
s=''
s+='\n -------------< 사용 방법 >-------------'
s+='\n 연결 종료 : !quit 입력 or ctrl + c '
s+='\n 참여 중인 멤버 보기 : !member 입력 '
s+='\n 귓속말 보내기 : /w [상대방이름] [메시지] '
s+='\n'
s+='\n 이 프로그램은 Jupyter Notebook에 '
s+='\n 최적화되어 있습니다. '
s+='\n --------------------------------------\n\n'
def now_time():
now = datetime.datetime.now()
time_str=now.strftime('[%H:%M] ')
return time_str
def send_func(lock):
while True:
try:
recv = received_msg_info.get()
if recv[0]=='!quit' or len(recv[0])==0:
msg=str('[SYSTEM] '+now_time()+left_member_name)+'님이 연결을 종료하였습니다.'
elif recv[0]=='!enter' or recv[0]=='!member':
now_member_msg='현재 멤버 : '
for mem in member_name_list:
if mem!='-1':
now_member_msg+='['+mem+'] '
recv[1].send(now_member_msg.encode())
if(recv[0]=='!enter'):
msg=str('[SYSTEM] '+now_time()+member_name_list[recv[2]])+'님이 입장하였습니다.'
else:
recv[1].send(now_member_msg.encode())
continue
elif recv[0].find('/w')==0: # 귓속말 기능
split_msg=recv[0].split()
if split_msg[1] in member_name_list:
msg=now_time()+'(귓속말) '+member_name_list[recv[2]] +' : '
msg+=recv[0][len(split_msg[1])+4:len(recv[0])]
idx=member_name_list.index(split_msg[1])
whisper_list[idx]=recv[2] # 귓속말을 받은 상대에게 보낸 사람 count값 저장
socket_descriptor_list[idx].send(msg.encode())
else:
msg='해당 사용자가 존재하지 않습니다.'
recv[1].send(msg.encode())
continue
elif recv[0].find('/r')==0: # 귓속말 답장 기능
whisper_receiver=whisper_list[recv[2]]
if whisper_receiver!=-1:
msg=now_time()+'(귓속말) '+member_name_list[recv[2]] +' : '
msg+=recv[0][3:len(recv[0])]
socket_descriptor_list[whisper_receiver].send(msg.encode())
whisper_list[whisper_receiver]=recv[2]
else:
msg='귓속말 대상이 존재하지 않습니다.'
recv[1].send(bytes(msg.encode()))
continue
else:
msg = str(now_time() + member_name_list[recv[2]]) + ' : ' + str(recv[0])
for conn in socket_descriptor_list:
if conn =='-1': # 연결 종료한 클라이언트 경우.
continue
elif recv[1] != conn: #자신에게는 보내지 않음.
conn.send(msg.encode())
else:
pass
if recv[0] =='!quit':
recv[1].close()
except:
pass
def recv_func(conn, count, lock):
if socket_descriptor_list[count]=='-1':
return -1
while True:
global left_member_name
data = conn.recv(1024).decode()
received_msg_info.put([data, conn, count])
if data == '!quit' or len(data)==0:
# len(data)==0 은 해당 클라이언트의 소켓 연결이 끊어진 경우에 대한 예외 처리임.
lock.acquire()
print(str(now_time()+ member_name_list[count]) + '님이 연결을 종료하였습니다.')
left_member_name=member_name_list[count] # 종료한 클라이언트 닉네임 저장.
socket_descriptor_list[count]= '-1'
for i in range(len(whisper_list)):
if whisper_list[i]==count:
whisper_list[i]=-1
member_name_list[count]='-1'
lock.release()
break
conn.close()
print(now_time()+'서버를 시작합니다')
server_sock=socket(AF_INET, SOCK_STREAM)
server_sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # Time-wait 에러 방지.
server_sock.bind((HOST, PORT))
server_sock.listen()
count = 0
socket_descriptor_list=['-1',] # 클라이언트들의 소켓 디스크립터 저장.
member_name_list=['-1',] # 클라이언트들의 닉네임 저장, 인덱스 접근 편의를 위해 0번째 요소 '-1'로 초기화.
whisper_list=[-1,]
received_msg_info = Queue()
left_member_name=''
lock=Lock()
while True:
count = count +1
conn, addr = server_sock.accept()
# conn과 addr에는 연결된 클라이언트의 정보가 저장된다.
# conn : 연결된 소켓
# addr[0] : 연결된 클라이언트의 ip 주소
# addr[1] : 연결된 클라이언트의 port 번호
while True:
client_name=conn.recv(1024).decode()
if not client_name in member_name_list:
conn.send('yes'.encode())
break
else:
conn.send('overlapped'.encode())
member_name_list.append(client_name)
socket_descriptor_list.append(conn)
whisper_list.append(-1)
print(str(now_time())+client_name+'님이 연결되었습니다. 연결 ip : '+ str(addr[0]))
if count>1:
sender = Thread(target=send_func, args=(lock,))
sender.start()
pass
else:
sender=Thread(target=send_func, args=(lock,))
sender.start()
receiver=Thread(target=recv_func, args=(conn, count, lock))
receiver.start()
server_sock.close()
# Client
from socket import *
from threading import *
import os
import datetime
import time
#--------------클라이언트 세팅 -----------------------
Host='127.0.0.1' # 서버의 IP주소를 입력하세요.
Port = 9190 # 사용할 포트 번호.
#---------------------------------------
def now_time():
now = datetime.datetime.now()
time_str=now.strftime('[%H:%M] ')
return time_str
def send_func():
while True:
send_data=input('당신 : ')
client_sock.send(send_data.encode('utf-8'))
if send_data=='!quit':
print('연결을 종료하였습니다.')
break
client_sock.close()
os._exit(1)
def recv_func():
while True:
try:
recv_data=(client_sock.recv(1024)).decode('utf-8')
if len(recv_data)==0:
print('[SYSTEM] 서버와의 연결이 끊어졌습니다.')
client_sock.close()
os._exit(1)
except Exception as e:
print('예외가 발생했습니다.', e) # 예외처리중
print('[SYSTEM] 메시지를 수신하지 못하였습니다.')
else:
print(recv_data)
pass
client_sock=socket(AF_INET, SOCK_STREAM)
try:
client_sock.connect((Host,Port))
except ConnectionRefusedError:
print('서버에 연결할 수 없습니다.')
print('1. 서버의 ip주소와 포트번호가 올바른지 확인하십시오.')
print('2. 서버 실행 여부를 확인하십시오.')
os._exit(1)
except:
print('프로그램을 정상적으로 실행할 수 없습니다. 프로그램 개발자에게 문의하세요.')
else:
print('[SYSTEM] 서버와 연결되었습니다.')
while True:
name = input('사용하실 닉네임을 입력하세요 :')
if ' ' in name:
print('공백은 입력이 불가능합니다.')
continue
client_sock.send(name.encode())
is_possible_name=client_sock.recv(1024).decode()
if is_possible_name=='yes':
print(now_time()+ '채팅방에 입장하였습니다.')
client_sock.send('!enter'.encode())
break
elif is_possible_name=='overlapped':
print('[SYSTEM] 이미 사용중인 닉네임입니다.')
elif len(client_sock.recv(1024).decode())==0:
print('[SYSTEM] 서버와의 연결이 끊어졌습니다.')
client_sock.close()
os._exit(1)
sender=Thread(target=send_func, args=())
receiver=Thread(target=recv_func, args=())
sender.start()
receiver.start()
while True:
time.sleep(1)
pass
client_sock.close()
사용 방법
간단하다.
1. 서버를 실행한 후, 클라이언트를 실행한다.
2. 서버의 ip와 port번호를 입력하면 서버와 연결되고 닉네임까지 입력하면 다른 클라이언트들과 통신이 가능하다.
(서버 역할을 할 pc를 고정시켜놨기 때문에 client 코드에서 ip와 port를 입력받는 부분은 지웠다. 클라이언트 코드에 서버의 ip와 port를 저장해놓으면 된다. 실행할 때마다 입력하는 게 귀찮다는 피드백이 있어서 수정한 부분이다.)
커맨드 입력을 통해 기능을 수행할 수 있다.
- !member를 입력하면 현재 접속 중인 멤버의 목록을 출력할 수 있고,
- !quit를 입력하면 연결을 끊을 수 있다.
- '/w name msg' 을 입력하면 해당 name을 가진 유저에게만 msg를 전송한다.
- 귓속말을 할때마다 '/w name'을 치는 게 귀찮아서 '/r'만 치면 가장 최근에 귓속말했던 상대에게 보낼 수 있도록 추가했다.
(리그 오브 레전드 게임과 동일하게 구현한 귓속말 기능이다.)
추가 기능
입장할 때에는 채팅방에서 사용할 닉네임을 입력하게 되는데 사용자들 간 구분을 위해 최소한의 두 가지 제약을 뒀다.
첫 번째로, 공백을 허용하지 않고,
두 번째로, 채팅방에 먼저 입장한 사람의 닉네임과 같을 수 없다. 즉, 중복을 불허한다.
클라이언트들이 입장하고 퇴장할 때 카톡에서 '~~님이 입장하였습니다.' 와 같은 메시지를 다른 클라이언트들에게 뿌려준다.
다수의 클라이언트 간 통신을 하기 위해 멀티쓰레딩을 활용하였다.
전체적인 기능 자체는 단순하기 때문에 특별히 구상하거나 고심할 필요는 없었지만 예외 처리하는데 생각보다 시간이 많이 걸렸다. 모든 에러를 정리하진 못했지만 겪었던 문제들을 아래 글에 정리하였다.
[파이썬] 소켓 기반 채팅 프로그램 제작하면서 겪었던 에러
1. WinError 10038 [WinError 10038] 소켓 이외의 개체에 작업을 시도했습니다. 발생 원인 : 데이터 송수신 완료 전에 소켓을 close하면 발생한다. 해결 방법 : close() 함수를 사용한 위치 확인하기. 적절한
hyobn.tistory.com
프로그램을 만들면서 공부했던 내용도 따로 정리해보았다.
[파이썬] 채팅 프로그램 공부했던 내용
파이썬 멀티스레딩(Multi Threading) from threading import * x = Thread(target=yhb, args=('A',)) x.start() target : 쓰레드가 실행할 함수를 지정. args : target으로 지정한 함수에 넘길 인자. start() 함수..
hyobn.tistory.com
파이썬에서 서버-클라이언트 간에 소켓 통신이 이루어지는 과정을 간단히 정리해보았다.
[파이썬] 소켓 통신 과정
서버와 클라이언트의 통신 과정을 간단히 정리하면 다음과 같다. 5단계에 걸쳐 진행된다. 서버를 열고 열린 서버에 클라이언트가 연결을 요청해야 하므로 서버 파일을 실행한 후 클라이언트 파
hyobn.tistory.com
후기
gui도 구현하고 대화 내용 저장 등 기능들을 많이 추가하고 싶었지만 군생활을 제 기간에 안전하게 끝마치고 싶었으며 시간이 갈수록 업무와 일과 등 환경적인 측면이 많이 바뀌어 이 프로그램의 필요성이 많이 줄어들었.. 사실 거의 없어졌다. 아쉬웠지만 꽤나 유용하게, 재밌게 잘 사용했다.
서버, 클라이언트 코드도 체계적으로 잘 짜고 싶었는데 파이썬 문법이 가물가물한 상태로 인터넷 검색이 안되는 환경에서 주로 개발을 하다 보니 계획했던 형식에서 많이 벗어나게 되고 스파게티 코드가 된 것 같다.
짧은 기간이었지만 내가 만든 프로그램을 공유하여 사람들과 직접 사용하고 피드백을 받으며 보완하고 재미있는 경험이었다.
'Project' 카테고리의 다른 글
[Spring] TipMI 프로젝트 정리 (1) | 2023.02.27 |
---|---|
[파이썬] 채팅 프로그램 제작하며 공부했던 내용 (2) | 2022.03.13 |
[파이썬] 소켓 기반 채팅 프로그램 제작하면서 겪었던 에러 (0) | 2021.09.28 |
미루고 미루고 또 미루다 해가 바뀌고 나서야 채팅 프로그램 포스팅을 하게 되었다.
작년 5월경, 자대 배치받고 환경에 슬슬 적응해나갈 때쯤 한 가지 불편한 점이 생겼다. 사무실에서 일하다 보면 다른 사무실 사람들과의 소통이 필수적이다. 하지만 각자 다른 사무실에 있고 사무실 간 거리가 있기 때문에 소통수단이 필요하다. 내선 전화가 있긴 하지만 교장 관리나 작업 등으로 한쪽이 자리를 비워 엇갈리는 경우가 정말 빈번하다. 부재중 전화가 와있어 다시 전화하면 80% 확률로 상대방이 부재중이다... 결국 상대 사무실까지 걸어가서 쪽지를 남긴다. 상당히 비효율적이고 불편했다.
그래서 채팅 프로그램을 만들었다. 소켓 통신 관련 이론 지식을 얻기 위해 도서관에 있던 '윤성우의 TCP/IP 열혈 프로그래밍' 책을 읽고 사무실에서 남는 시간에 파이썬으로 코딩을 했다. 책에서는 c언어 기반으로 설명하지만 사무실 컴퓨터로는 Jupyter Notebook만 사용할 수 있어서 파이썬 메소드를 다시 찾아보며 만들었다. 파이썬이 2년 만이라 어색했다. 어찌저찌 구현을 다 하고 배포하여 다른 사람들과 실제로 사용하였다. 꽤 유용했다.
기능은 간단하다. 채팅, 말 그대로 메시지를 주고 받는 프로그램이다. 여러 명의 클라이언트들과 채팅할 수 있는 단톡이다. 한 컴퓨터에서 server를 실행하고 해당 서버 ip에 클라이언트들이 연결하는 구조이며 필요한 기능과 요소만을 구현한 기본에 충실한 프로그램이다.
------------------------------------------------------------
2022/04/07 코드 수정 및 보완했습니다
+ 가장 먼저 접속한 클라이언트에게 메시지 전송 안되는 에러 해결
+ 특정 상황에서 연결 끊어지는 에러 해결
+ 귓속말 기능 추가
+ 프로그램 정상 작동 확인
+ 코드 수정 및 보완
+ 주석 추가
서버, 클라이언트 코드
# Server
from socket import *
from threading import *
from queue import *
import sys
import datetime
#------------- 서버 세팅 -------------
HOST = '127.0.0.1' # 서버 ip 주소 .
PORT = 9190 # 사용할 포트 번호.
#------------------------------------
s=''
s+='\n -------------< 사용 방법 >-------------'
s+='\n 연결 종료 : !quit 입력 or ctrl + c '
s+='\n 참여 중인 멤버 보기 : !member 입력 '
s+='\n 귓속말 보내기 : /w [상대방이름] [메시지] '
s+='\n'
s+='\n 이 프로그램은 Jupyter Notebook에 '
s+='\n 최적화되어 있습니다. '
s+='\n --------------------------------------\n\n'
def now_time():
now = datetime.datetime.now()
time_str=now.strftime('[%H:%M] ')
return time_str
def send_func(lock):
while True:
try:
recv = received_msg_info.get()
if recv[0]=='!quit' or len(recv[0])==0:
msg=str('[SYSTEM] '+now_time()+left_member_name)+'님이 연결을 종료하였습니다.'
elif recv[0]=='!enter' or recv[0]=='!member':
now_member_msg='현재 멤버 : '
for mem in member_name_list:
if mem!='-1':
now_member_msg+='['+mem+'] '
recv[1].send(now_member_msg.encode())
if(recv[0]=='!enter'):
msg=str('[SYSTEM] '+now_time()+member_name_list[recv[2]])+'님이 입장하였습니다.'
else:
recv[1].send(now_member_msg.encode())
continue
elif recv[0].find('/w')==0: # 귓속말 기능
split_msg=recv[0].split()
if split_msg[1] in member_name_list:
msg=now_time()+'(귓속말) '+member_name_list[recv[2]] +' : '
msg+=recv[0][len(split_msg[1])+4:len(recv[0])]
idx=member_name_list.index(split_msg[1])
whisper_list[idx]=recv[2] # 귓속말을 받은 상대에게 보낸 사람 count값 저장
socket_descriptor_list[idx].send(msg.encode())
else:
msg='해당 사용자가 존재하지 않습니다.'
recv[1].send(msg.encode())
continue
elif recv[0].find('/r')==0: # 귓속말 답장 기능
whisper_receiver=whisper_list[recv[2]]
if whisper_receiver!=-1:
msg=now_time()+'(귓속말) '+member_name_list[recv[2]] +' : '
msg+=recv[0][3:len(recv[0])]
socket_descriptor_list[whisper_receiver].send(msg.encode())
whisper_list[whisper_receiver]=recv[2]
else:
msg='귓속말 대상이 존재하지 않습니다.'
recv[1].send(bytes(msg.encode()))
continue
else:
msg = str(now_time() + member_name_list[recv[2]]) + ' : ' + str(recv[0])
for conn in socket_descriptor_list:
if conn =='-1': # 연결 종료한 클라이언트 경우.
continue
elif recv[1] != conn: #자신에게는 보내지 않음.
conn.send(msg.encode())
else:
pass
if recv[0] =='!quit':
recv[1].close()
except:
pass
def recv_func(conn, count, lock):
if socket_descriptor_list[count]=='-1':
return -1
while True:
global left_member_name
data = conn.recv(1024).decode()
received_msg_info.put([data, conn, count])
if data == '!quit' or len(data)==0:
# len(data)==0 은 해당 클라이언트의 소켓 연결이 끊어진 경우에 대한 예외 처리임.
lock.acquire()
print(str(now_time()+ member_name_list[count]) + '님이 연결을 종료하였습니다.')
left_member_name=member_name_list[count] # 종료한 클라이언트 닉네임 저장.
socket_descriptor_list[count]= '-1'
for i in range(len(whisper_list)):
if whisper_list[i]==count:
whisper_list[i]=-1
member_name_list[count]='-1'
lock.release()
break
conn.close()
print(now_time()+'서버를 시작합니다')
server_sock=socket(AF_INET, SOCK_STREAM)
server_sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # Time-wait 에러 방지.
server_sock.bind((HOST, PORT))
server_sock.listen()
count = 0
socket_descriptor_list=['-1',] # 클라이언트들의 소켓 디스크립터 저장.
member_name_list=['-1',] # 클라이언트들의 닉네임 저장, 인덱스 접근 편의를 위해 0번째 요소 '-1'로 초기화.
whisper_list=[-1,]
received_msg_info = Queue()
left_member_name=''
lock=Lock()
while True:
count = count +1
conn, addr = server_sock.accept()
# conn과 addr에는 연결된 클라이언트의 정보가 저장된다.
# conn : 연결된 소켓
# addr[0] : 연결된 클라이언트의 ip 주소
# addr[1] : 연결된 클라이언트의 port 번호
while True:
client_name=conn.recv(1024).decode()
if not client_name in member_name_list:
conn.send('yes'.encode())
break
else:
conn.send('overlapped'.encode())
member_name_list.append(client_name)
socket_descriptor_list.append(conn)
whisper_list.append(-1)
print(str(now_time())+client_name+'님이 연결되었습니다. 연결 ip : '+ str(addr[0]))
if count>1:
sender = Thread(target=send_func, args=(lock,))
sender.start()
pass
else:
sender=Thread(target=send_func, args=(lock,))
sender.start()
receiver=Thread(target=recv_func, args=(conn, count, lock))
receiver.start()
server_sock.close()
# Client
from socket import *
from threading import *
import os
import datetime
import time
#--------------클라이언트 세팅 -----------------------
Host='127.0.0.1' # 서버의 IP주소를 입력하세요.
Port = 9190 # 사용할 포트 번호.
#---------------------------------------
def now_time():
now = datetime.datetime.now()
time_str=now.strftime('[%H:%M] ')
return time_str
def send_func():
while True:
send_data=input('당신 : ')
client_sock.send(send_data.encode('utf-8'))
if send_data=='!quit':
print('연결을 종료하였습니다.')
break
client_sock.close()
os._exit(1)
def recv_func():
while True:
try:
recv_data=(client_sock.recv(1024)).decode('utf-8')
if len(recv_data)==0:
print('[SYSTEM] 서버와의 연결이 끊어졌습니다.')
client_sock.close()
os._exit(1)
except Exception as e:
print('예외가 발생했습니다.', e) # 예외처리중
print('[SYSTEM] 메시지를 수신하지 못하였습니다.')
else:
print(recv_data)
pass
client_sock=socket(AF_INET, SOCK_STREAM)
try:
client_sock.connect((Host,Port))
except ConnectionRefusedError:
print('서버에 연결할 수 없습니다.')
print('1. 서버의 ip주소와 포트번호가 올바른지 확인하십시오.')
print('2. 서버 실행 여부를 확인하십시오.')
os._exit(1)
except:
print('프로그램을 정상적으로 실행할 수 없습니다. 프로그램 개발자에게 문의하세요.')
else:
print('[SYSTEM] 서버와 연결되었습니다.')
while True:
name = input('사용하실 닉네임을 입력하세요 :')
if ' ' in name:
print('공백은 입력이 불가능합니다.')
continue
client_sock.send(name.encode())
is_possible_name=client_sock.recv(1024).decode()
if is_possible_name=='yes':
print(now_time()+ '채팅방에 입장하였습니다.')
client_sock.send('!enter'.encode())
break
elif is_possible_name=='overlapped':
print('[SYSTEM] 이미 사용중인 닉네임입니다.')
elif len(client_sock.recv(1024).decode())==0:
print('[SYSTEM] 서버와의 연결이 끊어졌습니다.')
client_sock.close()
os._exit(1)
sender=Thread(target=send_func, args=())
receiver=Thread(target=recv_func, args=())
sender.start()
receiver.start()
while True:
time.sleep(1)
pass
client_sock.close()
사용 방법
간단하다.
1. 서버를 실행한 후, 클라이언트를 실행한다.
2. 서버의 ip와 port번호를 입력하면 서버와 연결되고 닉네임까지 입력하면 다른 클라이언트들과 통신이 가능하다.
(서버 역할을 할 pc를 고정시켜놨기 때문에 client 코드에서 ip와 port를 입력받는 부분은 지웠다. 클라이언트 코드에 서버의 ip와 port를 저장해놓으면 된다. 실행할 때마다 입력하는 게 귀찮다는 피드백이 있어서 수정한 부분이다.)
커맨드 입력을 통해 기능을 수행할 수 있다.
- !member를 입력하면 현재 접속 중인 멤버의 목록을 출력할 수 있고,
- !quit를 입력하면 연결을 끊을 수 있다.
- '/w name msg' 을 입력하면 해당 name을 가진 유저에게만 msg를 전송한다.
- 귓속말을 할때마다 '/w name'을 치는 게 귀찮아서 '/r'만 치면 가장 최근에 귓속말했던 상대에게 보낼 수 있도록 추가했다.
(리그 오브 레전드 게임과 동일하게 구현한 귓속말 기능이다.)
추가 기능
입장할 때에는 채팅방에서 사용할 닉네임을 입력하게 되는데 사용자들 간 구분을 위해 최소한의 두 가지 제약을 뒀다.
첫 번째로, 공백을 허용하지 않고,
두 번째로, 채팅방에 먼저 입장한 사람의 닉네임과 같을 수 없다. 즉, 중복을 불허한다.
클라이언트들이 입장하고 퇴장할 때 카톡에서 '~~님이 입장하였습니다.' 와 같은 메시지를 다른 클라이언트들에게 뿌려준다.
다수의 클라이언트 간 통신을 하기 위해 멀티쓰레딩을 활용하였다.
전체적인 기능 자체는 단순하기 때문에 특별히 구상하거나 고심할 필요는 없었지만 예외 처리하는데 생각보다 시간이 많이 걸렸다. 모든 에러를 정리하진 못했지만 겪었던 문제들을 아래 글에 정리하였다.
[파이썬] 소켓 기반 채팅 프로그램 제작하면서 겪었던 에러
1. WinError 10038 [WinError 10038] 소켓 이외의 개체에 작업을 시도했습니다. 발생 원인 : 데이터 송수신 완료 전에 소켓을 close하면 발생한다. 해결 방법 : close() 함수를 사용한 위치 확인하기. 적절한
hyobn.tistory.com
프로그램을 만들면서 공부했던 내용도 따로 정리해보았다.
[파이썬] 채팅 프로그램 공부했던 내용
파이썬 멀티스레딩(Multi Threading) from threading import * x = Thread(target=yhb, args=('A',)) x.start() target : 쓰레드가 실행할 함수를 지정. args : target으로 지정한 함수에 넘길 인자. start() 함수..
hyobn.tistory.com
파이썬에서 서버-클라이언트 간에 소켓 통신이 이루어지는 과정을 간단히 정리해보았다.
[파이썬] 소켓 통신 과정
서버와 클라이언트의 통신 과정을 간단히 정리하면 다음과 같다. 5단계에 걸쳐 진행된다. 서버를 열고 열린 서버에 클라이언트가 연결을 요청해야 하므로 서버 파일을 실행한 후 클라이언트 파
hyobn.tistory.com
후기
gui도 구현하고 대화 내용 저장 등 기능들을 많이 추가하고 싶었지만 군생활을 제 기간에 안전하게 끝마치고 싶었으며 시간이 갈수록 업무와 일과 등 환경적인 측면이 많이 바뀌어 이 프로그램의 필요성이 많이 줄어들었.. 사실 거의 없어졌다. 아쉬웠지만 꽤나 유용하게, 재밌게 잘 사용했다.
서버, 클라이언트 코드도 체계적으로 잘 짜고 싶었는데 파이썬 문법이 가물가물한 상태로 인터넷 검색이 안되는 환경에서 주로 개발을 하다 보니 계획했던 형식에서 많이 벗어나게 되고 스파게티 코드가 된 것 같다.
짧은 기간이었지만 내가 만든 프로그램을 공유하여 사람들과 직접 사용하고 피드백을 받으며 보완하고 재미있는 경험이었다.
'Project' 카테고리의 다른 글
[Spring] TipMI 프로젝트 정리 (1) | 2023.02.27 |
---|---|
[파이썬] 채팅 프로그램 제작하며 공부했던 내용 (2) | 2022.03.13 |
[파이썬] 소켓 기반 채팅 프로그램 제작하면서 겪었던 에러 (0) | 2021.09.28 |