HOST = '' PORT = 9009 lock = threading.Lock() # syncronized 동기화 진행하는 스레드 생성, 즉, 단 하나의 프로세스나, 스레드만 접근해서 데이터를 수정함!
classUserManager:# 사용자관리 및 채팅 메세지 전송을 담당하는 클래스 # ① 채팅 서버로 입장한 사용자의 등록 # ② 채팅을 종료하는 사용자의 퇴장 관리 # ③ 사용자가 입장하고 퇴장하는 관리 # ④ 사용자가 입력한 메세지를 채팅 서버에 접속한 모두에게 전송
def__init__(self): self.users = {} # 사용자의 등록 정보를 담을 사전 {사용자 이름:(소켓,주소),...}
defaddUser(self, username, conn, addr):# 사용자 ID를 self.users에 추가하는 함수 if username in self.users: # 이미 등록된 사용자라면 conn.send('이미 등록된 사용자입니다.\n'.encode()) returnNone
# 새로운 사용자를 등록함 lock.acquire() # 스레드 동기화를 막기위한 락 self.users[username] = (conn, addr) lock.release() # 업데이트 후 락 해제
self.sendMessageToAll('[%s]님이 입장했습니다.' % username) print('+++ 대화 참여자 수 [%d]' % len(self.users))
return username
defremoveUser(self, username):# 사용자를 제거하는 함수 if username notin self.users: return
lock.acquire() del self.users[username] lock.release()
self.sendMessageToAll('[%s]님이 퇴장했습니다.' % username) print('--- 대화 참여자 수 [%d]' % len(self.users))
defmessageHandler(self, username, msg):# 전송한 msg를 처리하는 부분 if msg[0] != '/': # 보낸 메세지의 첫문자가 '/'가 아니면 self.sendMessageToAll('[%s] %s' % (username, msg)) return
if msg.strip() == '/quit': # 보낸 메세지가 'quit'이면 self.removeUser(username) return-1
defsendMessageToAll(self, msg): for conn, addr in self.users.values(): conn.send(msg.encode())
스레딩을 위한 코드가 보이지 않는다.서버와 클라이언트가 있을때 서버는 스레드로 관리해야하는것아닌가(병렬처리..)
스레딩을 하기위해 ChatingServer(socketserver.ThreadingMixIn, socketserver.TCPServer) 상속을 사용하였다.메서드를 재정의하므로, 믹스인 클래스가 먼저 옵니다. 다양한 어트리뷰트를 설정하면 하부 서버 메커니즘의 동작도 변경됩니다.
웹 브라우져에서 위에 작성한 html를 실행시키면 javascript에서 python websocket서버로 접속을 합니다. 그리고 hello와 test의 메시지를 작성해서 보냈는데 server측에서는 hello와 test의 메시지를 받아서 콘솔에 출력을 했고 브라우저에서는 echo : 가 붙은 메시지가 표시가 되었습니다.
<!DOCTYPE html> <html> <head> <metahttp-equiv="Content-Type"content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <formonsubmit="return false;"> <!-- 서버로 메시지를 보낼 텍스트 박스 --> <inputid="textMessage"type="text"> <!-- 전송 버튼 --> <inputonclick="sendMessage()"value="Send"type="button"> <!-- 접속 종료 버튼 --> <inputonclick="disconnect()"value="Disconnect"type="button"> </form> <br /> <!-- 출력 area --> <textareaid="messageTextArea"rows="10"cols="50"></textarea> <scripttype="text/javascript"> // 웹 서버를 접속한다. var webSocket = new WebSocket("ws://localhost:9998"); // 웹 서버와의 통신을 주고 받은 결과를 출력할 오브젝트를 가져옵니다. var messageTextArea = document.getElementById("messageTextArea"); // 소켓 접속이 되면 호출되는 함수 webSocket.onopen = function(message){ messageTextArea.value += "Server connect...\n"; }; // 소켓 접속이 끝나면 호출되는 함수 webSocket.onclose = function(message){ messageTextArea.value += "Server Disconnect...\n"; }; // 소켓 통신 중에 에러가 발생되면 호출되는 함수 webSocket.onerror = function(message){ messageTextArea.value += "error...\n"; }; // 소켓 서버로 부터 메시지가 오면 호출되는 함수. webSocket.onmessage = function(message){ // 출력 area에 메시지를 표시한다. messageTextArea.value += "Recieve From Server => "+message.data+"\n"; }; // 서버로 메시지를 전송하는 함수 functionsendMessage(){ var message = document.getElementById("textMessage"); messageTextArea.value += "Send to Server => "+message.value+"\n"; //웹소켓으로 textMessage객체의 값을 보낸다. webSocket.send(message.value); //textMessage객체의 값 초기화 message.value = ""; } functiondisconnect(){ webSocket.close(); } </script> </body> </html>
python파일로 client구성
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
import asyncio # 웹 소켓 모듈을 선언한다. import websockets
asyncdefmy_connect(): # 웹 소켓에 접속을 합니다. asyncwith websockets.connect("ws://localhost:9998") as websocket: # 10번을 반복하면서 웹 소켓 서버로 메시지를 전송합니다. for i in range(1,10): await websocket.send("hello socket!!") # 웹 소켓 서버로 부터 메시지가 오면 콘솔에 출력합니다. data = await websocket.recv() print(data) # 비동기로 서버에 접속한다. asyncio.get_event_loop().run_until_complete(my_connect())
Nginx websocket wss:// 적용하기
https에서는 wss가 필수이므로 반드시 ssl적용이 필요했다.
upstream부터 server { }를 따로 설정해줄수있지만 1.1.3버전부터 nginx에서는 이미 websocket에 대해 따로 지원을 해준다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
upstream pythonchattingserver { server chattingserver:7777; }
server { ##아래와 같은 양식으로 추가 location /websocket/ { proxy_pass http://pythonchattingserver/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 86400; } }
1 2
// js로 요청하는 방법 var webSocket = new WebSocket("wss://chatting.lostcatbox.com/websocket/");
오류 해결
모든 브라우저에서 예상대로 종료해주기를 바라는 건 욕심이였다. 모바일 크롬에서만 하면 모두가 채팅이 불가능하게 되었다…
왜그랬을까?
server의 소캣은 user에 기록되어있었지만 실제로는 연결이 끊기 소캣에 대해서는 send()를 해도 오류가 발생하였다.
이를 해결하기위해 try, except 구문을 통해 메세지를 보낼때 만약 전체메시지 전송도중에 오류가 발생하면 self.removeUser(username)함수를 통해 그 등록된 유저와 소캣을 제거하고, pass를 하여 메세지를 모두에게 보내는 함수의 for문이 끊기지 않도록 하여 해결하였다
send() raises a ConnectionClosed exception when the client disconnects, which breaks out of the while True loop.