안녕하세요. 이번 시간에는 Socket.io Server API에 대해 알아보겠습니다. 지난 시간에는 간단하게 한 사용자로부터 받은 데이터를 전체 사용자들에게 뿌려줬는데요. 이번 시간에는 그 데이터를 특정 사용자에게만 뿌린다든가, 특정 그룹에게만 뿌린다든가, 아니면 나 자신을 제외한 사람에게만 뿌리는 방법에 대해 알아보겠습니다. 채팅방이나 메신저를 만들 때 참 적합한 API인 것 같습니다. Socket.io의 사용법부터 알아보고 싶으시다면 지난 강좌부터 보시면 됩니다.
Socket.io 홈페이지의 설명이 부실하기 때문에 제가 좀 정리해보았습니다. 다음 코드에서부터 시작합니다. 서버에서 작성해줍니다.
io.on('connection', (socket) => {
socket.on('disconnect', () => {
console.log('disconnected');
});
});
잘 보시면 io와 socket이라는 변수가 있습니다. io는 socket.io 패키지를 import한 변수고, socket은 커넥션이 성공했을 때 커넥션에 대한 정보를 담고 있는 변수입니다. socket 변수를 사용해 서버에서 이벤트 리스너를 등록하면 됩니다. 몇 개의 이벤트는 이미 예약되어 있는데요. 일례로 disconnect 이벤트는 클라이언트와의 연결이 끊어졌을 때 발생합니다.
클라이언트에서는 socket.io 클라이언트 스크립트를 넣고 다음과 같이 보내고 받으면 됩니다.
var socket = io.connect('서버 주소');
socket.on('서버에서 받을 이벤트명', function(데이터) {
// 받은 데이터 처리
socket.emit('서버로 보낼 이벤트명', 데이터);
});
설정
설정은 io.configure()
나 io.set()
이 아닙니다. 많은 인터넷 블로그에서 이렇게 설명을 하는데 이것은 예전 API입니다. 처음에 socket.io에 주소를 연결할 때 설정도 함께 연결하면 됩니다. 설정 가능한 부분은 공식 홈페이지에 잘 나와 있습니다.
io('주소', { 설정명: '설정값' }); // 클라이언트 설정
SocketIo(server, { 설정명: '설정값' }); // 서버 설정
Socket.io는 웹소켓말고 폴링도 같이 지원하는데 순수한 웹소켓만 사용하고 싶다면 클라이언트에서 다음과 같이 합니다.
io('주소', { transports: ['websocket'] });
Socket.io는 익스프레스처럼 미들웨어를 사용할 수 있습니다. 다음은 Socket.io와 express-session과 연동하는 예시입니다. socket 객체에는 req와 res 객체가 다 들어 있습니다.
io.use((socket, next) => {
require('express-session')(옵션)(socket.request, socket.request.res, next);
});
여기서부터는 서버 상의 API입니다. 클라이언트는 socket.on
과 socket.emit
만 있으면 됩니다.
전체
전체라고 함은 클라이언트에서 socket.on('이벤트명', 콜백)
를 등록해 해당 이벤트가 오기를 기다리는 페이지 전부를 의미합니다. (사실 이벤트리스너를 등록하지 않아도 연결된 모든 클라이언트에 전달됩니다. 다만 이벤트리스너가 없기 때문에 무시됩니다) 전체에게 메세지를 보내는 방법은 지난 시간에 이미 했습니다. 서버에 요청을 보냈던 자신에게도 메세지가 다시 온다는 특징이 있습니다. 제 홈페이지의 예를 들자면, 새로운 포스트를 등록하거나 수정했을 경우 모든 사람에게 포스팅의 정보가 전송됩니다.
io.emit('이벤트명', 데이터);
또는
io.sockets.emit('이벤트명', 데이터);
해도 됩니다.
네임스페이스
Socket.io에는 네임스페이스라는 것도 존재합니다. 전체에게 메시지를 보내는 것은 사실 / 네임스페이스에게 메시지를 보내는 것입니다. 네임스페이스를 바꾸려면 서버에서는 of 메서드를 사용합니다.
const chat = io.of('/chat');
chat.on('connection', (socket) => {
...
});
클라이언트에서는 연결 시 주소를 바꿔줘야합니다. 주소 뒤에 네임스페이스를 붙여줍니다.
io.connect('주소/chat', 설정);
위와 같이 네임스페이스를 바꾸고 네임스페이스별로 따로 이벤트를 연결할 수 있습니다. 이렇게 하는 이유는 필요한 사람들에게만 메시지를 보내기 위함입니다.
예를 들어 기본값인 / 네임스페이스를 사용한다고 생각해봅시다. 채팅방 목록이 새로 갱신되든, 채팅 메시지가 오가든 모든 사람들에게 모든 이벤트가 전송됩니다. 채팅방 안에 들어있는 사람은 새로운 채팅 메시지만 받으면 되지 채팅방 목록이 갱신되는 것까지는 알 필요 없습니다. 대기실에 있는 사람들도 채팅방 안의 메시지가 오고 가는 것을 알 필요가 없고요. 따라서 네임스페이스를 분리하여 같은 네임스페이스에 연결된 사람들끼리만 웹소켓 통신을 하는 것입니다.
io.of(네임스페이스).emit('이벤트명', 데이터);
위의 방식으로 같은 네임스페이스의 사람들에게 데이터를 보낼 수 있습니다.
나를 제외한 전체
나를 제외한 전체에게 메세지를 보내는 방법입니다. io를 사용하지 않고 socket 안에 있는 broadcast 객체를 사용합니다. 제 홈페이지에서는 댓글을 달 때 자신을 제외한 모두에게 실시간으로 전송됩니다. 자신의 댓글은 서버를 거치지 않고 바로 클라이언트 상에서 처리됩니다.
socket.broadcast.emit('이벤트명', 데이터);
특정인
특정 한 사람에게 메세지를 보낼 수 있습니다. 귓속말같은 것이나 1대1 채팅을 구현할 때 사용하면 좋겠죠?
io.to(소켓아이디).emit('이벤트명' 데이터);
sockets 객체를 함수처럼 사용하여 인자로 소켓아이디를 제공하면 그 소켓아이디를 가진 사람에게만 메세지가 전달됩니다. 자신의 소켓아이디는 socket.id
를 통해 얻을 수 있습니다. 다른 사람에게 메세지를 보내려면 그 사람의 소켓 아이디가 필요한데 이 때는 데이터에 자신의 소켓 아이디를 넣어 전달해줘서 다른 사람들이 자신에게 메시지를 보낼 수 있도록 허용하면 됩니다.
특정 그룹
특정 그룹에게도 메세지를 보낼 수 있습니다. 대신 사람들을 그 그룹에 먼저 들어야가 합니다. 네임스페이스보다는 작은 개념입니다. 네임스페이스 안에 그룹을 만드는 셈입니다.
socket.join(방의 아이디); // 그룹에 들어가기
socket.leave(방의 아이디); // 그룹 떠나기
방의 아이디는 단순히 문자열일 뿐이라서 아무 거나 생성해주면 됩니다. 예를 들면 방의 아이디가 'room01'일 경우 두 명 이상이 'room01'에 join하면 그룹 채팅을 할 수 있습니다. 메세지는 그룹 전체에게 보내는 방식과 나를 제외한 그룹 전체에게 보내는 두 가지 방식이 있습니다.
io.to(방의 아이디).emit('이벤트명', 데이터); // 그룹 전체
socket.broadcast.to(방의 아이디).emit('이벤트명', 데이터); // 나를 제외한 그룹 전체
이제 좀 정리가 된 것 같네요. 응용하는 것은 여러분의 자유입니다.
그룹의 목록과 그룹 안의 소켓들을 확인하는 방법은 다음과 같습니다.
io.adapter.rooms
io.of(네임스페이스).adapter.rooms
socket.adapter.rooms
한가지 팁을 드리자면, 위의 방법대로 참여 인원 수나 방의 수를 구하는 것이 불안정하기 때문에 서버 상에서 배열을 만들어 방의 아이디를 모아두는 것이 편할 것 같습니다. 그리고 방 안에는 참여한 사람들의 소켓 아이디를 넣어두고요.
[
{ _id: 'room01', members: ['zero_id', 'aero_id']},
{ _id: 'room02', members: ['nero_id', 'hero_id']},
]
이런 식으로요. 서버가 꺼지지 않는 이상 저 배열은 데이터베이스처럼 동작할 겁니다. 이 방식은 서버의 메모리에 저장하는 것이기 때문에 날아갈 위험이 있습니다. 나중에는 저 부분을 데이터베이스와 연결하여 방의 목록과 멤버를 영구적으로 저장하면 됩니다.
이제 위의 API를 사용해서 채팅방을 만들어보세요!