목차
1. Socket.io에 대해
- Socket.io 특징
- Socket.io 그룹화 개념
- Socket.io 핵심 메소드
2. 프로젝트 기본 설정
3. Express 및 Socket 설정
4. 웹 프론트
- 웹 프론트 구현
- 웹 실시간 채팅 테스트
5. 네이티브 앱(AOS) 프론트
- 앱 프론트 구현
- 앱 실시간 채팅 테스트
6. 크로스 플랫폼 (Flutter) 프론트
- 크로스 플랫폼 구현
- 크로스 플랫폼 실시간 채팅 테스트
1. Socket.io에 대해

Socket.io는 클라이언트와 서버 간의 저지연, 양방향 통신을 가능하게 하는 JS 기반 라이브러리이다.
클라이언트와 서버 간의 메시지 전송이 이벤트 기반으로 이루어지며 이벤트 핸들러를 통해 특정 이벤트에 반응하여 데이터를 처리할 수 있다.
채팅하면 WebSocket, WebSocket하믄 채팅 아닌가? (무식티비)
채팅의 국룰은 WebSocket이 맞다. 그런데 WebSocket과 Socket.io는 동일한 개념이 아니다.
HTML5 WebSocket은 매우 유용한 기술이지만 오래된 브라우저의 경우 지원하지 않는 경우가 있다.
Socket.io는 WebSocket과 달리 브라우저, 디바이스에 상관없이 모든 환경에서 실시간 통신을 지원한다. 또한 WebSocket이 동작하지 않는 환경에서도 HTTP Long Polling, HTTP Polling 등 기타 통신을 지원하고 연결이 끊기면 자동으로 재연결을 시도한다.

Socket.io에는 WebSocket에는 없는 네임스페이스, 룸 개념이 있다.
📌 [ Namespace : 네임스페이스 ]
- 네임스페이스는 Socket.IO에서 동일한 서버 내에서 여러 개의 독립된 연결 공간을 제공
- 기본 네임스페이스는 '/'
- 추가적인 네임스페이스를 정의함으로써, 서로 다른 용도의 연결을 구분 가능
예를 들어, 채팅 애플리케이션에서 일반 채팅, 관리자 채팅, 알림 서비스 등을 각각의 네임스페이스로 분리하여 관리할 수 있다.
📌 [ Room : 룸 ]
- 룸은 네임스페이스 내에서 추가적인 그룹화를 제공하는 기능
- 룸을 사용하면, 같은 네임스페이스 내에서 특정 그룹에만 메시지를 전송 가능
- 룸은 소켓을 그룹으로 나누고, 특정 룸에 속한 소켓들에게만 이벤트를 전송하거나 *브로드캐스트 가능
- 룸 이름은 문자열, 소켓은 여러 룸에 참여 가능
브로드캐스트(Broadcast)란?
서버 측에서 특정 클라이언트가 아닌 모든 클라이언트에게 메시지를 보내기 위해 사용되는 기능으로
현재 연결된 클라이언트를 제외한 나머지 모든 클라이언트로 메시지를 전달할 때 유용하다.
마지막으로 네임스페이스와 룸을 표로 비교해보자.
특징 | Namespace | Room |
용도 | Namespace 전체를 구분하여 독립된 연결 관리 | Namespace 내에서 소켓을 그룹화 |
관리 범위 | 서버와 클라이언트 모두에서 관리 가능 | 서버 측에서만 관리 가능 |
유연성 | 다양한 서비스, 기능을 독립적으로 운영 가능 | Room에 속한 그룹만 이벤트 전송 |
이벤트 범위 | 특정 Namespace에 있는 모든 소켓 | 특정 Room에 있는 소켓에만 전송 |
다음으로 Socket.io의 핵심 메소드를 알아보자.
Socket.io에서 클라이언트와 서버 간의 이벤트 기반 통신을 구현하는데 사용되는 핵심 메소드로 emit, on이 있다.
emit 메소드는 특정 이벤트를 발생시키고, 해당 이벤트와 관련된 데이터를 전송하는 역할을 한다.
즉, 서버나 클라이언트가 상대방에게 메시지를 보낼 때 사용하는 메소드이다.
▼ emit 사용 예시
// eventName : 전송하려는 이벤트명
// data : 이벤트와 함께 전송할 데이터
socket.emit('eventName', data);
on 메소드는 특정 이벤트를 수신하고, 그에 대한 처리를 수행하는 이벤트 리스너를 설정하는 메소드이다.
클라이언트와 서버 모두에서 상대방이 발생시킨 이벤트를 듣고 반응할 때 사용한다.
▼ on 사용 예시
// eventName : 수신하려는 이벤트명 (emit한 이벤트명과 일치해야함)
// data : 이벤트와 함께 전송된 데이터를 처리하기 위한 콜백 함수 매개변수
socket.on('eventName', (data) =>{
// 이벤트 발생 시 실행할 코드
});
전체적인 흐름은 다음과 같다.
연결 시도 → 초기 연결 → 데이터 전송(emit & on) → 연결 유지 → 연결 종료 |
1. 연결 시도 : 클라이언트가 서버에 연결 요청을 보내고, 서버는 클라이언트에 대해 소켓을 생성하고 고유한 ID를 할당한다.
2. 초기 연결 : 폴링을 통해 연결이 이루어진 후, 가능한 경우 WebSocket으로 업그레이드된다.
3. 데이터 전송 : 클라이언트와 서버는 emit과 on메소드를 사용하여 이벤트 기반으로 데이터를 주고받는다.
4. 연결 유지 : 메시지를 주기적으로 교환하며 연결 상태를 모니터링한다.
5. 연결 종료 : 클라이언트 또는 서버에서 연결이 끊어지면 disconnect이벤트가 발생하고, 필요시 자동 재연결이 시도된다.
2. 프로젝트 기본 설정
서버는 Node.js 프레임워크를 채택했으며, IDE는 vscode를 사용했다.
웹서버는 express를 활용하며, 채팅을 제외한 API는 json-server를 활용하며 실제 DB가 아닌 db.json을 데이터베이스 파일로 사용한다. (회사 과제였는데 이사님께서 json server 쓰라고 하셔서.. 쩔수 없이 노드 썼음 스프링 시켜줘ㅜ)
json server가 궁금하다면 이전 포스팅을 참고하길 바란다.
https://aeeazip.tistory.com/56
[API Mocking] API Mocking Server 구축 (2) - JSON Server
목차1. API Mocking 정의2. JSON Server3. 사용 방법4. 테스트 1. API Mocking 정의API Mocking 정의는 1편에 작성해두었으니 참고하길 바란다. https://aeeazip.tistory.com/55 [API Mocking] API Mocking Server 구축 (1) -
aeeazip.tistory.com
3일 안에 과제용으로 만든 demo라 부끄럽지만 전체 아키텍처는 다음과 같다.

