Zoom 클론코딩 #3 SoketIO
설명 및 설정
프론트 백엔드 실시간 연결해주는 framework
유연해서 연결이 끊기면 자동으로 재연결을 시도하고
websocket이 지원이 안되면 다른 방법을 찾아서 연결한다.
socketIO는 webScoket의 부가기능아님!!! webSocket은 브라우저에 api가 기본적으로 있어서 프론트에 설치를 안해도됐지만 socketIo는 기능도 더많고 다르기 때문에 백, 프론트 각각 설치 해줘야함
설치 npm i socket.io
import SocketIO from "socket.io";
const httpServer= http.createServer(app);
const wss = new WebSocket.Server({httpServer}); 기존 웹소켓 서버 방식
const wsServer= SocketIO(httpServer); 소켓아이오 서버 방식 매우 흡사하다.
socketio백엔드 연결 준비
server.js
const httpServer = http.createServer(app);
const wsServer = SocketIO(httpServer);
wsServer.on("connection", (socket) =>{
console.log(socket);
})
socketio프론트 연결 준비
pug에
script(src="/socket.io/socket.io.js")
app.js
const socket = io(); //위에 설치하면 프론트에 io라는 기능이 생김 자동으로 실행하고 있는 socketio서버를 찾는기능
연결확인
front에 console에 이상 없는지 확인,
back-end에 보면 직접 sockets=[]배열에 넣은 webSocket과 다르게 자동으로 접속한 서버 감지
pug
div#welcome
form
input(placeholder="room name", required, type="text")
btton Enter Room
app.js
webSocket과 달리 socket.send()로 항상 메세지로 보내고 해당 sokect.on("message",)텍스트 메세지로 받아 JSON.Stringify 할 필요없이
event이름과 object을 socket.emit(text,argment1,argument2,argument2)처럼 Json,텍스트,숫자,boolean등으로 여러개의 인자를 보낼 수 있음
마지막인자에 끝날 때 실행되는 함수
,server에서 호출하지만 front-end에서 실행되는 function보냄
백엔드에서도
wsServer.on("connection", (socket) =>{
socket.on("enter_room", (roomName, done)=> {
console.log(roomName);
setTimeout(()=>{
done("hello");
},1000);
});
})
};// 이벤트이름과 함수를 적어주면 됨
})
app.js
function backendDone(msg){
console.log("backend says: ", msg );
}
function hanldeRoomSubmit(event){
event.preventDefault();
const input = form.querySelector("input");
socket.emit("enter_room",input.value, backendDone );
input.value="";
}// 실행하면 백 콘솔에 rommName 나오고 , front console에 backend says hello 라고 뜸
절대 주의! 마지막 function(done)을 실행시킬 때 프론트엔드function을 백엔드 여기서, 실행하는것이 아니고
프론트에있는 버튼을 대신 눌려주는 거라고 생각. 프론트에서 실행함
백엔드에서 인자를 같이 보내서 실행 할 수있음.
Room
socket.io는 기본적으로 room을 제공.
socket.join("룸이름")
socket.onAny() :미들웨어 같은 것 어느 이벤트건간에 console.log하게 해줌
wsServer.on("connection", (socket) =>{
socket.onAny((event)=>{
console.log(`Socket event: ${event}`); // Socket event: enter_room
})
socket.on("enter_room", (roomName, done)=> {
console.log(socket.id); //fEHdwQp9pOYtxHfDAAAB
console.log(socket.rooms); //Set { 'fEHdwQp9pOYtxHfDAAAB' }
socket.join(roomName);
console.log(socket.rooms); //Set { 'fEHdwQp9pOYtxHfDAAAB', '1234' }
setTimeout(()=>{
done("hello");
},1000);
});
})
socket.join, leave, 방에다가 이벤트와 메세지를 보낼 수 있다.
방제 정하고 입장하면
방제 제목 form 사라지고 , msg from 생기고, roomName 나타내기
const socket = io();
const welcome = document.getElementById("welcome");
const form = welcome.querySelector("form");
const room = document.getElementById("room");
room.hidden=true;
let roomName;
function showRoom(msg){
room.hidden=false;
welcome.hidden = true;
const h3 = room.querySelector("h3");
h3.innerText= `Room ${roomName}`;
}
function hanldeRoomSubmit(event){
event.preventDefault();
const input = form.querySelector("input");
socket.emit("enter_room",input.value, showRoom );
roomName=input.value;
input.value="";
}
server-api/socket-to-room
io.on("connection", (socket) => { // to one room socket.to("others").emit("an event", { some: "data" }); // to multiple rooms socket.to("room1").to("room2").emit("hello"); // or with an array socket.to(["room1", "room2"]).emit("hello"); // a private message to another socket socket.to(/* another socket id */).emit("hey"); // WARNING: `socket.to(socket.id).emit()` will NOT work, as it will send to everyone in the room // named `socket.id` but the sender. Please use the classic `socket.emit()` instead. }); |
wsServer.on("connection", (socket) =>{
socket.onAny((event)=>{
console.log(`Socket event: ${event}`);
})
socket.on("enter_room", (roomName, done)=> {
socket.join(roomName);
done();
socket.to(roomName).emit("welcome"); //roomname room에 들어가면 welcome이라는 이벤트 발생
});
})
app.js
function addMessage(msg){ //자주 쓸 내용이라 함수로 따로 만듦
const ul = room.querySelector("ul");
const li = document.createElement("li");
li.innerText=msg;
ul.appendChild(li);
}
socket.on("welcome", ()=>{
addMessage("Someone Joined!")
});
나를 제외한 모든 socket에 Someone Joined라고 알람이 감
이벤트
disconnecting
This event is similar to disconnect but is fired a bit earlier, when the Socket#rooms set is not empty yet
완전히 서버와 연결이 끊나기 전에
app.js
socket.on("bye",()=>{
addMessage("Someone Left!")
});
server.js
socket.on("disconnecting",()=>{
socket.rooms.forEach((room)=> socket.to(room).emit("bye")); //socket.rooms하면 각각의 id 배열이 나옴
})
disconnect : 연결이 완전히 끊어졌을때 발생하는 이벤트 (room 정보가 비어있음)
disconnecting : 브라우져는 이미 닫았지만 아직 연결이 끊어지지 않은 그 찰나에
발생하는 이벤트 (그래서 room 정보가 살아있음)
--------------------------------------------------------------------
Set(2) { "asdfasdfasdf" , "room" } 에서
Set은 자바스크립트 자료구조 중 하나 입니다.
중복되는 data를 포함할 수 없고,
Index가 적용이 안되고,
forEach 사용이 가능합니다.
내가 메세지 보내기
app.js
function showRoom(msg){
room.hidden=false;
welcome.hidden = true;
const h3 = room.querySelector("h3");
h3.innerText= `Room ${roomName}`;
const form = room.querySelector("form");
form.addEventListener("submit", hanldeMessageSubmit);
}
function hanldeMessageSubmit(event){
event.preventDefault();
const input = room.querySelector("input");
socket.emit("new_message", input.value, roomName, ()=>{
addMessage(`You ${input.value}`); //함수로 백에 넘겨주는 이유는 그냥 바로 프론트에 나타내는 것이아닌 서버에 내용을 전달하여 해당 room에 있는 전체 사람들에게 socket.to하여 전달하여 나타내기위해인듯하다.
}); //백엔드에 new_message라는 이벤트를 발생 시키고잇음
}
server.js
socket.on("new_message",(msg,room,done)=>{
socket.to(room).emit("new_message", msg);
done(); //백엔드에서 시켜서 프런트에 발생하는 이벤트
})
})
닉네임 설정하기
server.js
wsServer.on("connection", (socket) =>{
socket["nickname"]="익명";
socket.onAny((event)=>{
console.log(`Socket event: ${event}`);
})
socket.on("enter_room", (roomName, done)=> {
socket.join(roomName);
done();
socket.to(roomName).emit("welcome",socket.nickname);
});
socket.on("disconnecting",( )=>{
socket.rooms.forEach((room)=> socket.to(room).emit("bye",socket.nickname));
})
socket.on("new_message",(msg,room,done)=>{
socket.to(room).emit("new_message", `${socket.nickname} :` msg);
done();
})
socket.on("nickname", ((nickname) => (socket["nickname"]= nickname)))
})
socket.on("enter_room", (roomName, done)=> {
socket.join(roomName);
done();
socket.to(roomName).emit("welcome",socket.nickname);
});
socket.on("disconnecting",( )=>{
socket.rooms.forEach((room)=> socket.to(room).emit("bye",socket.nickname));
})
app.js
function showRoom(msg){
room.hidden=false;
welcome.hidden = true;
const h3 = room.querySelector("h3");
h3.innerText= `Room ${roomName}`;
const msgForm = room.querySelector("#msg");
const nameForm = room.querySelector("#name");
msgForm.addEventListener("submit", hanldeMessageSubmit);
nameForm.addEventListener("submit", handleNicknameSubmit);
}
function handleNicknameSubmit(event){
event.preventDefault();
const input = room.querySelector("#name input");
socket.emit("nickname", input.value); //계속 메시지를 보내도 익명으로 나오고 socket.onAny에 socketevent가 안떳다.. 이유는 emit으로 이벤트를 발생시켜야하는데 socket.on을 작성했던것,,!
console.log(input.value);
}
socket.on("welcome",(user)=>{
addMessage(`${user}님이 입장하였습니다.` )
});
socket.on("bye",(left)=>{
addMessage(` ${left}님이 나갔습니다.!`)
});
Adapter
다른 서버들 사이에 실시간 어플리케이션을 동기화하는 것
우린 데이터베이스에 아무 것도 저장하고 있지않고 서버의 메모리에서 adapter을 사용하고있음
우리가 서버를 종료하고 시작할 때 모든 방과 메시지, 소켓은 사라진다.
재시작하면 사라짐
adapter은 누가 연결 되었는지, 현재 어플리케이션에 room이 얼마나 있는지 알려줌
wsServer.sockets.adapter하면 볼 수있는 것들 중
rooms : 어플리케이션에있는 모든 rooms 볼 수있다.
sids : socket ids 볼 수있음
모든 sockets은 private room이 있다.
그건 id가 방제목인 경우 그래서 우리가 private message를 보낼 수 있다.
Each Socket in Socket.IO is identified by a random, unguessable, unique identifier Socket#id. For your convenience, each socket automatically joins a room identified by its own id.
새로운 방이 생겼다고 모두에게 알리기wsServer.sockets.emit
socket.emit()이 아니라
server.sockets.emit()사용 : 연결 된 모든 소켓에게 사용됨
서버에서 모든 소켓에게 보내는 것 : broadcast
("room_change", publicRooms()); //룸 체인지 emit을 방에 들어갈때나 디스커넥트 상황 외에 젤 처음 커넥트 할때도 emit할 수 있게 하면 새로 서버에 연결된 클라이언트도 실시간으로 변경된 방 내역을 볼 수 있음.
function publicRooms() {
const {
sockets: {
adapter: { sids, rooms },
},
} = wsServer;
const publicRooms = [];
rooms.forEach((_, key) => {
if (sids.get(key) === undefined) {
publicRooms.push(key);
}
});
return publicRooms;
}
socket.on("enter_room", (roomName, done)=> {
socket.join(roomName);
done();
socket.to(roomName).emit("welcome", socket.nickname);
wsServer.sockets.emit("room_change", publicRooms());
});
socket.on("disconnect",()=>{
wsServer.sockets.emit("room_change", publicRooms());
})
app.js
socket.on("room_change", (rooms)=>{
const roomList = welcome.querySelector("ul");
roomList.innerText ="";
rooms.forEach((room)=>{
const li = document.createElement("li");
li.innerText= room;
roomList.append(li);
})
});
방 안에 있는 사람들의 수 세기
map의 set은 중복되지않는 값을 가진다.
우리가 가지고 있는 방들 중에 key 값을 가져와서 찾은 다음 set.size한다.
해당 key값을 가진 방의 사람 수 세기 함수
function countRoom(roomName){
return wsServer.sockets.adapter.rooms.get(roomName)?.size //Optional chaining
}
이것을 사람이 들어 오고 나갈 때 적용 할 것임
socket.on("enter_room", (roomName, done)=> {
socket.join(roomName);
done();
socket.to(roomName).emit("welcome", socket.nickname, countRoom(roomName));
wsServer.sockets.emit("room_change", publicRooms());
});
socket.on("disconnecting",( )=>{
socket.rooms.forEach((room)=> socket.to(room).emit("bye", socket.nickname, countRoom(room) - 1));
});//나가기 전에! disconnect일 때 적용하면 room의 정보를 모름 그리고 나가기전에 count하면 나까지 포함되므로 -1
socket.on("welcome", (user,newCount)=>{
addMessage(`${user}님이 입장하였습니다.` );
const h3 = room.querySelector("h3");
h3.innerText= `Room ${roomName} (${newCount})`;
});
socket.on("bye", (left, newCount)=>{
addMessage(` ${left}님이 나갔습니다.`);
const h3 = room.querySelector("h3");
h3.innerText= `Room ${roomName} (${newCount})`;
});
socket.on("new_
1) socket.to(roomName).emit("enter_room", socket.nickname, countRoom(roomName));
-> 방에서 자신을 제외한 모두에게 브로드캐스팅하여 메시지를 전달
2) wsServer.to(roomName).emit("enter_room", socket.nickname, countRoom(roomName));
-> 방에서 자신을 포함한 모든 유저에게 메시지를 전달합니다.
자신이 방에 입장했을 때 유저 카운트가 몇인지 확인하고 싶다면 2번도 적절히 섞어가며 사용하면 좋을 것 같습니다
Admin UI
socket.io 백엔드를 위한 것
소켓,룸,클라이언트 등 확인가능
https://socket.io/docs/v4/admin-ui/#current-features
Admin UI | Socket.IO
The Socket.IO admin UI can be used to have an overview of the state of your Socket.IO deployment.
socket.io
npm i @socket.io/admin-ui
설정/ 위에 페이지 참고
import http from "http";
import {Server} from "socket.io";
import { instrument } from "@socket.io/admin-ui";
import express from "express";
const httpServer = http.createServer(app);
const wsServer = new Server(httpServer,{
cors: {
origin: ["https://admin.socket.io"],
credentials: true
}
});
instrument(wsServer, {
auth: false
});
https://github.com/UHyun-K/zoomClone/commit/f4065d6a59338160686e7e620c28b239465c4654