게시글

강좌13 - NodeJS - 4년 전 등록 / 3년 전 수정

Socket.io Server API

설정하기, 전체 채팅, 1대1 채팅, 그룹 채팅
Node.js교과서 책이 출간되었습니다. 이 포스팅보다는 책이나 동영상 강좌를 보시는 것을 추천합니다.

안녕하세요. 이번 시간에는 Socket.io Server API에 대해 알아보겠습니다. 지난 시간에는 간단하게 한 사용자로부터 받은 데이터를 전체 사용자들에게 뿌려줬는데요. 이번 시간에는 그 데이터를 특정 사용자에게만 뿌린다든가, 특정 그룹에게만 뿌린다든가, 아니면 나 자신을 제외한 사람에게만 뿌리는 방법에 대해 알아보겠습니다. 채팅방이나 메신저를 만들 때 참 적합한 API인 것 같습니다. Socket.io의 사용법부터 알아보고 싶으시다면 지난 강좌부터 보시면 됩니다.

Socket.io 홈페이지의 설명이 부실하기 때문에 제가 좀 정리해보았습니다. 다음 코드에서부터 시작합니다. 서버에서 작성해줍니다.

io.on('connection', (socket) => {
  socket.on('disconnect', () => {
    console.log('disconnected');
  });
});

잘 보시면 iosocket이라는 변수가 있습니다. 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.onsocket.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를 사용해서 채팅방을 만들어보세요!

조회수:
0
목록
투표로 게시글에 관해 피드백을 해주시면 게시글 수정 시 반영됩니다. 오류가 있다면 어떤 부분에 오류가 있는지도 알려주세요! 잘못된 정보가 퍼져나가지 않도록 도와주세요.
Copyright 2016- . 무단 전재 및 재배포 금지. 출처 표기 시 인용 가능.

댓글

17개의 댓글이 있습니다.
2달 전
안녕하세요? socket.on을 한 번에 여러 개 연결해서 사용해도 문제가 없는지 궁금해서 질문드립니다. 예를 들어서,

socket
.on('a', () => {})
.on('b', () => {})
.on('c', () => {});

이런 식으로 다른 역할을 하는 이벤트리스너들을 붙여놔도 될까요?
2달 전
socket.on()이 socket을 return한다면 가능합니다.
3달 전
안녕하세요
답변감사드립니다.
전체 소스 올려봅니다.
환경은 node server.js
구글 웹서버 200ok 사용 했습니다.
join은 했습니다.

server.js
const io = require("socket.io");
const server = io.listen(3000);

/**
* 문자열이 빈 문자열인지 체크하여 결과값을 리턴한다.
* @param str : 체크할 문자열
*/
function isEmpty(str){

if(typeof str == "undefined" || str == null || str == "")
return true;
else
return false ;
}

server.on("connection", function(socket) {
console.log("user connected");
socket.emit("welcome", "welcome man");

socket.on('joinRoomS',(data) => {
//socket.leave(data);
//server.sockets.to("123").emit('joinRoomC', "123", "111");
socket.to("123").emit('joinRoomC', "123", "111");
console.log("joinRoomS:"+data);
});

socket.on('joinRoom2', (roomName, name) => {
socket.join(roomName, () => {
console.log(name + ' join a ' + roomName);
//roomNameG = roomName;
socket.to(roomName).emit('joinRoom2', roomName, name);
});
});

socket.on('setNameS', (name) => {
//if (typeof name == "undefined" || name == null || name == ""){
if (isEmpty(name)){
socket.name = 'Guest' + cnt;
cnt++;
//io.to(socket.id).emit('setNameC', socket.name);
} else {
socket.name = name;
}
//io.sockets.in(socket.id).emit('setNameC', socket.name);
//io.to(socket.id).emit('setNameC', socket.name);
//socket.to(socket.id).emit('setNameC', socket.name);
socket.emit('setNameC', socket.name);
console.log('user setName ' + socket.name);
});
});

클라이언트
<html>
<head>
<title>Socket io client</title>
<script src="http://localhost:3000/socket.io/socket.io.js"></script>
<script>
var socket = io("ws://localhost:3000");
// use your socket
socket.on("welcome", (message) => {
// do something with the message.
console.log("welcome~~:"+message);
});
socket.emit('setNameS', "111");
//socket.emit('joinRoomS', "123");
socket.emit('joinRoom2', "123","111");