socket.io는 가장 최신 버전인 4.7.5를 사용하며, 이와 맞는 socket.io-client 버전은 2.X 버전이다.
▼ package.json
{
"name": "policesample",
"version": "1.0.0",
"description": "경찰청 상황메신저 파일럿 서버",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "npx nodemon --verbose --exec babel-node index.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"cors": "^2.8.5",
"express": "^4.19.2",
"http": "^0.0.1-security",
"http-status-codes": "^2.3.0",
"json-server": "^0.17.4",
"path": "^0.12.7",
"socket.io": "^4.7.5"
},
"devDependencies": {
"@babel/cli": "^7.24.8",
"@babel/core": "^7.25.2",
"@babel/node": "^7.25.0",
"@babel/preset-env": "^7.25.4",
"nodemon": "^3.1.4"
}
}
서버 실행 명령어는 다음과 같다.
$ npm start
3. Express 및 Socket 설정
프로젝트 폴더 구조는 다음과 같다.
┌── node_modules
├── public
| ├── js
| ├── chat.html
| ├── chatRoom.html
| ├── createRoom.html
| ├── index.html
| └── style.css
├── src
│ ├── config // 설정 파일
│ ├── controller // API 요청 처리
│ ├── router // API 라우팅
│ ├── jsonServer.js // json server 설정
└───└── server.js // express 설정
├── .babelrc
├── .gitignore
├── db.json
├── index.js
├── nodemon.json
├── package.json
├── package-lock.json
└── README.md
먼저 웹서버 실행을 위한 express 설정이 필요하다.
▼ src/server.js
import express from 'express';
import cors from 'cors';
import testRouter from './router/testRouter';
import memberRouter from './router/memberRouter';
import chatRoomRouter from './router/chatRoomRouter';
import { setupSocket } from './config/socket';
const app = express();
const port = 3000;
app.use(express.json()); // request의 본문을 json으로 해석할 수 있도록 함 (JSON 형태의 요청 body를 파싱하기 위함)
app.use(cors()); // cors 허용
app.use(express.urlencoded({ extended: false })); // 단순 객체 문자열 형태로 본문 데이터 해석
app.use(express.static('public')); // public 폴더의 파일을 서빙
app.use('/test', testRouter); // 테스트
app.use('/member', memberRouter); // 회원용
app.use('/chatRoom', chatRoomRouter); // 채팅용
const server = app.listen(port, () => {
console.log(`Express server is running on http://localhost:${port}`);
});
setupSocket(server, app); // Socket.io 셋업
express : port 3000번
json server : port 3001번
포트는 위와 같이 할당해주었고 웹 프론트 구현도 같은 프로젝트에 위치하기 때문에 public 폴더 파일을 서빙해주었다.
server.js 파일 제일 하단에 setupSocket 메소드는 실제 Socket.io 설정을 해주는 부분이다. setupSocket 메소드는 src/config/socket.js에 정의되어있다.
▼ src/config/socket.js
import { Server } from 'socket.io'
export function setupSocket(server, app) {
const io = new Server(server, { path: "/socket.io" });
app.set("io", io);
// 네임스페이스 = 소켓 url에서 엔드 포인트 역할
const chatRoom = io.of("/chatRoom");
const chat = io.of("/chat");
// chatRoom 네임스페이스 전용 이벤트
chatRoom.on("connection", (socket) => {
console.log("chatRoom 네임스페이스 접속");
socket.on("disconnect", () => {
console.log("chatRoom 네임스페이스 접속 해제");
});
});
// chat 네임스페이스 전용 이벤트
chat.on("connection", (socket) => {
console.log("chat 네임스페이스 접속");
// 채팅방 입장
socket.on("joinRoom", (data) => {
const { userId, roomId } = data;
console.log(`${userId}님이 방 #${roomId}에 접속했습니다.`);
socket.join(roomId);
});
// 채팅방 퇴장
socket.on("leaveRoom", (data) => {
const { userId, roomId } = data;
console.log(`${userId}님이 방 #${roomId}을 퇴장했습니다.`);
socket.leave(roomId);
});
socket.on("disconnect", () => {
console.log("chat 네임스페이스 접속 해제");
});
});
}
해당 파일은 소켓 연결 및 해제, 채팅방 입장 및 퇴장에 관한 코드가 담긴 설정 파일이다.
먼저 서버 측에서 ① chat 네임스페이스를 생성하고, ② chat 네임스페이스의 connection, joinRoom, leaveRoom, disconnect 등의 이벤트를 수신한다.
이벤트명 | 이벤트 설명 |
connection / disconnect | 소켓 연결 / 소켓 연결 해제 |
joinRoom | 채팅방 입장 |
newChat | 새로운 채팅 전송 및 수신 |
leaveRoom | 채팅방 퇴장 |
이벤트를 하나씩 뜯어보자 ♥
① 소켓 연결 : connection
// chat 네임스페이스 전용 이벤트
chat.on("connection", (socket) => {
console.log("chat 네임스페이스 접속\n");
});
사용자가 채팅방 목록 중 하나를 선택해 특정 채팅방에 입장을 요청
→ 클라이언트는 소켓을 초기화 + socket.connect()을 호출 = connection 이벤트를 발생시킴
즉 connection 이벤트를 emit 하게 된다.
서버에서는 connection 이벤트를 수신하여 chat 네임스페이스로 소켓을 연결해준다.
② 채팅방 입장 : joinRoom
// chat 네임스페이스 전용 이벤트
chat.on("connection", (socket) => {
console.log("chat 네임스페이스 접속\n");
// 채팅방 입장
socket.on("joinRoom", (data) => {
const { userId, roomId } = data;
console.log("-------------------- 채팅방 입장 start -----------------------")
console.log(`${userId}님이 방 #${roomId}에 접속했습니다.`);
socket.join(roomId);
console.log(`방 #${roomId}의 현재 접속자 수: ${io.of("/chat").adapter.rooms.get(roomId)?.size}`);
console.log("-------------------- 채팅방 입장 end -------------------------\n")
});
});
사용자가 채팅방 목록 중 하나를 선택해 특정 채팅방에 입장
→ 클라이언트는 joinRoom 이벤트를 발생시킴
즉 joinRoom 이벤트를 emit 하게 된다.
이때 사용자Id, 채팅방Id를 data에 담고 이벤트를 emit하면, 서버에서는 joinRoom 이벤트를 수신하여 해당 사용자의 소켓을 “chat 네임스페이스 > 룸 #채팅방Id”로 join 시켜 채팅방에 입장시켜준다.
③ 새로운 채팅 전송 : newChat
▼ src/controller/chatRoomController.js
/*
* API No. 3
* API Name : 채팅 메세지 전송 API
* [POST] /chatRoom/:cRoomId/chat
*/
export const postChatMessage = async (req, res, next) => {
try{
console.log("채팅 메시지 전송 API 실행");
const params = validateCheck(req.body, { sendUserId: true, cRoomId: false, msgType: true, msgContent: true });
if (!params) {
return res.send(errResponse(status.INVALID_INPUT_TYPE));
}
const io = req.app.get("io"); // io 획득
// 1. db.json에 데이터 저장
const chatData = {
...req.body,
sendTime: new Date().toISOString()
};
const newChat = await jsonCall('post', `${JSON_SERVER_BASE_URL}/chat`, chatData);
console.log("newChat : " + newChat);
// 2. chat 네임스페이스
const cRoomId = req.body.cRoomId;
console.log("대상 채팅방 cRoomId : " + cRoomId);
console.log(`방 ${cRoomId}로 newChat 이벤트 발행:`, newChat);
io.of("/chat").to(cRoomId).emit("newChat", newChat);
return res.send(response({ msgId: newChat.id, sendTime: newChat.sendTime }));
} catch(error) {
console.error(error);
return res.send(errResponse(status.REQUEST_POST_CHAT_FAIL));
}
}
사용자가 실제로 채팅 내용을 작성하고 보내기 버튼을 클릭하면 채팅 전송 API를 호출한다.
서버의 채팅 전송 API에서는 다음과 같은 과정을 거친다.
① db.json에 채팅 데이터 저장
② “chat 네임스페이스의 룸 #채팅방Id”에 newChat 이벤트 발생 (newChat 이벤트를 emit)
위의 코드에서 실제로 소켓과 관련된 부분은 4줄 뿐이다.
const io = req.app.get("io"); // io 획득
const chatData = {
...req.body,
sendTime: new Date().toISOString()
};
const newChat = await jsonCall('post', `${JSON_SERVER_BASE_URL}/chat`, chatData);
io.of("/chat").to(cRoomId).emit("newChat", newChat);
클라이언트에서는 newChat 이벤트를 수신하여, 해당 채팅방에 접속한 모든 사용자들에게 새롭게 수신한 채팅 메시지를 화면에 퍼블리싱한다.
④ 채팅방 퇴장 : leaveRoom
// chat 네임스페이스 전용 이벤트
chat.on("connection", (socket) => {
console.log("chat 네임스페이스 접속\n");
// 채팅방 퇴장
socket.on("leaveRoom", (data) => {
const { userId, roomId } = data;
console.log("-------------------- 채팅방 퇴장 start -----------------------")
console.log(`${userId}님이 방 #${roomId}을 퇴장했습니다.`);
socket.leave(roomId);
console.log("-------------------- 채팅방 퇴장 end -------------------------\n")
});
});
사용자가 채팅방 화면에서 벗어나거나, 앱 종료
→ 클라이언트에서 leaveRoom 이벤트를 발생시킴
즉, leaveRoom 이벤트를 emit 하게 된다.
서버에서는 leaveRoom 이벤트를 수신하여 해당 사용자의 소켓이 속해있는 룸에서 퇴장시켜준다.
⑤ 소켓 연결 해제 : disconnect
// chat 네임스페이스 전용 이벤트
chat.on("connection", (socket) => {
console.log("chat 네임스페이스 접속\n");
socket.on("disconnect", () => {
console.log("chat 네임스페이스 접속 해제\n");
});
});
사용자가 앱을 종료
→ 클라이언트는 socket.disconnect()를 호출 = disconnect 이벤트를 발생시킴
즉 disconnect 이벤트를 emit 하게 된다.
서버에서는 disconnect 이벤트를 수신하여 chat 네임스페이스의 소켓 연결을 해제시킨다.
'Framework > Node.js' 카테고리의 다른 글
[Node.js] MongoDBAtlas 생성 및 연결과 모델 생성 (0) | 2024.07.03 |
---|
목차
1. Socket.io에 대해
- Socket.io 특징
- Socket.io 그룹화 개념
- Socket.io 핵심 메소드
2. 프로젝트 기본 설정
3. Express 및 Socket 설정
4. 웹 프론트
- 웹 프론트 구현
- 웹 실시간 채팅 테스트
5. 네이티브 앱(AOS) 프론트
- 앱 프론트 구현
- 앱 실시간 채팅 테스트
6. 크로스 플랫폼 (Flutter) 프론트
- 크로스 플랫폼 구현
- 크로스 플랫폼 실시간 채팅 테스트
1. Socket.io에 대해

