반응형
해당 글에서는 소켓 통신을 위해 Socket.io를 기반으로 소켓 서버를 구현하고, 채팅을 하는 예시를 구축해 봅니다.
💡 [참고] 아래의 Socket.io와 관련된 글들을 참고하시면 도움이 됩니다
분류 | 링크 |
Socket.io 기반 소켓 서버 구축 방법 -1 : 구성 요소 및 흐름 + React 기반 채팅 화면 구성 | https://adjh54.tistory.com/548 |
Socket.io 기반 소켓 서버 구축 방법 -2 : Room, Namespace 별 소켓 통신 방법 | https://adjh54.tistory.com/549 |
Socket.io 기반 소켓 서버 구축 방법 -3: PM2 클러스터링 + Redis 기반 분산 처리 | https://adjh54.tistory.com/567 |
PM2(Process Manager 2) 이해하고 주요 특징 알아보기 | https://adjh54.tistory.com/551 |
Github : Socket.io 기반 웹 소켓 소스코드 | https://github.com/adjh54ir/blog-codes/tree/main/node-wss-server |
Github : Socket.io를 이용하는 웹 클라이언트 소스코드(채팅) | https://github.com/adjh54ir/blog-codes/tree/main/react-chatting |
1) 소켓 통신(Socket Communication)
💡 소켓 통신(Socket Communication)
- 네트워크에서 두 컴퓨터 간의 ‘실시간 양방향 통신’을 제공하는 기술을 의미합니다. 양방향 통신은 송신자 측이 데이터를 수신할 수도 있으며, 수신자 측이 데이터를 송신할 수 있는 구조를 가집니다.
-두 컴퓨터 간에는 '특정 IP 주소와 포트번호'의 조합으로 네트워크 간의 연결을 수행하며, 수신자(클라이언트)는 데이터를 요청하면 송신자(서버)에서는 요청에 대한 응답을 제공해 주는 '클라이언트-서버' 모델을 사용하여 데이터를 송수신할 수 있습니다.
- 소켓 통신은 'TCP'와 'UDP'라는 두 가지 주요 프로토콜을 사용할 수 있습니다.
- 이러한 프로토콜을 통해 웹 서버, 이메일 서버, 데이터베이스 서버 등 다양한 응용 프로그램에서 사용되며, 실시간 통신, 스트리밍, 온라인 게임 등에서도 사용됩니다.
💡 [참고] 소켓 통신에 대해 상세히 궁금하시면 아래의 글을 참고하시면 도움이 됩니다.
2) Socket.io
💡 Socket.io
- 서버-클라이언트 간의 실시간 양방향 통신을 가능하게 하는 라이브러리입니다. 주로 웹 애플리케이션에서 클라이언트와 서버 간의 실시간 데이터 전송을 위해 사용이 됩니다.
- 이는 WebSocket 프로토콜을 사용하지만, 브라우저 호환성을 위해 필요한 경우에는 풀백(fallback) 메커니즘을 사용합니다.
[ 더 알아보기 ]
💡 풀백 메커니즘
- 주어진 기술이나 프로토콜이 작동하지 않을 경우, 대체할 수 있는 다른 방법을 사용하는 것을 의미합니다.
- 예를 들어, Socket.io는 WebSocket을 기본적으로 사용하지만, 만약 클라이언트의 브라우저가 WebSocket을 지원하지 않는다면, HTTP 롱 폴링 등 다른 통신 방식을 사용하여 여전히 실시간 통신을 가능하게 합니다.
1. 웹 소켓(Socket.io)에서의 송신자(Server) - 수신자(Client) 관계
💡 웹 소켓(Socket.io)에서의 송신자(Server) - 수신자(Client) 관계
- Socket.io에서는 양방향 통신을 지원합니다. 이는 송신자와 수신자에서 통신을 주고받는 관계로 이해할 수 있습니다.
💡 송신자(Server)
- 클라이언트와의 실시간 연결을 관리하고, 양방향 데이터 전송을 처리합니다.
- 송신자(Server)는 클라이언트로부터 메시지를 수신하고, 필요에 따라 클라이언트에게 메시지를 전송할 수 있습니다.
💡 수신자(Client)
- 서버와 실시간으로 연결되어 데이터를 주고받는 역할을 합니다.
- 수신지(Client)는 사용자의 입력이나 이벤트를 서버로 전송하고, 서버로부터 수신된 데이터를 처리하여 사용자에게 표시합니다.
💡 [참고] Socket Server로 구축할 수 있는 플랫폼입니다.
- 소켓 서버로 구축할 수 있는 플랫폼들을 의미합니다.
💡 [참고] Socket Client로 구축할 수 있는 플랫폼입니다.
- 소켓 서버에 연결하여 소켓 통신을 할 수 있는 클라이언트의 플랫폼을 의미합니다.
2. 주요 특징
특징 | 설명 |
실시간 통신 | WebSocket을 기반으로 클라이언트와 서버 간의 실시간 양방향 통신을 지원합니다. |
브라우저 호환성 | 다양한 브라우저와 호환되며, WebSocket을 지원하지 않는 브라우저에서는 폴백 메커니즘을 통해 통신을 유지합니다. |
자동 재연결 | 연결이 끊어졌을 때 자동으로 재연결을 시도합니다. |
룸 및 네임스페이스 | 특정 그룹의 클라이언트에게만 메시지를 전송할 수 있는 룸(Room)과 네임스페이스(Namespace)를 제공합니다. |
이벤트 기반 | 이벤트 기반의 프로그래밍 모델을 제공하여, 특정 이벤트 발생 시 특정 기능을 수행할 수 있습니다. |
3. Socket.io 주요 메서드
메서드 | 분류 | 설명 | 예시 |
io() | 초기화 | 클라이언트 측에서 서버와의 연결을 초기화하는 메서드입니다. | const io = require('socket.io')(3000); |
open() | 소켓 서버 연결 | 서버와의 연결을 시작합니다. | io.open(); |
close() | 소켓 서버 연결 종료 | 서버와의 연결을 종료합니다. | io.close(); |
connect() | 소켓 서버 수동 연결 | 서버와의 연결을 수동으로 시작합니다. | socket.connect(); |
disconnect() | 소켓 서버 수동 연결 종료 | 서버와의 연결을 수동으로 종료합니다. | socket.disconnect(); |
on(event, callback) | 이벤트 수신 | 특정 이벤트를 ‘수신’하여 처리하는 이벤트 리스너를 설정합니다. | socket.on('message', (data) => { console.log(data); }); |
emit(event, data) | 이벤트 전송 | 서버로 특정 이벤트와 데이터를 ‘전송’합니다. | socket.emit('message', 'Hello World!'); |
timeout(time).emit(event, data) | 특정 시간 이벤트 전송 | 연결 시간 초과 시 특정 이벤트와 데이터를 서버로 전송합니다. | socket.timeout(5000).emit('message', 'Hello World!'); |
onAny(callback) | 이벤트 수신 | 모든 이벤트를 듣고 처리하는 이벤트 리스너를 설정합니다. | socket.onAny((event, data) => { console.log(event, data); }); |
once(event, callback) | 이벤트 수신 | 특정 이벤트를 한 번만 듣고 처리하는 이벤트 리스너를 설정합니다. | socket.once('message', (data) => { console.log(data); }); |
off(event, callback) | 이벤트 제거 | 특정 이벤트 리스너를 제거합니다. | socket.off('message', callbackFunction); |
offAny(callback) | 이벤트 제거 | 모든 이벤트 리스너를 제거합니다. | socket.offAny(callbackFunction); |
removeListener(event, callback) | 이벤트 제거 | 특정 이벤트 리스너를 제거합니다 | socket.removeListener('message', callbackFunction); |
removeAllListeners(event) | 이벤트 제거 | 특정 이벤트에 대한 모든 리스너를 제거합니다. | socket.removeAllListeners('message'); |
of(namespace) | 네임스페이스 가입 | 특정 네임스페이스를 지정하여 이벤트를 수신하거나 전송합니다. | const nsp = io.of('/mynamespace'); |
of(namespace).on(event, callback) | 네임스페이스 이벤트 수신 | 특정 네임스페이스에서 이벤트를 수신하여 처리하는 이벤트 리스너를 설정합니다. | io().of(”mynamespace”).on('message', (data) => { console.log(data); }); |
of(namespace).emit(event, data) | 네임스페이스 이벤트 전송 | 특정 네임스페이스로 이벤트와 데이터를 전송합니다. | io().of(”mynamespace”).emit('message', 'Hello World!'); |
join(room) | 룸 가입 | 소켓을 특정 방에 가입시킵니다. | socket.join('room1'); |
leave(room) | 룸 나가기 | 소켓을 특정 방에서 탈퇴시킵니다. | socket.leave('room1'); |
to(room).emit(event, data) | 룸 이벤트 전송 | 특정 룸(Room)에 속한 클라이언트들에게 이벤트와 데이터를 전송하는 데 사용됩니다. | io.to('room1').emit('message', 'Hello Room!'); |
4. 주요 구성요소: Room, Namespace
4.1. Room
💡 Room
- Socket.io에서 클라이언트들을 그룹으로 묶는 기능을 제공합니다. 이 클라이언트는 여러 Room에 참여할 수 있으며, 서버는 특정 Room에 속한 클라이언트들에게만 메시지를 전송할 수 있습니다.
- 이를 통해 메시지를 특정 그룹의 클라이언트에게만 효율적으로 전달할 수 있습니다.
💡 Room 개념 예시 : 방 입장 및 메시지 전달 과정
- 소켓 서버와 클라이언트 A, B, C 간의 관계에서 사용됩니다.
1. 클라이언트 A와 B는 소켓이 연결된 이후 "myroom"이라는 Room에 참여합니다(Join).
2. 따라서 클라이언트 A와 B는 "myroom"이라는 그룹에 속하게 됩니다.
3. 이제 서버에서 "myroom" 그룹으로 특정 메시지 이벤트와 메시지를 전송합니다.
4. 해당 "myroom"에 속한 클라이언트 A와 B에게 메시지가 ‘일괄적’으로 전송됩니다.
io.on("connection", socket => {
// 클라이언트를 특정 Room에 참여시키기
socket.join('myroom');
}
// 특정 Room에 있는 모든 클라이언트(클라이언트 A, B)에게 메시지 전송
io.to('myroom').emit('message', 'Hello My Room');
// or
// 여러 room1, room2, room3에 속해 있는 클라이언트 들에게 모두 메시지 전송
io.to("room1").to("room2").to("room3").emit("some event");
💡 Room 방 나가기 예시 : 방 나가기 및 메시지 전달
- 소켓 서버와 클라이언트 A, B, C 간의 관계에서 사용됩니다. 이전 관계에서는 클라이언트 A, B가 myroom이라는 그룹에 소속되어 있었고, 클라이언트 A는 해당 그룹을 나가게 됩니다.
1. 기존의 클라이언트 A와 B는 “myroom”이라는 Room에 참여했었습니다. 그런데, 클라이언트 A가 그룹을 나갑니다.
2. 서버는 “myroom” 그룹으로 특정 메시지와 이벤트를 전송합니다.
3. 해당 “myroom” 그룹에 속한 클라이언트 B에게만 메시지가 전송됩니다.
io.on("connection", socket => {
// 클라이언트를 특정 Room에서 나가게 하기
socket.leave('myroom');
}
// 특정 Room에 있는 모든 클라이언트(클라이언트 A, B)에게 메시지 전송
io.to('myroom').emit('message', 'Hello My Room');
4.2. Namespace
💡 Namespace
- Socket.io에서 기본 네임스페이스(/) 외에 추가적인 네임스페이스를 생성하여 클라이언트 연결을 분리하는 기능을 제공합니다. 이를 통해 서로 다른 네임스페이스 간에 독립적인 이벤트 처리가 가능합니다.
- 예를 들어, 채팅 애플리케이션에서 일반 채팅과 관리자 채팅을 분리할 때 이용합니다. 또는 게임에서 로비와 게임 플레이 구역을 분리할 때 이용됩니다.
💡 Socket.io 네임스페이스
- “admin”이라는 네임스페이스를 생성하고, 해당 네임 스페이스에 이벤트 리스너를 설정하는 예시입니다
1. 기존 default namespace가 아닌 별도 구성한 “admin”이라는 네임스페이스로 연결을 합니다.
2. 연결이 완료되어 발생하는 소켓을 기반으로 message라는 이벤트를 등록(수신) 설정합니다.
3. 클라이언트로부터 “admin” 네임스페이스의 “message” 이벤트가 발생하면 해당 이벤트에 데이터 로그가 출력됩니다.
// 새로운 네임스페이스 생성
const adminNamespace = io.of('/admin');
// 네임스페이스 내에서 이벤트 리스너 설정
adminNamespace.on('connection', (socket) => {
console.log('Admin connected');
socket.on('message', (data) => {
console.log('Admin message:', data);
});
});
3) Socket.io 기반 초기 구축 및 연결
1. Node 서버 환경 구축
💡 Node 서버 환경 구축
- npm init 명령어를 통해서 package.json 파일을 구성합니다.
# package.json init
$ npm init
2. socket.io 및 필요 라이브러리를 추가해 줍니다.
💡 socket.io 및 필요 라이브러리를 추가해 줍니다.
- socket 통신을 위한 라이브러리로 socket.io를 설치하며 이외에 라이브러리를 추가해 줍니다.
💡cors 라이브러리
- 웹 애플리케이션이 서로 다른 출처(domain, protocol, port)에서 리소스를 요청할 때 보안 상의 이유로 브라우저는 기본적으로 이러한 요청을 차단합니다.
- CORS는 이러한 제한을 우회하여 특정 출처에서의 요청을 허용하도록 설정할 수 있는 메커니즘을 제공합니다.
- Socket.io와 같은 라이브러리를 사용할 때, 클라이언트와 서버가 서로 다른 출처에 있을 경우 CORS 설정이 필요합니다. 이를 통해 클라이언트가 서버에 안전하게 접근할 수 있습니다.
💡 express 라이브러리
- HTTP 서버를 손쉽게 구축할 수 있도록 도와주는 웹 프레임워크입니다.
- Socket.io와 결합하여, 클라이언트가 실시간 통신을 시작하기 전에 HTTP 요청을 통해 초기 연결을 설정하고, 이후 WebSocket으로 업그레이드하여 실시간 통신을 이어나갈 수 있습니다.
# socket.io install
$ npm i socket.io cors express
3. 루트 경로에 app.js 파일을 생성하고 구성해 줍니다.
💡 루트 경로에 app.js 파일을 생성하고 구성해줍니다
1. WebSocket Server 설정 : port, cors 지정
- 해당 서버로 접속할 포트를 지정하고, cors()를 통해서 다른 도메인에서의 모든 요청에 대해서 허용합니다.
2. WebSocket Server 설정 값 및 socket.io 구성
- HTTP 서버를 생성하고 지정된 포트로 접근 가능하도록 설정합니다.
- 생성된 HTTP 서버를 기반으로 소켓 서버를 설정하고 socket.io에 대한 cors에 대해 모두 허용을 설정합니다.
3. 루트 엔드포인트로 접속 시 health Check 수행
- 루트 경로(”/”)로 접근시 “Hello world!” 메시지를 출력하고 로그를 출력합니다.
4. 소켓 연결 시작 - io.on("connection") : 클라이언트가 소켓 서버에 연결되었을 때 실행됩니다.
- socket.on("disconnect"): 클라이언트가 연결 해제되었을 때 실행됩니다.
// import library
const app = require("express")(); // 외부 라이브러리 사용
const cors = require("cors"); // 외부 라이브러리 사용
const http = require("http"); // Node 제공 모듈 사용
/*
* 1. WebSocket Server 설정 : port, cors 지정
*/
const port = 5001;
app.set("port", port); // 웹 소켓 포트를 지정합니다
app.use(cors({ origin: "*" }));
// cors를 지정해줍니다.
/*
* 2. WebSocket Server 설정값 및 socket.io 구성
*/
const server = http.createServer(app).listen(port); // 설정 값에 따르는 서버 정보 구축
const io = require("socket.io")(server, {
cors: {
origin: "*",
methods: ["*"], // 모든 메서드 허용
},
}); // 설정 값에 따라 socket.io 구축
/*
* 3. 루트 엔드포인트로 접속 시 health Check 수행
*/
app.get("/", (req, res, next) => {
res.send("Hello world!"); // 화면상으로 출력
console.log("====== Health Check ======="); // 로그 출력
});
/*
* 4. 소켓 연결 시작
*/
io.on("connection", (socket) => {
socket.on("message", (msg) => {
io.emit("message", msg);
});
console.log("a user connected");
socket.on("disconnect", () => {
console.log("user disconnected");
});
});
[ 더 알아보기 ]
💡 WebSocket Server 설정에서 cors()를 허용하는데 socket.io 설정에서 cors를 허용하는 이유는?
1. app.use(cors({ origin: "*" }))
- Express.js 서버에서 HTTP 요청에 대한 CORS 설정을 허용합니다. 이는 웹 소켓 연결을 설정하기 전에 초기 HTTP 요청을 허용하는 역할을 합니다.
2. require("socket.io")(server, { cors: { origin: "*", methods: ["*"] } });
- Socket.io 서버에서 WebSocket 연결에 대한 CORS 설정을 허용합니다. 이는 클라이언트가 WebSocket을 통해 서버와 실시간 통신을 할 때 필요한 CORS 설정을 허용하는 역할을 합니다.
4. node 서버를 실행합니다
# 노드서버 실행
$ node app.js
5. 브라우저 접근
💡 브라우저 접근
- http://localhost:5001 포트로 접근하여 실행을 확인합니다.
💡 [참고] 해당 소스코드는 아래의 Git Repository에서 확인이 가능합니다.
4) Socket.io 기반 활용하기 : React 기반 채팅 구성
1. 초기 개발 환경 구축
💡 초기 개발환경 구축
- 아래와 같이 CRA + Typescript 기반으로 React 프로젝트와 Router를 구성하였습니다.
2. 라이브러리 설치
# socket-io-client를 통해서 socket.io 통신을 수행합니다.
$ npm install socket.io-client
[ 더 알아보기 ]
💡 socket.io와 socket.io-client 차이점은?
- socket.io: 서버 측 라이브러리로, Node.js 환경에서 사용됩니다. 서버와 클라이언트 간의 실시간 양방향 통신을 가능하게 하며, WebSocket 프로토콜을 기본으로 사용하지만, 필요에 따라 폴백 메커니즘을 통해 다른 프로토콜을 사용할 수 있습니다.
- socket.io-client: 클라이언트 측 라이브러리로, 브라우저나 Node.js 클라이언트 환경에서 사용됩니다. 서버와의 실시간 연결을 관리하고, 데이터를 주고받을 수 있도록 도와줍니다.
3. component 구성
💡 component 구성
- socket.io-client를 기반으로 Socket에 접속하며 이를 통해 ‘message’라는 값이 발생하였을 때 이벤트를 수행하는 채팅 컴포넌트를 구성합니다.
1. socket.io-client를 통해서 소켓 서버에 연결합니다.
- 사전에 구성한 webScocket 서버를 연결합니다. (* cors 도메인에 대한 허용했기에 접근이 가능합니다.)
2. 상태관리
- message라는 입력하는 메시지 값과 messages라는 누적되는 메시지를 관리하는 상태관리 state를 선언하였습니다.
3. initSocket()
- Socket.io의 특정 이벤트(”message”)가 발생했을 때, 특정 작업(socket.io)을 수행
4. sendMessage()
- 전송 버튼을 눌렀을때 상대방에게 메시지를 전송하는 메서드입니다. - 이는 socket.emit('message', newMessage);을 통해서 메시지를 발생시켜, 소켓 연결이 되어있는 관계에서 메시지를 전달합니다.
import React, { useState, useEffect } from 'react';
import io from 'socket.io-client';
// socket.io-client를 통해서 소켓 서버에 연결합니다.
const socket = io('http://localhost:5001');
const SocketIoComponent = () => {
// 상태 관리
const [message, setMessage] = useState<string>(''); // 입력되는 메시지
const [messages, setMessages] = useState<{ id: string; text: string }[]>([]); // 누적되는 메시지 관리
useEffect(() => {
initSocket(); // Socket.io의 특정 이벤트가 발생했을 때 특정 작업을 수행하도록 등록합니다.
return () => {
socket.off('message');
};
}, []);
/**
* Socket.io의 특정 이벤트가 발생했을 때 특정 작업을 수행하도록 등록합니다.
*/
const initSocket = () => {
// [Socket] "message"가 발생하였을때, 이벤트가 수행합니다.
socket.on('message', (message: { id: string; text: string }) => {
setMessages((prevMessages) => [...prevMessages, message]);
});
};
/**
* 메시지를 전송하는 메서드
* @param e
*/
const sendMessage = (e: React.FormEvent) => {
e.preventDefault();
const newMessage = { id: socket.id, text: message };
// [Socket] 메시지를 전송합니다.
socket.emit('message', newMessage);
setMessage(''); // 전송 이후 입력한 텍스트를 지워줍니다.
};
return (
<div>
<h1>Chat</h1>
<div
style={{
width: 500,
height: 300,
backgroundColor: '#f5d682',
border: '1px solid red',
}}>
<div style={{ flex: 1, flexDirection: 'column' }}>
{messages.map((item, index) => (
<div key={index}>
<strong>{item.id === socket.id ? 'You' : item.id}:</strong> {item.text}
</div>
))}
</div>
</div>
<form onSubmit={sendMessage}>
<input
type='text'
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
<button type='submit'>Send</button>
</form>
</div>
);
};
export default SocketIoComponent;
4. 결과 화면
4.1. [node-wss-server] 소켓 서버를 실행합니다.
# app.js 파일을 실행합니다.
$ node app.js
4.2. [react-chatting] 클라이언트 웹 서버를 실행해 줍니다.
# react를 실행합니다.
$ npm start
4.3. 두 개의 브라우저에 웹 서버 경로로 접근합니다.
4.4. A 브라우저에서 입력을 합니다
💡 A 브라우저에서 입력을 합니다
- 왼쪽에 있는 A 브라우저에서 값을 입력하고 Send 버튼을 누르면 B 브라우저 내에서 해당 데이터가 전달됨을 확인하였습니다.
4.5. B 브라우저에서 입력을 합니다.
💡 B 브라우저에서 입력을 합니다
- B 브라우저에서 텍스트를 입력하고 'Send' 버튼을 눌렀을 때 A 브라우저에게 전달이 됨을 확인하였습니다.
💡 [참고] 소켓통신을 이용하기 위한 채팅방을 구성한 React Repository입니다.
- 해당 소스코드는 아래의 Github 내에 'socketIo' 패키지 내에 포함되어 있는 컴포넌트의 내용입니다.
💡 [참고] 소켓통신에서 소켓 서버 역할을 하는 node Repository입니다.
오늘도 감사합니다. 😀
반응형
'Node > 이해하기' 카테고리의 다른 글
[Node] PM2(Process Manager 2) 이해하고 주요 특징 알아보기 (0) | 2024.09.01 |
---|---|
[Node] Socket.io 기반 소켓 서버 구축 방법 -2 : Room, Namespace 별 소켓 통신 방법 (1) | 2024.08.25 |
[Node] Node 버전 상황에 따라 변경 방법 : 라이브러리 n 활용 (0) | 2024.04.22 |
[Node] 자바스크립트 패키지 매니져(npm/yarn) 이해하기 -1 (2) | 2022.06.26 |
[Node] Node.js 이해하기-2(환경구성, Express, MongoDB) (0) | 2022.06.19 |