socket.on("setNameC", (message) => {
// do something with the message.
console.log("setNameC:"+message);
});
socket.on("joonRoomC", (roomName,name) => {
// do something with the message.
console.log("joonRoomC:"+roomName+" Name:"+name);
});
socket.on("joinRoom2", (roomName,name) => {
// do something with the message.
console.log("joonRoom2:"+roomName+" Name:"+name);
});
</script>
</head>
<body>
</body>
</html>
3달 전
안녕하세요.
socket.io 만 단독으로 쓰고 싶은데 io.to(방의 아이디).emit('이벤트명', 데이터); // 그룹 전체 으로 보내니 클라에 전달이 안되네요.socket.emit("welcome", "welcome man"); 으로 전체 msg는 발송 됩니다.
해결책이 있을까요?
3달 전
socket.to(방아이디)로는 되나요? 그리고 서버쪽에서 방에 들어갈 사람들 socket.join(방아이디)하셨죠?
4달 전
제로초님! 보통 소켓 아이디는 클라이언트와 연결되어있으면 영구적인가요? 즉 서버가 꺼졋다가 켜저도 데이터베이스에 그 유저 소켓 아이디를 저장시켜서 계속 쓸수있는가요 ? 아니면 서버가 꺼졋다 켜지면 재발급된 소켓 아이디를 다시 유저디비에 할당을 해줘야하는것인가요 ??
4달 전
위에 댓글 작성자입니다! 윗 댓글은 무시하셔도됩니다. jwt로 사용자 인증을 하는데 socketId를 jwt의 payload로 할당을 해서 식별하는방법도 괜찮을까요 ??
4달 전
넣으셔도 됩니다만, socket id는 연결이 끊어졌다 다시 맺어질때마다 바뀝니다.
4달 전
감사합니다 ㅎ.ㅎ
4달 전
안녕하세요 제로초님. 포스팅 잘봤습니다,! 채팅 구현 앱을 제작하려고하는데 서버사이드 namespace 부분에서 햇갈려서 질문남깁니다. 클라에서 임의의 난수(중복되지 않는값) 으로 네임스페이스를 주면 서버사이드에서 여러개의 채팅방을 만들 수 있는건가요 ?? 그렇게 된다면 서버사이드에서는 모든 네임스페이스와 대화기록 로그등을 디비에 저장해주는게 올바른 구현일까요 ?? 항상 감사합니다.
4달 전
채팅방은 네임스페이스보다는 room으로 만드는 게 좋습니다. room이 더 아래 단계입니다. 네임스페이스는 room들의 그룹이라고 생각하시고요. 그리고 채팅방 대화기록 이런 건 모두 디비에 저장하셔야 합니다.
4달 전
제가 구현하고자 하는것이 대학생 커뮤니티 기반 채팅방을 원하고있습니다. 그래서 제가 원래 생각한 대로라면 네임스페이스에 학교 코드를 정의해놓고 room에 학과별 채팅방을 생각해 두고 있습니다.. 이렇게 계층적으로 나뉜 것들도 네임스페이스 없이 room으로 해도 괜찮을까요 ??! (+ 빠른답변 해주셔서 감사드립니다 ㅠ.ㅠ)
4달 전
네임스페이스가 필수는 아닙니다. 네임스페이스가 없으면 기본 네임스페이스 아래로 들어가고요. 어차피 DB에서만 방 번호와 학과 학교 등을 연결해두시면 됩니다.
5달 전
zerocho 글 잘봤습니다. 혹시 socketio를 이용한 친구추가기능같은거는 어떻게 해야하나요?? 친구 추가 기능은 먼가 같은 방의 채팅을 하는게 아닌 다른 방과 다른 방에서 메시전를 보내는 느낌인데 어떻게 해야할지 감이 안오네요.
5달 전
친구추가는 socket.io라기보다는 db 기능입니다. db에서 친구 관계를 저장하시면 됩니다. socket.io를 활용해서는 실시간으로 친구요청이 왔다는 거 보내주는 이벤트 정도 만드시면 됩니다.
5달 전
그러면 친구 추가 페이지에 친구추가를 socketio를 보내면 친 친구추가요청을 수락을 받으면 그 친구추가된 사용자를 db에 넣으면 되는건가요??
5달 전
네네 사용자 테이블간에 다대다 관계를 설정해주시면 됩니다.
5달 전
알려주셔서 감사합니다!
5달 전
또 질문해서 죄송합니다. 방을 만들때 그냥 게시판 형태로 방을 만들어도되나요?? 하나하나 id를 부여해서 데이터베이스 수만큼 방을 만들면 "데이터베이스 수[방에 들어간 id]" 이런식으로해서 방이 맞는 사람만 해서 대화를 나눠도되는건가요??
5달 전
DB에 저장하는 형식은 자유입니다. 방이 맞는 사람끼리 대화하는 건 socket.join(방 아이디) 이렇게 해서 대화하시면 됩니다. DB랑은 별개입니다. DB에 저장하는 건 기존 대화 내역 이런걸 저장하는 겁니다.
5달 전
그럼 데이터베이스에 대화기록, 이미지, 파일등을 insert해서 관리를 한다는거군요
5달 전
안녕하세요. 제로초님 배열을 데이터베이스랑 연동하시라는 말씀이 배열에 넣은거처럼 데이터베이스에 넣으라는 말씀이신가요??
일 년 전
안녕하세요!! 공부하는데 많은 도움 받고있습니다! 감사합니다!
현재 웹소켓을 이용해 채팅하는 프로그램을 만들고 있는데 vue를 이용해 클라이언트 서버를 실행하고, node를 이용해 api서버를 실행하는 2개의 서버를 실행하는 구조입니다.
이런 구조라면 소켓을 어떻게 사용해야할지 감이 잡히지 않아서 질문드립니다!
일 년 전
그냥 프론트에서 서버쪽 ws 주소를 입력해서 연결하시면 됩니다. ws://노드api서버웹소켓주소
일 년 전
채팅방을 만들경우 채팅방에 제어중에 얼리기 녹이기 같은 기능을 구현하려면 server.js에서 작업을 해줘야 하나요? 아니면 별도의 명령어가 존재하나요?
일 년 전
그런 기능은 없어서 직접 구현하셔야 합니다.
2년 전
nodejs 클러스터상황(pm2나) 에서 socket.io 이벤트명이 중복일때는 동일한걸로 처리되나요? 각각으로 처리되나요
2년 전
각각 처리됩니다.
2년 전
특정인한테 보내는 client 측 코드는 어떻게 짜나요?
users 라는 object에 user 들을 저장하고
io.to(users[name]).emit('whisper', {msg: message, nick: socket.id});
이런 식으로 보내고 있습니다.
2년 전
io.to(socket.id).emit 이런 식으로 하셔야 합니다. 특정인의 소켓 아이디를 to에 넣어주면 됩니다.
2년 전
connection 에서 연결된 socket.id 를 users 에 넣는데 client 코드를 어떻게 짜야 할지 모르겠어요 .
socket.on('whisper', function (data) {}) 단순히 이렇게 받았는데 메세지가 자꾸 안와서...
2년 전
클라이언트는 그 정도로 충분하고요. 서버에서 보낼 때가 문제인 것으로 보이네요. users[name] 여기에 socket.id를 넣어두셔야 합니다(참고로 새로고침하거나 할 때마다 socket.id가 달라지므로 매번 users[name] 수정이 필요합니다)
2년 전
해결했습니다... 위에서 socket.id 를 name 으로 덮어쓰고 있었네요. 오늘 10시에 보러가겠습니다!! 감사합니다!!!
2년 전
노드js인가요
2년 전
네 그렇습니다~
2년 전
오브젝트를 보내주고 받을수는 없나요? var img = new Image(); img라는 오브젝트를 보내주고 받고 싶은데 여러 방법으로 시도해봤는데 그냥 단지 오브젝트라는 정보만 보내주고 받네요..
2년 전
네 오브젝트는 못 보내고요. 컨텐츠를 보내야합니다. 문자열로 바꿔서요
3년 전
음..사용자가 방에 들어왔는지 어떻게아나요... 채팅방을 만들어서 방안에 있는사람에게 보낼려고하는데 사용자의 socket.Id가 room에 들어왔는지 체크하는 방법 알려주세요,....
3년 전
socket.set('room',function(){})이게 없어졋다고 하는데 대신할 방법 없나요?
3년 전
제가 현재 이 부분 책을 쓰고 있는데 한 번 확인해보겠습니다.
3년 전
socket.adapter.rooms[방 아이디] 안에 사용자 소켓 목록들이 들어 있습니다.
3년 전
항상 잘보고 있습니다.
socket.js에서 DB의 특정값이 바뀐것을 어떻게 detecting하나요? 또는 외부 서버에서 받은 post data를 브라우저에게 알려줄려면 어떻게 해줘야하나요? 대화나 댓글이 아니라.. 예를들면

