이 장에서는 사람들이 익명으로 참여하여 자유롭게 GIF 파일을 올릴 수 있는 채팅방을 만들어보겠습니다.
몽고디비와 몽고디비 ODM인 몽구스를 사용할 것입니다. 몽구스를 설치한 후, 몽구스 스키마를 생성하겠습니다.
채팅방 스키마와 채팅 내역 스키마만 있으면 됩니다. 사용자는 익명이니 딱히 저장할 필요가 없습니다.
사용자의 이름은 랜덤 색상으로 구별하겠습니다. 먼저 필요한 모듈을 설치합니다.
이미지를 업로드하고 서버에 HTTP 요청을 할 것이므로 multer와 axios를 같이 설치합니다.
color-hash 모듈은 조금 전에 언급했던 랜덤 색상을 구현해주는 모듈입니다.
방 제목(title), 최대 수용 인원(max), 방장(owner), 비밀번호(password), 생성 시간(createdAt) 등을 받습니다.
수용 인원은 기본적으로 10명이며, 최소 인원은 2명 이상으로 설정합니다.
채팅방에 혼자 있으면 아무 의미가 없으니까요. 비밀번호는 required 속성이 없으므로 꼭 넣지 않아도 됩니다.
비밀번호를 설정하면 비밀방으로 설정하지 않으면 공개방입니다.
채팅방 아이디(room)와 채팅을 한 사람(user), 채팅 내역(chat), GIF 이미지 주소(img), 채팅 시간(createdAt)을 저장합니다.
room 필드는 Room 스키마와 연결하여 Room 컬렉션의 ObjectId가 들어가게 됩니다.
chat 또는 img 필드에 required 속성이 없는 이유는 채팅 메시지나 GIF 이미지 중에서 하나만 저장하면 되기 때문입니다.
서버를 실행할 때 몽고디비에 바로 접속할 수있도록 서버와 몽구스를 연결합니다.
이제 채팅 앱 메인 화면과 채팅방 등록 화면을 만들어보겠습니다.
채팅뿐만 아니라 채팅방도 실시간으로 추가되거나 제거됩니다.
화면의 레이아웃을 담당하는 "layout.html" 파일을 작성하고, "views/error.html"을 수정합니다.
이제 메인 화면을 담당하는 main.html 파일을 작성
io.connect 메서드의 주소가 달라졌다는 점에 주목해주세요.
주소 뒤에 /room이 붙었습니다.
이것을 네임스페이스라고 부르며, 서버에서 /room 네임스페이스를 통해 보낸 데이터만 받을 수 있습니다.
네임스페이스를 여러 개 구분해 주고 받을 데이터를 분류할 수 있습니다.
Socket에는 미리 newRoom과 removeRoom 이벤트를 달아두었습니다.
서버에서 웹소켓으로 해당 이벤트를 발생시키면 이벤트 리스너의 콜백 함수가 실행됩니다.
콜백 함수의 내용이 길지만 특별한 것은 없습니다. 각각 테이블에 새로운 방 목록을 추가하거나 제거하는 코드입니다.
입장 버튼을 누르면, 비밀방일 경우 비밀번호를 받고 공개방일 경우 바로 입장시킵니다.
채팅방 생성화면을 담당하는 room.html 파일을 작성합니다.
채팅방 화면을 담당하는 chat.html 파일을 작성합니다.
채팅 메시지는 세 가지로 구분됩니다: 내 메시지 (mine), 시스템 메시지 (system), 남의 메시지 (other).
메시지 종류에 따라 메시지 디자인이 달라집니다.
스크립트 부분이 복잡하지만 크게 socket.io 연결 부분, socket.io 이벤트 리스너, 폼 전송 부분으로 구분됩니다.
socket.io 연결 부분을 살펴보면, io.connect 메서드의 주소가 main.html과 다릅니다.
이번에는 네임스페이스가 /chat입니다.
/room 네임스페이스로 보낸 데이터는 받을 수 없고, /chat 네임스페이스로 보낸 데이터만 받을 수 있습니다.
socket에는 join과 exit 이벤트 리스너를 연결했습니다.
join과 exit은 각각 사용자의 입장과 퇴장에 관한 데이터가 웹소켓으로 전송될 때 호출됩니다.
사용자의 입장과 퇴장을 알리는 메시지를 표시합니다. 이제 서버의 socket.is에 웹소켓 이벤트를 연결합니다.
이제 접속한 사용자에게 고유한 색상을 부여하려고 합니다.
익명 채팅이지만 자신과 남을 구별하기 위한 최소한의 사용자 정보는 필요합니다.
현재 우리가 사용할 수 있는 고유한 값은 세션 아이디 (req.sessionID)와 소켓 아이디 (socket.id)입니다.
그런데 매 번 페이지를 이동할 때마다 소켓 연결이 해제되고 다시 연결되면서 소켓 아이디가 바뀌게 됩니다.
따라서 세션 아이디를 사용합니다.
color-hash 패키지는 세션 아이디를 HEX 형식의 색상 문자열 (#12C6B8와 같은)로 바꿔주는 패키지입니다.
해시(hash)이므로 같은 세션 아이디는 항상 같은 색상 문자열로 바뀝니다.
단, 사용자가 많아질 경우에는 색상이 중복되는 문제가 생길 수 있습니다.
하지만 예제와 같은 규모의 애플리케이션에는 충분히 사용할 수 있습니다.
세션에 color 속성이 없을 때는 req.sessionID를 바탕으로 color 속성을 생성합니다.
앞으로 req.session.color를 사용자 아이디처럼 사용합니다.
app.set('io', io)로 라우터에서 io 객체를 사용할 수 있게 저장해두었고 req.app.get('io')로 접근할 수 있습니다.
처음 보는 메서드인 of가 있습니다.
Socket.IO에 네임스페이스를 부여하는 메서드입니다.
Socket.IO는 기본적으로 / 네임스페이스에 접속하지만, of 메서드를 사용하면 다른 네임스페이스를 만들어 접속할 수 있습니다.
같은 네임스페이스 간에 데이터를 전달할 수 있습니다.
현재 채팅방 생성 및 삭제에 관한 정보를 전달하는 /room과 채팅 메시지를 전달하는 /chat라는 두 개의 네임스페이스를 만들었습니다.
이렇게 네임스페이스를 구분했으므로 지정된 네임스페이스에 연결한 클라이언트들에게만 데이터를 전달합니다.
/room 네임스페이스에 이벤트 리스너를 붙여준 모습입니다. 네임스페이스마다 각각 이벤트 리스너를 붙일 수 있습니다.
/chat 네임스페이스에 붙인 이벤트 리스너입니다.
/room과 비슷하지만 네임스페이스 접속 시 socket.join 메서드가 있고, 접속해제 시 socket.leave 메서드가 있습니다.
각각 방에 들어가고 방에서 나가는 메서드입니다. 연결이 끊기면 자동으로 방에서 나가지만, 확실히 나가기 위해 추가했습니다.
Socket.IO에는 네임스페이스보다 더 세부적인 개념으로 '방'이라는 것이 있습니다.
같은 네임스페이스 안에서도 같은 방에 들어있는 소켓끼리만 데이터를 주고받을 수 있습니다.
join 메서드와 leave 메서드는 방의 아이디를 인수로 받습니다.
사용자가 브라우저에 접속할때 socket.emit('join',방아이디)를 호출하면
socket.js의 join 이벤트에서 data 메개변수로 방아이디를 전달받아 방에 접속할 것입니다.
방에서 나갈때는 socket.leave(방아이디) 메서드를 호출해야하지만, 연결이 끊기면(disconnet이벤트) 자동으로 방에서 나가므로 이예제에서는 호출하지 않았습니다.
라우터 작성
컨트롤러 작성
컨트롤러에서는 몽고디비와 웹 소켓 모두에 접근할 수 있습니다.
createRoom 컨트롤러는 채팅방을 만드는 컨트롤러입니다.
app.set('io', io)로 저장했던 io 객체를 req.app.get('io')로 가져옵니다.
io.of('/room').emit 메서드는 /room 네임스페이스에 연결한 모든 클라이언트에 데이터를 보내는 메서드이며,
GET / 라우터에 접속한 모든 클라이언트가 새로 생성된 채팅방에 대한 데이터를 받을 수 있습니다.
네임스페이스가 따로 없는 경우에는 io.emit 메서드로 모든 클라이언트에 데이터를 보낼 수 있습니다.
enterRoom 컨트롤러는 채팅방에 접속해 채팅방 화면을 렌더링하는 컨트롤러입니다.
렌더링 전에 방이 존재하는지, 비밀방일 경우에는 비밀번호가 맞는지, 허용 인원을 초과하지는 않았는지 검사합니다.
io.of('/chat').adapter.rooms에 방 목록이 들어있습니다.
io.of('chat').adapter.rooms.get(방아이디)를 하면 해당 방의 소켓 목록이 나옵니다.
이것으로 소켓의 수(size)를 세서 참가 인원의 수를 알아낼 수 있습니다.
removeRoom 컨트롤러는 채팅방을 삭제하는 컨트롤러입니다. 채팅방과 채팅 내역을 함께 삭제합니다.
이제 브라우저를 통해 방을 생성해봅시다.
gif-chat 서버를 시작하기 전에 몽고디비를 먼저 실행해야 한다는 것을 잊지 마세요.
몽고디비와 gif-chat 서버를 실행한 후 브라우저 두 개를 띄워놓고 http://localhost:8005에 접속합니다.
브라우저 두 개를 사용하는 이유는 두 명이 접속한 것과 비슷한 상황을 연출하기 위해서입니다.
'clone toy projects > node_expresss_chat' 카테고리의 다른 글
채팅 기능 구현하기 (0) | 2023.08.30 |
---|---|
미들웨어와 소켓 연결하기 (0) | 2023.08.30 |
Soket.IO 사용하기 (0) | 2023.08.30 |
ws 모듈로 웹소켓 사용하기 (0) | 2023.08.30 |
웹소켓 이해하기 (0) | 2023.08.30 |