나만의 채팅 서비스 만들기 (react + express + Socketio)

박재훈·2021년 4월 21일
5

갑자기 왜?


마이스터고등학교에 재학하며 다양한 프로젝트 경험을 쌓을 수 있었습니다.
다양한 프로젝트를 진행하며 여러 기술을 사용해보았지만, 정작 꼭 사용해보고 싶었던 기술인 Websocket기술을 사용해 본 경험이 전무했습니다.
마침 제가 꼭 지원하고 싶은 회사에서 Websocket기술을 바탕으로 한 서비스를 개발한다는 소식을 듣고, Websocket을 이용한 간단한 채팅 앱을 구현해보도록 하겠습니다.

프로젝트 세팅

백엔드 먼저 설정한 후 리액트 프로젝트를 설정하겠습니다.

터미널
mkdir back
cd back

express socket_project
cd socket_project

npm init -y 
npm install socket.io // socket.io 설치
npm install express //express 설치
npm install

이제 app.js파일을 생성한 후 app.js파일의 기본 세팅을 해주겠습니다.

//app.js

const httpServer = require("http").createServer();
const io = require("socket.io")(httpServer, {
  cors: {
    origin: "http://localhost:3000",
    methods: ["GET", "POST"],
  },
});//cors 오류로 인한 설정

io.on("connection", (socket) => {
  console.log("connection");
  socket.on("init", (payload) => {
    console.log(payload);
  });
});

httpServer.listen(80);

이제 클라이언트 세팅을 해주겠습니다.
간단한 프로젝트이므로 cra를 통해 세팅해주겠습니다.

cd ..// 루트 디렉토리로 이동
create-react-app front
cd front
npm install socket.io-client
//app.js
import React, { useEffect } from "react";
import "./App.css";
import io from "socket.io-client";
const socket = io.connect("http://localhost:80");
socket.emit("init", { name: "jaehoon" });
function App() {
  useEffect(() => {
    return () => {
      socket.close();
    };
  }, []);
  return (
    <div>
      <input></input>
    </div>
  );
}

export default App;

이제 서버와 클라이언트를 실행시키면


다음과 같이 정상적으로 연결됨을 확인할 수 있습니다.

이제 본격적으로 채팅 앱을 만들기 전 소켓 통신의 흐름에 대해 살펴보도록 하겠습니다.

클라이언트 소켓은 처음 생성 한 후
(import io from "socket.io-client";)
서버 측에 연결을 요청합니다.
(const socket = io.connect("http://localhost:80");)
서버 소켓에 연결이 받아지면 데이터를 송수신하고
(socket.emit("init", { name: "jaehoon" });)
모든 처리가 완료되면 소켓을 닫습니다.
(socket.close();)

컴포넌트 스타일 작성

자 이제 본격적으로 채팅 앱을 위한 클라이언트의 스타일을 작성하도록 하겠습니다.

//App.js
import React, { useEffect } from "react";
import "./App.css";
import io from "socket.io-client";
const socket = io.connect("http://localhost:80");
socket.emit("init", { name: "jaehoon" });
function App() {
  useEffect(() => {
    return () => {
      socket.close();
    };
  }, []);
  return (
    <div className="App">
      <div className="Box">
        <div className="ChatBox">
        </div>
        <div className="InputBox">
          <input placeholder="내용"></input>
          <input placeholder="이름"></input>
          <button>등록</button>
        </div>
      </div>
    </div>
  );
}

export default App;
//App.css
.App {
  width: 100vw;
  height: 100vh;
  padding : 0;
  display: flex;
  justify-content: center;
  align-items: center;
}
.Box{
  width:40%;
  height: 80%;
  border:1px solid black;
  border-radius: 1rem;
}
.ChatBox{
  border:1px solid black;
  border-radius: 1rem;
  height:95%;
  width: 100%;
 display: flex;
 justify-content: center;
}
.InputBox{
  display: flex;
  height: 5%;
  flex:1;
  justify-content: center;
  align-items: center;

}
.Chat{
  display: flex;
  justify-content: start;
  align-items: center;
  width:80%;
  height:20%;
  border:1px solid sandybrown;
  border-radius: 1rem;
}
.ChatLog{
  margin-left:100px;
}