외부서버가 post or get으로 1000이라는 숫자를 넘겨준다면 index.js(route)에서 그 값을 받을 것이고, socket.js에게 이 값을 전달해줘야지 emit을 해줄 수 있을텐데 초보라서.. 잘 이해가 안됩니다. 하루종일 이리저리 해봤지만 실패해서 이렇게 질문드립니다...
3년 전
socket을 라우터로 import(또는 require)해오세요.
3년 전
app.js 에서
socketio = require('socket.io');
socket = require('socket.js');

server = http.createServer(app);
var io = new sokcetio(server);
socket(io);

하는데

index.js에서 import or require한다면
server를 하나 더 만들어야하는거 아닌가요?
3년 전
일단 socket.js가 뭔가요? 직접 만드신 건가요? Server를 리콰이어하는게 아니라 모듈로 만든 후 소켓 객체를 리콰이어하시면 됩니다. io나 sockets요
3년 전
이전글 socket.io로 실시간업데이트 포스팅에서 socket.js 만드신것 그대로 만들었습니다. 그래서 app(이전 포스팅에서는 server).js에서 위 작업을 했습니다.
3년 전
app.set('socket', socket)으로 익스프레스랑 연결한 후 라우터에서 req.app.get('socket')으로 꺼낼 수도 있어요
3년 전
저도 지금 이문제로 골머리중. app.get으로 꺼내면 io.emit은 날라 가는데 io.to('socketid') 는 안날라감. ㅠ.ㅠ. 왜 안날라 가는지 모르겠음.
3년 전
올바른 socketId가 맞나요? 네임스페이스도 살펴보세요~
3년 전
아... socket.id가 서버가 계속 다시 시작되면서 바뀌었었네요. 감사합니다.
3년 전
1