웹소켓이 뭘까? 채팅 초간단하게 만들기

림민지·2025년 7월 14일

Today I Learn

목록 보기
61/62

웹소켓이 뭘까?

: 웹소켓이란 웹브라우저와 서버가 실시간으로 아주 빠르게 대화할 수 있게 해주는 기술이다.

사진 출처 : https://sendbird.com/ko/developer/tutorials/websocket-vs-http-communication-protocols

우리가 평소에 웹사이트를 이용하는 방식은 바로 HTTP 방식이다.

이는 요청 ↔ 응답 방식인데, 질문하고 대답을 듣는 방식이라고 할 수 있다.

내가 요청하면 서버가 응답하고 그 흐름이 뚝 끊기는 것이다. 또 다른 요청이 필요하면 다시 보내야하는 것!

이렇게 되면 새로운 정보가 생겨도 서버가 절대 먼저 알려주지 못한다.

내가 계속해서 서버한테 새로운 거 뭐 들어온거 없니? 있니? 이렇게 물어봐야해서 실시간 채팅이나 주식처럼 정보 업데이트가 필요한 서비스에는 매우매우 비효율적이다 (계속해서 내가 새로들어온 정보 있는지 찔러봐야하니까)

이 문제를 해결하기 위해 웹소켓!! 이 나왔다

얘는 한 번 연결하면 내가 이제 끊어줘 라고 하지 않는 이상 계속해서 상태를 유지해준다

언제든지 필요할때마다 양쪾에서 메세지를 주고 받을 수 있따

장점

  • 실시간 양방향 통신
  • 속도 매우 빠름
  • 자원소모가 적다 (연결 유지는 있지만, 재연결을 계속 안해도 되니까 효율적임)

대표적인 예시로는 카톡이나 슬랙 같은 메신저에서 메세지 보내면 바로 알림 오는게 웹소켓 덕분이다!

또 주식 ㅏ격이 실시간으로 반영되는 것이나 인스타에서 누가 내 게시물에 좋아요를 누르면 그 알림이 바로 오는 실시간 알림도 웹소켓이다!

즉 웹소켓은 웹에서, 실시간으로, 양방향 통신을 해주는 기술!!

→ 즉각적인 정보공유가 매우 중요한 서비스에서 유용하게 사용된당


직접 구현하기

스프링에서 웹소켓 연결하기? 컵라면 먹기와 같다 (핵쉽다는 뜻 나는 헤맸음ㅠㅠㄴ)가

먼저 우리가 웹소켓 연결할게! 라고 알려줘야겠죠?

@Configuration 등록해줄 WebSocketConfig 클래스를 만들어줍시다

WebSocketConfig

@Configuration
@EnableWebSocket //웹소켓 활성화
public class WebSocketConfig implements WebSocketConfigurer {
	private final ChatWebSocketHandler chatWebSocketHandler; //ChatWebSocketHandler를 가져오기
	
	//여기는 @AllArgsConstructor 로 처리해도 무방하다 (그냥 생성자임)
	public WebSocketConfig(ChatWebSocketHandler chatWebSocketHandler){
		this.chatWebSocketHandler = chatWebSocketHandler;
		}
	
	@Override
	public void registerWebSocketHandlers(WebSocketHandlerRegistry registry){
		registry.addHandler(chatWebSocketHandler, "/ws/chat") //해당 경로로 들어오는 모든 요청은 웹소켓 핸들러가 처리한다!
						.setAllowedOrigins("*"); //모든 도메인 접속을 허용했지만, 향후 배포환경이라면 특정 도메인으로 제한하는 것이 좋다
		}
}

이 설정이 끝나면 거의 다 된 것이나 다름없다~!

LLM을 결합한 챗봇으로 구현할 수도 있지만 우선 웹소켓으로 채팅을 만든다는 것에 집중해보자

→ 다음 글에서 인공지능이랑 소통하는 로직을 다뤄보쟈

ChatWebSocketHandler

@Component
@Slf4j
public class ChatWebSocketHandler extends TextWebSocketHandler {

    private final RestTemplate restTemplate;
    private final ObjectMapper objectMapper;

    // RestTemplate과 ObjectMapper 주입
    public ChatWebSocketHandler(RestTemplate restTemplate, ObjectMapper objectMapper) {
        this.restTemplate = restTemplate;
        this.objectMapper = objectMapper;
    }
		
		
		//채팅 기록을 저장할 리스트 생성
    private final Map<String, List<GeminiRequestDto.Content>>  chattingHistory = new ConcurrentHashMap<>();


		@Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        log.info("WebSocket 연결 성공: {}", session.getId());

        // 해당 세션에 대한 Gemini 대화 기록을 초기화합니다.
        List<GeminiRequestDto.Content> history = chattingHistory.computeIfAbsent(session.getId(), k -> {
            log.info("새로운 세션 {} 에 대한 Gemini 대화 기록 초기화", k);
            return new ArrayList<>();
        });

        if (history.isEmpty() || !history.get(0).getParts().get(0).getText().equals(SYSTEM_PROMPT)) {
            history.add(0, new GeminiRequestDto.Content(
                    Collections.singletonList(new GeminiRequestDto.Part(SYSTEM_PROMPT)), "user"));
            log.info("세션 {} 에 시스템 프롬프트 추가 완료.", session.getId());
        }
    }
		
		
		@Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
		
		/*
    3. 웹소켓 연결이 종료
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        log.info("웹소켓 세션 종료 : {} (이유: {})", session.getId(), status.getReason());
    }

    /*
    웹소켓 전송 중 에러가 발생했을 때 호출
     */
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        log.error("웹소켓 전송 중 에러가 발생했습니다. 세션 {}: {}", session.getId(), exception.getMessage(), exception);
        if (session.isOpen()) {
            session.close(CloseStatus.SERVER_ERROR);
        }
    }
profile
@lim_128

0개의 댓글