Socket.IO는 실시간 웹 애플리케이션을 위한 이벤트 기반 라이브러리이다. 웹 클라이언트와 서버 간의 실시간 양방향 통신을 가능케 한다. 

 

 

https://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

+ Recent posts