Notice
Recent Posts
Recent Comments
Link
«   2026/06   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

개발자가 될래요

Next + Socket.io 로 채팅 구현(2) 본문

프로젝트

Next + Socket.io 로 채팅 구현(2)

Youcan 2024. 8. 17. 23:00

일단 이전 포스팅에서 socket의 연결을 확인하였다.

 

다음은 메세지를 입력하고 그 채팅을 출력하는 작업을 하려고 한다.

 

server.ts

import { createServer } from "http";
import next from "next";
import { Server as SocketIOServer, Socket } from "socket.io";

const dev = process.env.NODE_ENV !== "production";
const hostname = process.env.HOSTNAME;
const port = 4000;

const app = next({ dev, hostname, port });
const handler = app.getRequestHandler();

app.prepare().then(() => {
  const httpServer = createServer(handler);

  const io = new SocketIOServer(httpServer, {
    cors: {
      origin: `http://localhost:3000`,
      methods: ["GET", "POST"],
      credentials: true,
    },
  });

  io.on("connection", (socket: Socket) => {
    console.log("New client connected", socket.id);

    socket.on("message", (data: { userId: string; message: string }) => {
      console.log("Message received:", data.userId, data.message);
      socket.emit("response", "Message received!");
    });

    socket.on("disconnect", () => {
      console.log("Client disconnected", socket.id);
    });
  });

  httpServer
    .once("error", (err: Error) => {
      console.error(err);
      process.exit(1);
    })
    .listen(port, () => {
      console.log(`> Ready on http://${hostname}:${port}`);
    });
});

 

app/chat/page.tsx


"use client";

import { useEffect, useState } from "react";
import { socket } from "@/socket";
import InputComponents from "../components/Input";

export default function ChatPage() {
  const [isConnected, setIsConnected] = useState(false);
  const [transport, setTransport] = useState("N/A");
  const [chatLog, setChatLog] = useState<{ userId: string; message: string }[]>(
    []
  );

  useEffect(() => {
    if (socket.connected) {
      onConnect();
    }

    function onConnect() {
      setIsConnected(true);
      setTransport(socket.io.engine.transport.name);

      socket.io.engine.on("upgrade", (transport) => {
        setTransport(transport.name);
      });
    }

    function onDisconnect() {
      setIsConnected(false);
      setTransport("N/A");
    }

    socket.on("connect", onConnect);
    socket.on("disconnect", onDisconnect);
    socket.on("message", (data: { userId: string; message: string }) => {
      setChatLog((prevChatLog) => [...prevChatLog, data]);
    });

    return () => {
      socket.off("connect", onConnect);
      socket.off("disconnect", onDisconnect);
      socket.off("message");
    };
  }, []);

  const addMessage = (userId: string, message: string) => {
    setChatLog((prevChatLog) => [...prevChatLog, { userId, message }]);
  };

  const status = isConnected ? "bg-green-500" : "bg-red-700";
  const port = transport != "N/A" ? "bg-green-500" : "bg-slate-400";

  return (
    <div>
      <div
        className={`flex w-48 h-10 m-4 items-center justify-center rounded-full ${status}`}
      >
        <p>Status: {isConnected ? "connected" : "disconnected"}</p>
      </div>
      <div
        className={`flex w-48 h-10 m-4 items-center justify-center rounded-full ${port}`}
      >
        <p>Transport: {transport}</p>
      </div>
      
      <div>
        <p className="text-3xl">Chat Log</p>
        <ul>
          {chatLog.map((chat, index) => (
            <li key={index}>
              {chat.userId} : {chat.message}
            </li>
          ))}
        </ul>
      </div>
      <InputComponents addMessage={addMessage} />
    </div>
  );
}

 

 

 

app/components/Input.tsx

"use client";

import { useState } from "react";
import { socket } from "@/socket";

type InputComponentsProps = {
  addMessage: (userId: string, message: string) => void;
};

export default function InputComponents({ addMessage }: InputComponentsProps) {
  const [message, setMessage] = useState<string>("");
  const [userId, setUserId] = useState<string>("");

  const sendMessage = (e: React.FormEvent) => {
    e.preventDefault();
    if (message.trim() && userId.trim()) {
      socket.emit("message", { userId, message });
      addMessage(userId, message);
      setMessage("");
    }
  };

  return (
    <>
      <div className="flex">
        <div>
          <input
            className="w-20 h-10 border-2 border-gray-500 bg-slate-200 mr-4"
            onChange={(e) => setUserId(e.target.value)}
            placeholder="ID입력"
            value={userId}
          />
        </div>
        
        <div className="flex">
          <form action="" onSubmit={sendMessage}>
            <input
              className="w-40 h-10 border-2 border-blue-500 bg-slate-200"
              onChange={(e) => setMessage(e.target.value)}
              placeholder="메세지입력"
              value={message}
            />
            <button className="w-20 h-10 bg-blue-200 mx-4 rounded-full">
              전송
            </button>
          </form>
        </div>
      </div>
    </>
  );
}

 

이런식으로 코드를 작성했고 실행시킨 페이지는 이렇다.

 

ID입력에는 추후에 로그인 기능을 추가했을 때, 로그인한 아이디를 넣는 것으로 수정하기로 하고.. 메세지를 입력하면 이렇다.

 

동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.

근데 생각해보니 나 혼자 친 채팅만 기록에 남고 다른 사람과 채팅하는 기능이 아니다..

 

그래서 찾아보다보니 io.emit과 socket.broadcast.emit의 차이점을 알게됐다.

 


io.emit은 연결된 모든 클라이언트를 대상으로 한다.

따라서 메세지를 보낸 Client에게도 다시 전달된다.

 

socket.broadcast.emit은 발신자의 client는 제외한다.


 

아무튼 코드를 수정하고 채팅을 해보자

server.ts

    socket.on("message", (data: { userId: string; message: string }) => {
      socket.broadcast.emit("message", data);
    });

 

 

동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.

채팅이 되는 것 같다..