Socket.io는 클라이언트와 서버 간의 저지연, 양방향 통신을 가능하게 하는 JS 기반 라이브러리이다.
클라이언트와 서버 간의 메시지 전송이 이벤트 기반으로 이루어지며 이벤트 핸들러를 통해 특정 이벤트에 반응하여 데이터를 처리할 수 있다.
채팅하면 WebSocket, WebSocket하믄 채팅 아닌가? (무식티비)
채팅의 국룰은 WebSocket이 맞다. 그런데 WebSocket과 Socket.io는 동일한 개념이 아니다.
HTML5 WebSocket은 매우 유용한 기술이지만 오래된 브라우저의 경우 지원하지 않는 경우가 있다.
Socket.io는 WebSocket과 달리 브라우저, 디바이스에 상관없이 모든 환경에서 실시간 통신을 지원한다. 또한 WebSocket이 동작하지 않는 환경에서도 HTTP Long Polling, HTTP Polling 등 기타 통신을 지원하고 연결이 끊기면 자동으로 재연결을 시도한다.

Socket.io에는 WebSocket에는 없는 네임스페이스, 룸 개념이 있다.
📌 [ Namespace : 네임스페이스 ]
- 네임스페이스는 Socket.IO에서 동일한 서버 내에서 여러 개의 독립된 연결 공간을 제공
- 기본 네임스페이스는 '/'
- 추가적인 네임스페이스를 정의함으로써, 서로 다른 용도의 연결을 구분 가능
예를 들어, 채팅 애플리케이션에서 일반 채팅, 관리자 채팅, 알림 서비스 등을 각각의 네임스페이스로 분리하여 관리할 수 있다.
📌 [ Room : 룸 ]
- 룸은 네임스페이스 내에서 추가적인 그룹화를 제공하는 기능
- 룸을 사용하면, 같은 네임스페이스 내에서 특정 그룹에만 메시지를 전송 가능
- 룸은 소켓을 그룹으로 나누고, 특정 룸에 속한 소켓들에게만 이벤트를 전송하거나 *브로드캐스트 가능
- 룸 이름은 문자열, 소켓은 여러 룸에 참여 가능
브로드캐스트(Broadcast)란?
서버 측에서 특정 클라이언트가 아닌 모든 클라이언트에게 메시지를 보내기 위해 사용되는 기능으로
현재 연결된 클라이언트를 제외한 나머지 모든 클라이언트로 메시지를 전달할 때 유용하다.
마지막으로 네임스페이스와 룸을 표로 비교해보자.
특징 | Namespace | Room |
용도 | Namespace 전체를 구분하여 독립된 연결 관리 | Namespace 내에서 소켓을 그룹화 |
관리 범위 | 서버와 클라이언트 모두에서 관리 가능 | 서버 측에서만 관리 가능 |
유연성 | 다양한 서비스, 기능을 독립적으로 운영 가능 | Room에 속한 그룹만 이벤트 전송 |
이벤트 범위 | 특정 Namespace에 있는 모든 소켓 | 특정 Room에 있는 소켓에만 전송 |
다음으로 Socket.io의 핵심 메소드를 알아보자.
Socket.io에서 클라이언트와 서버 간의 이벤트 기반 통신을 구현하는데 사용되는 핵심 메소드로 emit, on이 있다.
emit 메소드는 특정 이벤트를 발생시키고, 해당 이벤트와 관련된 데이터를 전송하는 역할을 한다.
즉, 서버나 클라이언트가 상대방에게 메시지를 보낼 때 사용하는 메소드이다.
▼ emit 사용 예시
// eventName : 전송하려는 이벤트명
// data : 이벤트와 함께 전송할 데이터
socket.emit('eventName', data);
on 메소드는 특정 이벤트를 수신하고, 그에 대한 처리를 수행하는 이벤트 리스너를 설정하는 메소드이다.
클라이언트와 서버 모두에서 상대방이 발생시킨 이벤트를 듣고 반응할 때 사용한다.
▼ on 사용 예시
// eventName : 수신하려는 이벤트명 (emit한 이벤트명과 일치해야함)
// data : 이벤트와 함께 전송된 데이터를 처리하기 위한 콜백 함수 매개변수
socket.on('eventName', (data) =>{
// 이벤트 발생 시 실행할 코드
});
전체적인 흐름은 다음과 같다.
연결 시도 → 초기 연결 → 데이터 전송(emit & on) → 연결 유지 → 연결 종료 |
1. 연결 시도 : 클라이언트가 서버에 연결 요청을 보내고, 서버는 클라이언트에 대해 소켓을 생성하고 고유한 ID를 할당한다.
2. 초기 연결 : 폴링을 통해 연결이 이루어진 후, 가능한 경우 WebSocket으로 업그레이드된다.
3. 데이터 전송 : 클라이언트와 서버는 emit과 on메소드를 사용하여 이벤트 기반으로 데이터를 주고받는다.
4. 연결 유지 : 메시지를 주기적으로 교환하며 연결 상태를 모니터링한다.
5. 연결 종료 : 클라이언트 또는 서버에서 연결이 끊어지면 disconnect이벤트가 발생하고, 필요시 자동 재연결이 시도된다.
2. 프로젝트 기본 설정
서버는 Node.js 프레임워크를 채택했으며, IDE는 vscode를 사용했다.
웹서버는 express를 활용하며, 채팅을 제외한 API는 json-server를 활용하며 실제 DB가 아닌 db.json을 데이터베이스 파일로 사용한다. (회사 과제였는데 이사님께서 json server 쓰라고 하셔서.. 쩔수 없이 노드 썼음 스프링 시켜줘ㅜ)
json server가 궁금하다면 이전 포스팅을 참고하길 바란다.
https://aeeazip.tistory.com/56
[API Mocking] API Mocking Server 구축 (2) - JSON Server
목차1. API Mocking 정의2. JSON Server3. 사용 방법4. 테스트 1. API Mocking 정의API Mocking 정의는 1편에 작성해두었으니 참고하길 바란다. https://aeeazip.tistory.com/55 [API Mocking] API Mocking Server 구축 (1) -
aeeazip.tistory.com
3일 안에 과제용으로 만든 demo라 부끄럽지만 전체 아키텍처는 다음과 같다.

