1. WebSocket 의미
메시지 처리 방식 : Polling, Long Polling, WebSocket
1) Polling : 클라이언트(ex) 웹 브라우저)가 정해진 간격으로 서버에 요청을 보내 새로운 메시지나 데이터가 있는지 확인. 그러나 일정 주기로 메시지를 클라이언트에서 가져가는 방식이라서 다른 사용자가 작성한 메시지가 전달되는데까지 일정시간 지연 발생 및 클라이언트 별로 Polling 되는 시점에 따라 서로 보고있는 메시지가 다르기 때문에 실시간으로 보이는 대화 내용이 불일치 할 수 있음.
2) Long Polling : 클라이언트가 서버에 요청을 보내면, 서버는 새로운 메시지가 도착할 때까지 응답을 지연시킨다. 새로운 메시지가 도착하면 그 즉시 응답을 보내고 클라이언트는 즉시 다시 요청을 보낸다.
https://velog.io/@codingbotpark/Web-Socket-%EC%9D%B4%EB%9E%80
2. 스프링 부트 설정
WebSocket - STOMP 프로토콜
- 프론트 입장(React.js) 예시 :
function App() {
const stompClient = useRef(null);
const [messages, setMessages] = useState([]);
const [inputValue, setInputValue] = useState("");
const handleInputChange = (event) => {
setInputValue(event.target.value);
};
const disconnectApi = async (sendId, rcvId, chatRoomId) => {
try {
const config = {
headers: {
Authorization:
"Bearer aaaa",
},
};
const payload = {
sendId: 8,
rcvId: 3,
chatRoomId: 2,
};
const response = await axios.post(
"/api/test/v1/chat-room/disconnect",
payload,
config
);
if (response.status === 200) {
console.log("Chat room disconnected successfully:", response.data);
}
} catch (error) {
// Handle errors here
console.error("Error disconnecting chat room:", error);
}
};
const connect = () => {
stompClient.current = new Client({
brokerURL: "ws://localhost:80/ws", // WS 서버 아이피
connectHeaders: {
Authorization:
"Bearer aaaa",
chatRoomId: 2, // Passing the chatRoomId in connect headers
sendId: 8,
rcvId: 3,
},
// connection 맺기
onConnect: () => {
console.log("Connected to WebSocket");
// connection 맺은 후 subscribe();
subscribe();
},
debug: (str) => {
console.log(new Date(), str);
},
reconnectDelay: 5000, // Auto reconnect after 5 seconds
});
stompClient.current.activate();
};
const subscribe = () => {
stompClient.current.subscribe("/sub/chat/room/2", (message) => {
const newMessage = JSON.parse(message.body);
setMessages((prevMessages) => [...prevMessages, newMessage]);
});
};
const disconnect = (chatRoomNo, authToken) => {
console.log("채팅이 종료되었습니다.");
disconnectApi();
stompClient.current.deactivate();
};
useEffect(() => {
connect();
return () => disconnect();
}, []);
// pub 영역
const sendMessage = () => {
if (stompClient.current && inputValue) {
const body = {
chatRoomId: 2,
sendId: 8,
rcvId: 3,
message: inputValue,
};
stompClient.current.publish({
destination: `/pub/send-message`,
body: JSON.stringify(body),
});
// setInputValue("");
}
};
return (
<div className="chat-container">
<div className="send-message">
<h3>사용자 채팅창</h3>
<input
type="text"
value={inputValue}
onChange={handleInputChange}
placeholder="Type your message here"
onKeyPress={(e) => {
if (e.key === "Enter") {
sendMessage();
}
}}
/>
<button onClick={sendMessage}>Send</button>
</div>
<div className="received-messages">
<h3>Received Messages</h3>
<ul>
{messages.map((item, index) => (
<li key={index} className="list-item">
<strong>{item.sendId}: </strong>
{item.message}
</li>
))}
</ul>
</div>
</div>
);
}
export default App;
- 스프링 부트(WS 서버 입장)
@Slf4j
@Component
@RequiredArgsConstructor
public class StompHandler implements ChannelInterceptor {
private final TokenProvider tokenProvider;
private final ChatHeadService chatHeadService;
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
log.debug("message: " + message);
log.debug("헤더: " + message.getHeaders());
log.debug("토큰: " + accessor.getNativeHeader("Authorization"));
String token = getAuthorizationToken(accessor);
switch (Objects.requireNonNull(accessor.getCommand())) {
case CONNECT:
log.debug("STOMP Connect command");
try {
tokenProvider.validateToken(token);
Long chatRoomId = getChatRoomId(accessor);
Long senderId = getSendId(accessor);
Long rcvId = getRcvId(accessor);
chatHeadService.connectToChatRoom(chatRoomId, senderId, rcvId);
} catch (IOException e) {
throw new RuntimeException(e);
}
break;
case DISCONNECT:
log.debug("STOMP Disconnect command");
// DISCONNECT는 api로 Header값 꺼낼 수 없음
// Long chatRoomId = getChatRoomId(accessor);
// Long senderId = getSendId(accessor);
// Long rcvId = getRcvId(accessor);
// chatHeadService.disConnectToChatRoom(chatRoomId, senderId, rcvId);
break;
case SEND:
// Header에서 authorization 꺼낼 수 없음.
log.debug("STOMP Send command");
// Handle send case logic here
break;
case SUBSCRIBE:
log.debug("STOMP Subscribe command", message);
// Handle subscribe case logic here
break;
default:
log.warn("Unknown STOMP command");
break;
}
return message;
}
}
STOMP = Simple Text Oriented Messaging Protocol는 웹 소켓 위에서 작동하는 메시징 프로토콜로, 클라이언트와 서버간의 메시지 기반 통신을 간소화하교 표준화한다.
세션을 서버에서 따로 관리하는 MAP 자료구조 방식 비교해서 알아두기
차례 : 외부 메시지 브로커 필요한 이유
https://brunch.co.kr/@springboot/695
3. RabbitMQ or Kafka 연결
4. 배민 사례
https://techblog.woowahan.com/2547/
'CS > Network' 카테고리의 다른 글
우분투 Wake on Lan 설정 (0) | 2024.09.11 |
---|---|
프론트는 https로 서비스, apiserver는 http 프로토콜일 경우 (0) | 2023.09.19 |
IP로 실제 위치 추적이 가능할까? (0) | 2023.06.23 |
0.0.0.0, 127.0.0.1, 255.255.255.255 (0) | 2023.05.06 |
MIME 타입 (0) | 2022.12.22 |