프론트에서 사용할채팅 기능을 위해서 찾아보다가 웹소켓이란것을 알게되었다.
HTTP통신
보통은 클라이언트간에 데이터를 전송할때 1번 클라이언트가 2번에게 데이 보냈을때 2번이 이 데이터를 조회하기위해서는 새로고침을 눌러서 변경된데이터를 조회해야한다 이 방법은 3-way 핸드 쉐이크라고 하며
연결
1번 => 2번 (내말들리니?) syn =>
1번 <= 2번 (잘 들려, 너도 들리니?) syn_send <= syn,ack
1번 => 2번 (ㅇㅇ 잘들려) ack => sync_rcvd
이렇게 3번 의과정을거쳐서 통신을 하게된다
연결 종료
1번 => 2번 (할말 다 했으니까 이제 끊을게) close =>
1번 <= 2번 (알겠어 (끊을 준비를 하며)) closewait1 => closewait
1번 <= 2번 (이제 끊어도 돼) closewait2 <= close
1번 => 2번 (ㅇㅋ) ack =>
이런 연결과 종료 과정이 많고 http패킷에는 헤더와 바디 처럼 채팅 기능에는 필요없는 패킷을 가지고있어서 채팅 기능에는 주로 웹소켓이라는 전송기술을 사용하게 된다.
나는 스프링을 통해서 웹소켓을 구현했고 스프링에서 제공해주는 websocket라이브러리를 사용했다.
build.gradle
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-websocket'
서버가 필요하기때문에 내장서버를 가진 starter-web과 websocket을 의존성으로 가져와야한다.
webSocketConfig
@Configuration
@EnableWebSocket
@RequiredArgsConstructor
public class WebSocketConfig implements WebSocketConfigurer {
private final WebSocketHandler webSocketHandler;
private final SocketTextHandler socketTextHandler;
private final HandshakeInterceptor ChattingHandshakeInterceptor;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// endpoint 설정 : /api/v1/chat/{postId}
// 이를 통해서 ws://localhost:8080/chat/rooms/{방번호} 으로 요청이 들어오면 websocket 통신을 진행한다.
// setAllowedOrigins("*")는 모든 ip에서 접속 가능하도록 해줌
registry.addHandler(socketTextHandler, "/chat/rooms/*")
.addInterceptors(ChattingHandshakeInterceptor)
//cors설정
.setAllowedOrigins("*");
//sockjs설정
// .withSockJS();
}
}
sockjs는 현재 cors에러가 생겨서 주석처리를 해놓았다.
setAllowedOrigins 는 cors메서드인데 여기서 허용하는 주소를 설정하면 해당하는주소만 접근할수있다고한다.
현재는 " * " 로 모든 주소에서 접근이 가능하도록 처리해주었다.
addInterceptors는 웹소켓에 클라이언트가 핸드셰이킹(연결)이 일어나기전에 필요한 처리를 해줄수있는 인터셉터를 추가해줄수있는 메서드이다.
요청하는 주소에 방번호를 가지고있어서 해당 데이터를 처리해주기위해서 인터셉터를 추가해주었다.
ChattingHandShakeInterceptor
@Component
public class ChattingHandshakeInterceptor implements HandshakeInterceptor {
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Map<String, Object> attributes) throws Exception {
String path = request.getURI().getPath();
String roomId = path.substring(path.lastIndexOf('/') + 1);
//주소에서 방번호를 받아와 추가.
attributes.put("roomId", roomId);
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Exception exception) {
}
}
여기서 넣은 roomId파라미터는 핸들러에서 session에 getAttributes().get("roomId") 로 받아올수있다.
이제 연결은 과정까지는 모두 만들어진것이다 이제 연결될때, 연결이 종료될때, 데이터를 보낼때 처리해줄 handler를 구현해야한다. configuration에서 파라미터로 들어가있는 socketTextHandler 코드이다.
package com.example.chat_test.chat;
import com.example.chat_test.model.Room;
import com.example.chat_test.model.RoomRepository;
import java.util.HashSet;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
@Component
@RequiredArgsConstructor
@Slf4j
public class SocketTextHandler extends TextWebSocketHandler {
private final RoomRepository roomRepository;
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
Long roomId = getRoomId(session);
roomRepository.room(roomId).getSessions().add(session);
log.info("새 클라이언트와 연결 되었습니다.");
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
Long roomId = getRoomId(session);
Room currentRoom = roomRepository.room(roomId);
log.info(message.getPayload());
for (WebSocketSession connectedSession : currentRoom.getSessions()) {
connectedSession.sendMessage(message);
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
Long roomId = getRoomId(session);
roomRepository.room(roomId).getSessions().remove(session);
log.info("클라이언트와 연결이 해제 되었습니다.");
}
private Long getRoomId(WebSocketSession session) {
return Long.parseLong(
session.getAttributes()
.get("roomId")
.toString()
);
}
}
TextWebSocketHandler를 확장하게되면 필요한 메서드를 상속받아서 사용할수있게된다 .
나는 현재 room , roomRepository를 통해서 방이있고 해당방에 들어가있는 세션들을 관리할수있게 처리 해두어서
처음 세션이연결될때 해당하는 룸의 sessions컬렉션에 접속된 세션을 추가시켜주고 세션이 종료될때는 세션을 remove시키도록 해주었다.
해당 방에만 메시지를 보내는 기능은 같은 방에있는 세션이 문자를 보낼때 해당 세션이가진 roomId를 받아와서 방을 조회하고 그 room이 가진 session에 모두 메시지를 send해주는 방법으로 처리해주었다.
'공부 임시 저장소' 카테고리의 다른 글
스프링 부트 - spring-security-jwt (0) | 2024.06.25 |
---|---|
스프링부트 - aws s3파일저장 (0) | 2024.06.25 |
스프링부트 - 파일 저장 (0) | 2024.06.24 |
채팅앱 - 채팅 미전송 메시지 처리 (0) | 2024.06.24 |
사이드 프로젝트) 채팅앱 (0) | 2024.06.24 |