...
Socket.IO
node.js에서는 많은 웹 소켓 구현체가 있다. socket.io와 ws가 있다.
ws는 기본에 충실한 느낌이고, socket.io는 기본도 기본이지만 뭔가 색다른 기능을 제공한다.
예를 들어 room이라는 기능을 이용해 여러 개의 채팅방을 만들 수 있고 소켓에 연결된 전체 클라이언트에게 broadcast를 보낼 수 있다거나, room 별로 broadcast를 보낼 수 있다.
이런 부분들을 좀 더 쉽고 직관적으로 제공하는 것이 특징이다.
거기다가, HTML5 WebSocket은 매우 유용한 기술이지만 오래된 브라우저의 경우 지원하지 않는 경우가 있다.
브라우저 간 호환이나 이전 버전 호환을 고려하여 Node.js를 위한 강력한 Cross-platform WebSocket API인 Socket.io를 사용하는 것이 바람직하다.
socket.io는 채팅방에 특화된 라이브러리라, 만일 양방향 통신 기술이 필요한데 채팅방이 아닌 다른 서비스를 구현한다고 한다면 socket.io는 맞지 않을수 있다.
> npm i socket.io # 소켓IO 설치
Socket IO 이벤트 통신
소켓IO 의 메소드의 특징은 클라이언트에서 발생하는 이벤트는 개발자가 임의로 설정할 수 있다는 점이다.
이벤트는 문자열로 지정하며 직접 이벤트를 발생시킬 수 있다. 전반적으로 노드 이벤트 핸들러 방식을 따르고 있다고 보면 된다.
// 해당 이벤트를 받고 콜백함수를 실행
socket.on('받을 이벤트 명', (msg) => {
})
// 이벤트 명을 지정하고 메세지를 보낸다.
socket.emit('전송할 이벤트 명', msg)
이런식으로 메세지 마다 고유한 이벤트를 등록해 구별해서 송수신하면,
채팅방에서 '귓속말' 기능처럼 특정 어느 사람한테만 메세지를 송신한다던지 ..등 다양한 통신 기능을 구현할 수 있다.
Socket IO 송수신 메소드
소켓 메세지 수신
// 접속된 모든 클라이언트에게 메시지를 전송한다
io.emit('event_name', msg);
// 메시지를 전송한 클라이언트에게만 메시지를 전송한다
socket.emit('event_name', msg);
// 메시지를 전송한 클라이언트를 제외한 모든 클라이언트에게 메시지를 전송한다
socket.broadcast.emit('event_name', msg);
// 특정 클라이언트에게만 메시지를 전송한다
io.to(id).emit('event_name', data);
소켓 메세지 송신
// 클라이언트와 소켓IO 연결됬는지 안됬는지 이벤트 실행. (채팅방에 누가 입장하였습니다/퇴장하였습니다 )
io.on('connection/disconnection', (socket) => {
});
// 클라이언트에서 지정한 이벤트가 emit되면 수신 발생
socket.on('event_name', (data) => {
});
Socket.IO 통신 구현해보기
소켓 서버 설정
1) app서버 생성
const app = require("express")();
const server = app.listen(8005, ()=>{ });
2) 소켓IO에 서버 정보 넘겨주고 구동
const SocketIO = require('socket.io');
// 서버 연결, path는 프론트와 일치시켜준다.
const io = SocketIO(server, { path: '/socket.io' });
* path옵션: 이 경로를 통해 통신을 수행하며, 생략시 디폴트 값은 /socket.io 로 지정된다.
3) 소켓 연결에 성공하면 이벤트 통신
//* 웹소켓 연결 시
io.on('connection', (socket) => {
//* 연결 종료 시
socket.on('disconnect', () => {
console.log('클라이언트 접속 해제', ip, socket.id);
clearInterval(socket.interval);
});
//* 에러 시
socket.on('error', (error) => {
console.error(error);
});
//* 클라이언트로부터 메시지 수신
socket.on('reply', (data) => { // reply라는 이벤트로 송신오면 메세지가 data인수에 담김
console.log(data);
});
//* 클라이언트로 메세지 송신
socket.emit('news', 'Hello Socket.IO'); // news라는 이벤트로 문자열을 포함하여 송신
});
소켓 클라이언트 설정
1) socket.io 모듈 스크립트 로드
<script src="/socket.io/socket.io.js"></script>
socket.io 모듈은 내부적으로 "루트/socket.io" 경로에 socket.io.js 파일을 자동으로 등록해둔다.
결과적으로 위 코드는 socket.io모듈이 자동으로 생성해둔 http://127.0.0.1:8005/socket.io/socket.io.js 에 접근하여 JS 스크립트를 불러오게 된다.
그리고 이 JS스크립트에서 소켓IO 객체를 뽑아 클라이언트에서도 소켓 통신을 할수 있게 되는 것이다.
2) 소켓IO 객체 생성 및 연결
<!-- 익스프레스 서버와 소켓 서버가 연결이 되면, 소켓IO 서버에서 js파일을 넣어준다 -->
<script src="/socket.io/socket.io.js"></script>
<script>
// 위의 socket.io.js에서 뽑아 쓴다.
const socket = io.connect('http://localhost:8005', { // ws:// 를 안쓰고 http를 쓴다
path: '/socket.io', // 서버 path와 일치시켜준다
transports: ['websocket']
});
</script>
연결할 서버 경로 및 옵션을 설정해준다.
- path 옵션
: 이 경로를 통해 각종 통신을 수행하며, node.js상에서 설정한 path와 동일하게 지정해야한다. - transports 옵션
: socket.io는 처음에 polling 연결을 시도하고, 웹소켓이 지원되는 브라우저인 경우, ws통신으로 이행한다.
: 처음부터 ws로 통신하고자 할 경우, transports 옵션 값을 ['websocket']으로 추가 설정해주면 된다.
3) 송수신 이벤트 처리 (연결/종료/에러/데이터 수신 등)
<!-- 익스프레스 서버와 소켓 서버가 연결이 되면, 소켓IO 서버에서 js파일을 넣어준다 -->
<script src="/socket.io/socket.io.js"></script>
<script>
// 위의 socket.io.js에서 뽑아 쓴다.
const socket = io.connect('http://localhost:8005', { // ws:// 를 안쓰고 http를 쓴다
path: '/socket.io', // 서버 path와 일치시켜준다
// transports: ['websocket']
});
// 서버로부터 메세지 수신
socket.on('news', function (data) {
console.log(data);
// 서버에게 메세지 송신
socket.emit('reply', 'Hello Node.JS');
});
</script>
소켓IO 전체 코드
기본적인 express.js 서버나 나머지 라우터 코드는 이전 포스팅 참조하길 바란다.
서버 (socket.js)
const SocketIO = require('socket.io');
module.exports = (server) => {
// 서버 연결, path는 프론트와 일치시켜준다.
const io = SocketIO(server, { path: '/socket.io' });
//* 웹소켓 연결 시
io.on('connection', (socket) => {
const req = socket.request; // 웹소켓과는 달리 req객체를 따로 뽑아야함
//* ip 정보 얻기
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
console.log('새로운 클라이언트 접속!', ip, socket.id, req.ip);
// socket.id 는 소켓 연결된 고유한 클라이언트 식별자라고 보면된다. 채팅방의 입장한 고유한 사람
//* 연결 종료 시
socket.on('disconnect', () => {
console.log('클라이언트 접속 해제', ip, socket.id);
clearInterval(socket.interval);
});
//* 에러 시
socket.on('error', (error) => {
console.error(error);
});
//* 클라이언트로부터 메시지
socket.on('reply', (data) => {
console.log(data);
});
//* 클라이언트로 메세지 보내기
socket.interval = setInterval(() => {
// 3초마다 클라이언트로 메시지 전송
socket.emit('news', 'Hello Socket.IO');
}, 3000);
});
};
먼저 socket.io 패키지를 불러와 익스프레스 서버에 연결한다.
SocketIO 객체의 두 번째 인수로 옵션 객체를 넣어 서버에 관현 여러 가지 설정들을 할 수 있다.
여기선 클라이언트가 접속할 경로인 path 옵션만 사용했다. 클라이언트도 이 경로와 일치하는 path를 넣어야한다.
연결 후에는 이벤트 리스너를 붙인다.
connection 이벤트는 클라이언트가 접속했을 때 발생하며, 콜백으로 소켓 객체를 제공한다.
Socket.IO의 핵심은 socket 객체와 io 객체이다.
socket.request 속성으로 요청 객체에 접근할 수 있고, socket.request.res 로는 응답 객체에 접근할 수 있다.
socket.id 로 소켓 고유의 아이디도 가져올 수 있으며 이 아이디로 소켓의 주인을 특정할 수 있다. (특정 채팅방 참여자에게 귓속말을 보낸다거나 강퇴시킨다거나)
socket에도 이벤트 리스너를 붙인다.
disconnect는 클라이언트가 연결을 끊었을 때 발생하고, error는 통신 과정에서 에러가 나왔을 때 발생한다.
reply는 사용자가 직접 만든 이벤트이다.
클라이언트에서 reply라는 이벤트 명으로 데이터를 보낼 때 서버에서 받는 부분이다.
이와 같이 이벤트 방식이 ws 모듈과는 다르다.
아래에 emit(이밋 이라 읽는다) 메서드로 3초마다 클라이언트 한 명에게 메시지를 보내는 부분이 있는데, 인수가 두 개이다.
첫 번째 인수는 이벤트 이름, 두 번째 인수는 데이터다.
즉, news라는 이벤트 이름으로 Hello Socket.IO라는 데이터를 클라이언트에 보낸 것이다.
클라이언트가 이 메시지를 받기 위해선 news라는 이벤트를 받을 news 이벤트 리스너를 만들어두어야 한다. 따라서 클라이언트 부분인 index.njk도 다음과 같이 바꿔준다.
클라이언트 (index.njk)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>GIF 채팅방</title>
</head>
<body>
<div>F12를 눌러 console 탭과 network 탭을 확인하세요.</div>
<!-- 익스프레스 서버와 소켓 서버가 연결이 되면, 소켓IO 서버에서 js파일을 넣어준다 -->
<script src="/socket.io/socket.io.js"></script>
<script>
// 위의 socket.io.js에서 뽑아 쓴다.
const socket = io.connect('http://localhost:8005', {
path: '/socket.io', // 서버 path와 일치시켜준다
//transports: ['websocket'] // polling 시도하지말고 바로 웹소켓으로 하려면 설정
});
socket.on('news', function (data) {
console.log(data);
socket.emit('reply', 'Hello Node.JS');
});
</script>
</body>
</html>
위의 src인 /socket.io/socket.io.js 는 Socket.IO에서 클라이언트로 제공하는 스크립트이며, 실제 파일이 아니다.
이 스크립트를 통해 서버와 유사한 API로 웹 소켓 통신이 가능하다.
스크립트가 제공하는 io 객체에 서버 주소를 적어 연결한다.
서버에서 보내는 news 이벤트를 받기 위해 news 이벤트 리스너를 붙여두었다. news 이벤트가 발생하면 emit 메서드로 다시 서버에 답장을 한다. 서버의 reply 이벤트 리스너로 답장이 간다.
서버를 실행하고 http://localhost:8005에 접속해보자.
개발자 도구 (F12) 의 Network 탭을 보면 조금 독특한 것을 발견할 수 있는데, 바로 웹 소켓 연결 말고도 폴링 연결 (xhr) 이 있다는 것이다.
웹소켓 지원 안하는 구형 브라우저 때문에, socket.IO는 먼저 polling 방식으로 연결 시도를 해보고,
그다음에 웹소켓이 가능하면 웹소켓으로 업그레이드(101 코드) 하는 것이다.
웹소켓의 프로토콜은 ws:// 인데 http를 써주는 이유가 바로 이것이다.
먼저 http로 연결해서 웹소켓 지원여부를 확인하고, 확인되면 ws로 업그레이드 해주는 방식인 것이다.
그래서 ws 프로토콜이 아니라 http 프로토콜을 사용한다는 점이 ws 모듈과는 다른 점이다.
처음부터 웹 소켓만 사용하고 싶다면 클라이언트에서 socket 옵션에 transports: ['websocket'] 을 넣어 제한할 수 있다.
네트워크 탭을 보면, 서로 통신하는걸 볼 수 있는데, 내용을 보니, 위에서 소켓 이벤트 통신한다고 거창하게 설명은 했지만, 실상은 단순히 문자열 배열을 받아 배열 인자를 파싱해 구분하는 아주 단순한 기법을 사용하는 걸 알 수 있다.
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.