Socket.IO는 실시간 웹 애플리케이션을 위한 이벤트 기반 라이브러리이다. 웹 클라이언트와 서버 간의 실시간 양방향 통신을 가능케 한다.
Socket.IO
Reliable Rest assured! In case the WebSocket connection is not possible, it will fall back to HTTP long-polling. And if the connection is lost, the client will automatically try to reconnect.
socket.io
MongoDB를 이용해, 로그인 시, 대화 목록을 가져옴.
socket io로 실시간 대화 송수신.
socket io- Server
3000포트(클라이언트)와의 CORS(Cross-Origin Resource Sharing)를 막기 위해 설정해준다.
// server/socket.ts
import { Server } from "socket.io";
import http from "http";
import express from "express";
const app = express();
const server = http.createServer(app);
const io = new Server(server, {
cors: {
origin: ["http://localhost:3000"],
methods: ["GET", "POST"],
},
});
온라인 유저 목록 송신- server
- io.on: 서버와 클라이언트에서 사용되는 수신용 이벤트리스너이다.
// server/socket.ts
io.on("connection", (socket) => {
console.log("a user connected", socket.id); // 출력) a user connected ihbfG-so_-acDN7SAAAB
- 클라이언트와 연결되면, 클라이언트의 소켓 아이디를 출력한다.
- 소켓에 연결된 유저 목록을 클라이언트로 넘겨주는 작업
interface UserSocketMap {
[key: string]: string;
}
const userSocketMap: UserSocketMap = {}; // {userId: socketId}
const userId: string = socket.handshake.query.userId as string;
if (userId !== "undefined") userSocketMap[userId] = socket.id;
// 유저 _id 배열을 넘겨준다.
io.emit("getOnlineUsers", Object.keys(userSocketMap));
- userSocketMap(object)에 저장된 값
온라인 유저 목록 수신: client
- 클라이언트에서 이벤트 리스너로 서버로부터 유저 _id 배열을 받아서
현재 접속 중인 유저 목록을 Context API로 전역적으로 저장하고 관리한다.
useEffect(() => {
if (authUser) {
const socket = io("http://localhost:5000", {
query: {
userId: authUser._id,
},
});
setSocket(socket);
socket.on("getOnlineUsers", (users) => {
setOnlineUsers(users);
});
- Context API를 사용하는 이유:
보통 애플리케이션 UI 랜더링 데이터로는 전역 상태 관리로는 Redux, Recoil, zustand... 등이 있지만,
Context API는 소켓과 같은 인스턴스 및 함수를 저장하는데 적합하다.
메시지 송신- client
메시지 보내기
클라이언트)
try {
const res = await fetch(`/api/messages/send/${selectedConversation._id}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ message }),
});
const data = await res.json();
메시지 수신- server
발신자와 수신자 ID 저장)
const { message } = req.body;
const { id: receiverId } = req.params;
const senderId = req.body.user._id;
발신자와 수신자 사이에 기존 대화를 찾기)
- 대화가 존재하지 않으면 발신자와 수신자를 참가자로 하는 새로운 대화를 생성한다.
let conversation = await Conversation.findOne({ participants: { $all: [senderId, receiverId] }, });
if (!conversation) { ... }:
** $all 연산자: 배열 필드가 주어진 모든 값을 포함하는 문서를 찾는데 사용 **
새 메시지를 생성)
- 새 메시지가 성공적으로 생성되면, 그 메시지의 ID를 대화의 메시지에 추가한다.
const newMessage = new Message({ senderId, receiverId, message, });
if (newMessage) { ... }
대화와 새 메시지를 데이터베이스에 저장)
- Promise.all은 해당 작업을 병렬로 수행하여 성능을 향상시킵니다.
await Promise.all([conversation.save(), newMessage.save()]);
수신자의 소켓 ID를 검색)
수신자의 소켓 ID가 발견되면, 새 메시지와 함께 수신자의 소켓에 newMessage 이벤트를 전송합니다.
수신자의 클라이언트는 새 메시지로 즉시 업데이트.
const receiverSocketId = getReceiverSocketId(receiverId);
if (receiverSocketId) { ... }
실시간 메시지 송-수신- client
server)
if (receiverSocketId) {
io.to(receiverSocketId).emit("newMessage", newMessage);
}
client)
useEffect(() => {
socket?.on("newMessage", (newMessage: any) => {
newMessage.shouldShake = true;
const sound = new Audio(notificationSound);
sound.play();
dispatch(setMessages([...messages, newMessage]))
});
return () => socket?.off("newMessage");
}, [socket, setMessages, messages]);
};
'IT > Backend' 카테고리의 다른 글
[BE] JWT 로그인 환경 구축 (0) | 2024.02.03 |
---|