socket.io는 가장 최신 버전인 4.7.5를 사용하며, 이와 맞는 socket.io-client 버전은 2.X 버전이다.
▼ package.json
{
"name": "policesample",
"version": "1.0.0",
"description": "경찰청 상황메신저 파일럿 서버",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "npx nodemon --verbose --exec babel-node index.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"cors": "^2.8.5",
"express": "^4.19.2",
"http": "^0.0.1-security",
"http-status-codes": "^2.3.0",
"json-server": "^0.17.4",
"path": "^0.12.7",
"socket.io": "^4.7.5"
},
"devDependencies": {
"@babel/cli": "^7.24.8",
"@babel/core": "^7.25.2",
"@babel/node": "^7.25.0",
"@babel/preset-env": "^7.25.4",
"nodemon": "^3.1.4"
}
}
서버 실행 명령어는 다음과 같다.
$ npm start
3. Express 및 Socket 설정
프로젝트 폴더 구조는 다음과 같다.
┌── node_modules
├── public
| ├── js
| ├── chat.html
| ├── chatRoom.html
| ├── createRoom.html
| ├── index.html
| └── style.css
├── src
│ ├── config // 설정 파일
│ ├── controller // API 요청 처리
│ ├── router // API 라우팅
│ ├── jsonServer.js // json server 설정
└───└── server.js // express 설정
├── .babelrc
├── .gitignore
├── db.json
├── index.js
├── nodemon.json
├── package.json
├── package-lock.json
└── README.md
먼저 웹서버 실행을 위한 express 설정이 필요하다.
▼ src/server.js
import express from 'express';
import cors from 'cors';
import testRouter from './router/testRouter';
import memberRouter from './router/memberRouter';
import chatRoomRouter from './router/chatRoomRouter';
import { setupSocket } from './config/socket';
const app = express();
const port = 3000;
app.use(express.json()); // request의 본문을 json으로 해석할 수 있도록 함 (JSON 형태의 요청 body를 파싱하기 위함)
app.use(cors()); // cors 허용
app.use(express.urlencoded({ extended: false })); // 단순 객체 문자열 형태로 본문 데이터 해석
app.use(express.static('public')); // public 폴더의 파일을 서빙
app.use('/test', testRouter); // 테스트
app.use('/member', memberRouter); // 회원용
app.use('/chatRoom', chatRoomRouter); // 채팅용
const server = app.listen(port, () => {
console.log(`Express server is running on http://localhost:${port}`);
});
setupSocket(server, app); // Socket.io 셋업
express : port 3000번
json server : port 3001번
포트는 위와 같이 할당해주었고 웹 프론트 구현도 같은 프로젝트에 위치하기 때문에 public 폴더 파일을 서빙해주었다.
server.js 파일 제일 하단에 setupSocket 메소드는 실제 Socket.io 설정을 해주는 부분이다. setupSocket 메소드는 src/config/socket.js에 정의되어있다.
▼ src/config/socket.js
import { Server } from 'socket.io'
export function setupSocket(server, app) {
const io = new Server(server, { path: "/socket.io" });
app.set("io", io);
// 네임스페이스 = 소켓 url에서 엔드 포인트 역할
const chatRoom = io.of("/chatRoom");
const chat = io.of("/chat");
// chatRoom 네임스페이스 전용 이벤트
chatRoom.on("connection", (socket) => {
console.log("chatRoom 네임스페이스 접속");
socket.on("disconnect", () => {
console.log("chatRoom 네임스페이스 접속 해제");
});
});
// chat 네임스페이스 전용 이벤트
chat.on("connection", (socket) => {
console.log("chat 네임스페이스 접속");
// 채팅방 입장
socket.on("joinRoom", (data) => {
const { userId, roomId } = data;
console.log(`${userId}님이 방 #${roomId}에 접속했습니다.`);
socket.join(roomId);
});
// 채팅방 퇴장
socket.on("leaveRoom", (data) => {
const { userId, roomId } = data;
console.log(`${userId}님이 방 #${roomId}을 퇴장했습니다.`);
socket.leave(roomId);
});
socket.on("disconnect", () => {
console.log("chat 네임스페이스 접속 해제");
});
});
}
해당 파일은 소켓 연결 및 해제, 채팅방 입장 및 퇴장에 관한 코드가 담긴 설정 파일이다.
먼저 서버 측에서 ① chat 네임스페이스를 생성하고, ② chat 네임스페이스의 connection, joinRoom, leaveRoom, disconnect 등의 이벤트를 수신한다.
이벤트명 | 이벤트 설명 |
connection / disconnect | 소켓 연결 / 소켓 연결 해제 |
joinRoom | 채팅방 입장 |
newChat | 새로운 채팅 전송 및 수신 |
leaveRoom | 채팅방 퇴장 |
이벤트를 하나씩 뜯어보자 ♥
① 소켓 연결 : connection
// chat 네임스페이스 전용 이벤트
chat.on("connection", (socket) => {
console.log("chat 네임스페이스 접속\n");
});
사용자가 채팅방 목록 중 하나를 선택해 특정 채팅방에 입장을 요청
→ 클라이언트는 소켓을 초기화 + socket.connect()을 호출 = connection 이벤트를 발생시킴
즉 connection 이벤트를 emit 하게 된다.
서버에서는 connection 이벤트를 수신하여 chat 네임스페이스로 소켓을 연결해준다.
② 채팅방 입장 : joinRoom
// chat 네임스페이스 전용 이벤트
chat.on("connection", (socket) => {
console.log("chat 네임스페이스 접속\n");
// 채팅방 입장
socket.on("joinRoom", (data) => {
const { userId, roomId } = data;
console.log("-------------------- 채팅방 입장 start -----------------------")
console.log(`${userId}님이 방 #${roomId}에 접속했습니다.`);
socket.join(roomId);
console.log(`방 #${roomId}의 현재 접속자 수: ${io.of("/chat").adapter.rooms.get(roomId)?.size}`);
console.log("-------------------- 채팅방 입장 end -------------------------\n")
});
});
사용자가 채팅방 목록 중 하나를 선택해 특정 채팅방에 입장
→ 클라이언트는 joinRoom 이벤트를 발생시킴
즉 joinRoom 이벤트를 emit 하게 된다.
이때 사용자Id, 채팅방Id를 data에 담고 이벤트를 emit하면, 서버에서는 joinRoom 이벤트를 수신하여 해당 사용자의 소켓을 “chat 네임스페이스 > 룸 #채팅방Id”로 join 시켜 채팅방에 입장시켜준다.
③ 새로운 채팅 전송 : newChat
▼ src/controller/chatRoomController.js
/*
* API No. 3
* API Name : 채팅 메세지 전송 API
* [POST] /chatRoom/:cRoomId/chat
*/
export const postChatMessage = async (req, res, next) => {
try{
console.log("채팅 메시지 전송 API 실행");
const params = validateCheck(req.body, { sendUserId: true, cRoomId: false, msgType: true, msgContent: true });
if (!params) {
return res.send(errResponse(status.INVALID_INPUT_TYPE));
}
const io = req.app.get("io"); // io 획득
// 1. db.json에 데이터 저장
const chatData = {
...req.body,
sendTime: new Date().toISOString()
};
const newChat = await jsonCall('post', `${JSON_SERVER_BASE_URL}/chat`, chatData);
console.log("newChat : " + newChat);
// 2. chat 네임스페이스
const cRoomId = req.body.cRoomId;
console.log("대상 채팅방 cRoomId : " + cRoomId);
console.log(`방 ${cRoomId}로 newChat 이벤트 발행:`, newChat);
io.of("/chat").to(cRoomId).emit("newChat", newChat);
return res.send(response({ msgId: newChat.id, sendTime: newChat.sendTime }));
} catch(error) {
console.error(error);
return res.send(errResponse(status.REQUEST_POST_CHAT_FAIL));
}
}
사용자가 실제로 채팅 내용을 작성하고 보내기 버튼을 클릭하면 채팅 전송 API를 호출한다.
서버의 채팅 전송 API에서는 다음과 같은 과정을 거친다.
① db.json에 채팅 데이터 저장
② “chat 네임스페이스의 룸 #채팅방Id”에 newChat 이벤트 발생 (newChat 이벤트를 emit)
위의 코드에서 실제로 소켓과 관련된 부분은 4줄 뿐이다.
const io = req.app.get("io"); // io 획득
const chatData = {
...req.body,
sendTime: new Date().toISOString()
};
const newChat = await jsonCall('post', `${JSON_SERVER_BASE_URL}/chat`, chatData);
io.of("/chat").to(cRoomId).emit("newChat", newChat);
클라이언트에서는 newChat 이벤트를 수신하여, 해당 채팅방에 접속한 모든 사용자들에게 새롭게 수신한 채팅 메시지를 화면에 퍼블리싱한다.
④ 채팅방 퇴장 : leaveRoom
// chat 네임스페이스 전용 이벤트
chat.on("connection", (socket) => {
console.log("chat 네임스페이스 접속\n");
// 채팅방 퇴장
socket.on("leaveRoom", (data) => {
const { userId, roomId } = data;
console.log("-------------------- 채팅방 퇴장 start -----------------------")
console.log(`${userId}님이 방 #${roomId}을 퇴장했습니다.`);
socket.leave(roomId);
console.log("-------------------- 채팅방 퇴장 end -------------------------\n")
});
});
사용자가 채팅방 화면에서 벗어나거나, 앱 종료
→ 클라이언트에서 leaveRoom 이벤트를 발생시킴
즉, leaveRoom 이벤트를 emit 하게 된다.
서버에서는 leaveRoom 이벤트를 수신하여 해당 사용자의 소켓이 속해있는 룸에서 퇴장시켜준다.
⑤ 소켓 연결 해제 : disconnect
// chat 네임스페이스 전용 이벤트
chat.on("connection", (socket) => {
console.log("chat 네임스페이스 접속\n");
socket.on("disconnect", () => {
console.log("chat 네임스페이스 접속 해제\n");
});
});
사용자가 앱을 종료
→ 클라이언트는 socket.disconnect()를 호출 = disconnect 이벤트를 발생시킴
즉 disconnect 이벤트를 emit 하게 된다.
서버에서는 disconnect 이벤트를 수신하여 chat 네임스페이스의 소켓 연결을 해제시킨다.
'Framework > Node.js' 카테고리의 다른 글
[Node.js] MongoDBAtlas 생성 및 연결과 모델 생성 (0) | 2024.07.03 |
---|