위와 같은 뷰를 완성했으니, 이제 서버쪽 로직을 구현해보도록 하겠습니다.

// back/app.js
const httpServer = require("http").createServer();
const io = require("socket.io")(httpServer, {
  cors: {
    origin: "http://localhost:3000",
    methods: ["GET", "POST"],
  },
});

io.on("connection", (socket) => {
  console.log("connection");
  socket.on("init", (payload) => {
    console.log(payload);
  });
  socket.on("send message", (item) => {//send message 이벤트 발생
     console.log(item.name + " : " + item.message);
    io.emit("receive message", { name: item.name, message: item.message });
    //클라이언트에 이벤트를 보냄
  });
});

httpServer.listen(80);

io.on을 통해 클라이언트와 연결을 하고, send message라는 이벤트가 발생했을 때 클라이언트로부터 받은 값을 바탕으로 receive message 이벤트를 발생시킵니다.

이제 클라이언트 쪽 코드를 살펴보겠습니다.

//App.js
import React, { useCallback, useEffect, useState } from "react";
import "./App.css";
import io from "socket.io-client";
const socket = io.connect("http://localhost:80");
socket.emit("init", { name: "jaehoon" });
function App() {
  const [chatArr, setChatArr] = useState([]);
  const [chat, setChat] = useState({ name: "", message: "" });
  useEffect(() => {
    return () => {
      socket.close();
    };
  }, []);
  useEffect(() => {
    socket.on("receive message", (message) => {
      setChatArr((chatArr) => chatArr.concat(message));
    }); //receive message이벤트에 대한 콜백을 등록해줌
  }, []);
  const buttonHandler = useCallback(() => {
    socket.emit("send message", { name: chat.name, message: chat.message }); 
    //버튼을 클릭했을 때 send message이벤트 발생
  }, [chat]);
  const changeMessage = useCallback(
    (e) => {
      setChat({ name: chat.name, message: e.target.value });
    },
    [chat]
  );
  const changeName = useCallback(
    (e) => {
      setChat({ name: e.target.value, message: chat.message });
    },
    [chat]
  );
  return (
    <div className="App">
      <div className="Box">
        <div className="ChatBox">
          {chatArr.map((ele) => (
            <div className="Chat">
              <div>{ele.name}</div>
              <div className="ChatLog">{ele.message}</div>
            </div>
          ))}
        </div>
        <div className="InputBox">
          <input placeholder="내용" onChange={changeMessage}></input>
          <input placeholder="이름" onChange={changeName}></input>
          <button onClick={buttonHandler}>등록</button>
        </div>
      </div>
    </div>
  );
}

export default App;

useEffect를 통해 처음 렌더링 되었을 때 receive message이벤트에 대한 콜백을 등록합니다.
그리고 버튼이 클릭될 때 마다 input의 값을 send message이벤트를 통해 전달해줍니다. 모두 정상적으로 동작한다면 아래와 같은 결과를 확인할 수 있습니다.


마치며....

소켓 통신은 실시간 통신, 특히 채팅 서비스를 구현할 때 꼭 필요한 기술입니다. 이 글을 작성하며 처음 사용해본 기술이지만 굉장히 흥미롭게 다가왔고, 소켓 통신에 대한 개념을 정리할 수 있는 좋은 기회 였다고 생각합니다. 긴 글 읽어주셔서 감사합니다!

profile
개발자를 꿈꾸는 고등학생의 블로그입니다.

1개의 댓글

comment-user-thumbnail
2022년 10월 17일

마이스터고등학교에서 이른 나이부터 개발자 실무를 공부하시는게 부럽네요
저는 31살에 개발 시작해서 굉장히 고생하고있습니다

답글 달기