Socket.io 이란?
socket.io를 알아보기 전, 웹 소켓에 대해 먼저 알아보자.
웹 소켓은 HTML5에 새로 추가된 실시간 양 방향 데이터 전송을 위한 기술이다.
http가 아닌 ws프로토콜을 사용하며, 따라서 브라우저, 서버가 ws프로토콜을 지원하면 사용 가능하다.
socket.io 사용 예제
socket.io 예제의 디렉토리 구조는 아래와 같다.
필요한 npm을 install 해준다.
npm install express socket.io
그리고 아래 코드를 각 파일에 붙여넣자.
// index.js
const express = require("express");
const app = express();
const webSocket = require("./socket");
app.get("/", (req, res) => {
res.sendFile(__dirname + "/index.html");
});
const server = app.listen(3000, () => {
console.log("listening on *:3000");
});
webSocket(server);
// socket.js
const SocketIO = require("socket.io");
module.exports = (server) => {
const io = SocketIO(server, { path: "/socket.io" });
io.on("connection", (socket) => {
socket.on("chat message", (msg) => {
io.emit("chat message", msg);
});
});
};
// index.html
<!DOCTYPE html>
<html>
<head>
<title>Socket.IO chat</title>
</head>
<body>
<ul id="messages"></ul>
<form id="form" action="">
<input id="input" autocomplete="off" /><button>Send</button>
</form>
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io();
var messages = document.getElementById("messages");
var form = document.getElementById("form");
var input = document.getElementById("input");
form.addEventListener("submit", function (e) {
e.preventDefault();
if (input.value) {
socket.emit("chat message", input.value);
input.value = "";
}
});
socket.on("chat message", function (msg) {
var item = document.createElement("li");
console.log(msg);
item.textContent = msg;
messages.appendChild(item);
window.scrollTo(0, document.body.scrollHeight);
});
</script>
</body>
</html>
socket 통신은 기본적으로 socket.emit(' eventName', Func) 으로 데이터를 보내고, socket.on('eventName', Listener)로 데이터를 받는 형식으로, 이러한 구조를 통해 데이터의 교환이 이루어진다.
socket.js와 index.html의 스크립트 부분 코드를 통해 한번 차근히 알아보자.
// socket.IO에서 클라이언트로 제공하는 스크립트.
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io();
var messages = document.getElementById("messages");
var form = document.getElementById("form");
var input = document.getElementById("input");
form.addEventListener("submit", function (e) {
e.preventDefault();
if (input.value) {
// "chat message"라는 이벤트 이름을 가진 곳으로 input.value를 보낸다.
socket.emit("chat message", input.value);
input.value = "";
}
});
socket.on("chat message", function (msg) {
var item = document.createElement("li");
// msg의 value를 보기위한 console, 서버에서 보내온 msg값이 출력됨.
console.log(msg);
item.textContent = msg;
messages.appendChild(item);
window.scrollTo(0, document.body.scrollHeight);
});
</script>
// 소켓 서버를 열어준다.
const io = SocketIO(server, { path: "/socket.io" });
// 소켓 연결이 되었을 때
io.on("connection", (socket) => {
// "chat message" 리스너로 들어온 이벤트를 받는다.
// msg value는 클라이언트에서 "chat message"이벤트로 송신한 값
socket.on("chat message", (msg) => {
// 클라이언트에서 들어온 msg를 io 객체에 보낸다.
io.emit("chat message", msg);
});
})
실행 프로세스를 알아보자.
서버측 코드에서 io 객체는 연결된 전체 클라이언트와의 상호작용을 위한 객체이고, socket 객체는 개별 클라이언트와의 상호작용을 위한 객체이다.
서버측에서는 소켓 서버를 열어둔다.
소켓이 "connection"(연결)되었을때 개별 클라이언트에게 "chat message"이벤트로 이벤트가 송신되면 io객체, 즉 연결된 전체 클라이언트에게 들어온 msg를 보낸다. (연결이 종료되면 "disconnect"가 발생한다.)
클라이언트 측에서는 io()메소드를 호출하여 socket서버에 접속한다.
이후 버튼 클릭이 발생하였을 때, 서버측으로 "chat message"라는 이벤트로 현재의 input값을 보낸다. 이후 서버에서 다시 수신된 "chat message"에 대해서 scoket.on을 통해 받은 이후 화면에 추가하고 있다.
이렇게 소켓을 구현할 수 있습니다.
Namespaces
위 예제에서 봤듯이 socket통신에서는 "이벤트 리스너"를 통해 행위에 대해 알맞은 이벤트를 발생시킵니다. 하지만 이러한 통신 행위를 특정 페이지의 특정 클라이언트가 아닌, 항상 모든 클라이언트가 받게 된다면 이상적인 기능이 아니며 그럴 필요도 없다.
그렇기에 특정 노드만 데이터 송수신이 가능 하도록 단일 공유 연결을 통해 응용프로그램의 로직을 분할할 수 있는 통신 채널이다.
// 네임스페이스 이벤트. room, chat이라는 io 객체를 만든다.
const room = io.of("/room");
const chat = io.of("/chat");
// room io객체가 연결되었을때, room에 대한 이벤트만 실행된다.
room.on("connection", (socket) => {
console.log("room 네임스페이스에 접속");
socket.on("disconnect", () => {
console.log("room 네임스페이스 접속 해제");
});
});
// chat io객체가 연결되었을 때, chat에 대한 이벤트만 실행된다.
chat.on("connection", (socket) => {
console.log("chat 네임스페이스에 접속");
// "방번호"는 예시를 위한 임의의 값
socket.join(방번호);
socket.to(방번호).emit("join", {
user: "system",
chat: `${아이디}님이 입장하셨습니다.`,
});
socket.on("disconnect", () => {
console.log("chat 네임스페이스 접속 해제");
// 방에서 나가는 메서드
socket.leave(roomId);
});
});
네임스페이스는 io.of(/path)의 형태로 구성된다.
위 예시에서 알 수 있듯이 클라이언트 측에서 /path (예시에서는 /room, /chat)에 대해 연결된 io객체에 대한 이벤트들만 적용되는 것이다.
// room 네임스페이스 연결
const socket = io.connect("http://localhost:8005/room", {
path: "/socket.io",
});
socket.on("newRoom", function (data) {
// code for newRooem event
});
socket.on("removeRoom", function (data) {
// code for removeRoom event
});
위 코드는 채팅방을 만드는 코드의 생략된 코드이다.
/room에 연결된 io객체를 통해 "newRoom" (방 생성) , "removeRoom" (방 삭제) 등의 이벤트가 이루어 졌을때 서버측에서는
io.of("/room").emit("newRoom", event);
와 같이 매치되는 이벤트에 대해 실행한다.
Room
chat에 대한 코드중 join, to, leave 메서드가 보이는데 이는 socket에 구현되어 있는 메서드이다.
// chat io객체가 연결되었을 때, chat에 대한 이벤트만 실행된다.
chat.on("connection", (socket) => {
console.log("chat 네임스페이스에 접속");
// "방번호"는 예시를 위한 임의의 값
socket.join(방번호); // join
socket.to(방번호).emit("join", { // to
user: "system",
chat: `${아이디}님이 입장하셨습니다.`,
});
socket.on("disconnect", () => {
console.log("chat 네임스페이스 접속 해제");
// 방에서 나가는 메서드
socket.leave(roomId); // leave
});
});
socket에는 room이라는 개념이 구현되어 있다. 공식 문서의 번역으로는
"클라이언트 하위 집합에 데이터를 브로드캐스트할 수 있는 서버측 개념"이다.
즉, 각 네임스페이스에서 임의로 채널을 지정할 수 있으며, 이를 room이라고 부른다.
해당 설명의 예시는 카카오톡의 채팅방을 생각하면 이해가 쉽다. 채팅방A의 채팅은 채팅방B에게 송수신 되지 않는다.
우리는 room에 .join(room번호) 되어있는 노드에서 .to(room번호).emit("something", event) 를 통해 해당 room에 연결 되어있는 클라이언트에게 메세지를 전송하고, .leave(room번호)를 통해 연결을 종료한다.
다음에는 리액트와 node.js, socket.io를 이용하여 채팅 앱 제작 포스팅을 하도록 하겠습니다.
* 본 포스팅은 아래 글을 참고하여 작성되었습니다.
Node.js 교과서 개정 2판 - 조현영 지음.
'Web' 카테고리의 다른 글
[NestJs/트러블슈팅] NestJs에서 mongoDB find(), virtual field (0) | 2023.10.26 |
---|---|
[SERVER] 웹 서버와 WAS 그리고 DB (0) | 2023.03.14 |
[좋은 웹 API 디자인] 사용하기 좋은 API 디자인 (0) | 2023.03.03 |
[좋은 웹 API 디자인] API 디자인 기초 (0) | 2023.03.01 |
[OAuth2.0] 카카오 로그인 구현 (0) | 2023.02.25 |