반응형

웹 소켓이란?

WebSocket은 웹 앱과 서버 간의 지속적인 연결을 제공하는 프로토콜입니다. 이를 통해 서버와 클라이언트 간에 양방향 통신이 가능해집니다. HTTP와는 달리, WebSocket 연결은 한 번 열린 후 계속 유지되므로, 서버나 클라이언트에서 언제든지 데이터를 전송할 수 있다는 것이 특징입니다. 그렇기에 실시간으로 진행되는 통신에서 적극적으로 사용하고 있습니다.

특징

  1. 양방향 통신
  • 데이터 송수신을 동시에 처리할 수 있는 통신 방법
  • 클라이언트와 서버가 서로에게 원할 때 데이터를 주고 받을 수 있따.
  • 통상적인 Http 통신은 Client가 요청을 보내는 경우에만 Server가 응답하는 단방향 통신
  1. 실시간 네트워킹
  • 웹 환경에서 연속된 데이터를 빠르게 노출 ex) 채팅, 주식, 비디오 데이터
  • 여러 단말기에 빠르게 데이터를 교환

웹 소켓 이전의 비슷한 기술

Polling vs Long polling vs Streaming

  • 결과적으로 이 모든 방법이 HTTP를 통해 통신하기 떄문에 Request, Response 둘다 Header가 불필요하게 큼

핸드 쉐이킹

클라이언트 요청 주요 헤더

Get / HTTP/1.1/r/n : 요청 메서드와 요청 파일정보. HTTP 버전을 의미

Host : 서버의 도메인 네임으로 Host 헤더는 반드시 존재해야 한다.

User-Agent : 사용자가 어떤 클라이언트를 통해 요청을 보냈는지 알 수 있다.

Origin: 요청을 보낼 때, 요청이 어느 주소에서 시작되었는지 나타낸다.

Connection: 클라이언트와 서버가 connection에 대한 옵션을 정할 수 있게 알려줌.

Cache-Control: 메시지와 함께 캐시 지시자를 전달하기 위해 사용하는 헤더이다.

Upgrade: websocket이라는 단어를 꼭 사용해야 한다. (이유는 WebSocket 연결 과정에 서술)

Sec-WebSocket-Key: 유효한 요청인지를 확인하기 위한 키로 base64로 인코딩한다.(클라이언트에서 생성)

서버 응답 주요 헤더

Connection: 클라이언트와 서버가 connection에 대한 옵션을 정할 수 있게 알려줌. Upgrade가 반드시 포함되어야 함

Date: HTTP 메시지를 생성한 일시를 나타냄.

Sec-WebSkcoet-Accept: 클라이언트로부터 받은 Sec-WebSocket-Key를 사용하여 계산된 값

Server: 서버 소프트웨어 정보를 나타냄.

Access-Control-Allow-Credentials:클라이언트 요청이 쿠키를 통해서 자격 증명을 해야 하는 경우에 true를 응답받은 클라이언트는 실제 요청 시 서버에서 정의된 규격의 인증값이 담긴 쿠키를 함께 보내야 한다.

Access-Control-Allow-Headers: 요청을 허용하는 헤더.

Upgrade: websocket이라는 단어를 꼭 사용해야함. (이유는 WebSocket 연결 과정 서술)

WebSocket 핸드셰이크 과정에서 Sec-WebSocket-Key 검증 원리

  1. 클라이언트는 핸드셰이크 요청 헤더에 Sec-WebSocket-Key를 포함합니다.
    예:
    Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
  2. 서버는 다음 과정을 통해 클라이언트의 요청을 검증합니다:
    • 클라이언트가 제공한 Sec-WebSocket-Key 값에 RFC 6455에서 정의된 고정 문자열 258EAFA5-E914-47DA-95CA-C5AB0DC85B11을 결합합니다.
    • 이 결합된 문자열을 SHA-1 알고리즘으로 해시한 후, 그 결과를 Base64로 인코딩합니다.
    • 생성된 결과가 서버 응답 헤더의 Sec-WebSocket-Accept에 포함됩니다.
  3. 클라이언트는 서버로부터 응답을 수신한 후, Sec-WebSocket-Accept 값을 확인하여 핸드셰이크가 성공적으로 완료되었는지 검증합니다.

이 후 웹 소켓이 열리게 되면 메시지를 전송할 수 있음

프레임 헤더 구조

126bytes이하일 경우

127 byte이상, 65535 bytes 보다 작은 경우

65535 bytes 이상일 경우

FIN : 이 프레임이 전체 메시지의 끝인지 나타내는 플래그

RSV1, RSV2, RSV3 : WebSocket 확장을 위해 예약된 필드

OPCODE :

MSK : 마스킹 필드로 1로 세팅되어 있으면 Payload 데이터에 마스크로 4바이트가 설정된다. 보통 프레임마다 랜덤하게 만들어지고 XOR 로 적용한다. 마스킹을 하는 이유는 cache-poisoning attack을 방지하기 위함이다.

LENGTH : 이 프레임에 포함된 데이터의 총 길이를 나타내는 단위

웹 소켓 프로토콜 특징

  • 최초 접속에서만 http 프로토콜 위에서 handshaking을 하기 때문에 http header를 사용한다.
  • 웹소켓을 위한 별도의 포트는 없으며, 기존 포트(http-80, https-443)
  • 프레임을 구성된 메시지라는 논리적 단위로 송수신
  • 메시지에 포함될 수 있는 교환 가능한 메시지는 텍스트와 바이너리

웹소켓 한계

  1. HTML 5 이후에 나온기술(이전에는 Socket.io, SockJs 활용해야함)
    즉, 브라우저와 웹 서버의 종류와 버전을 파악하여 가장 적합한 기술을 선택하여 사용해야 함
  2. 형식이 정해져 있기 않음
    → sub-portocols를 사용해서 주고 받는 메시지의 형태를 약속
    → STOMP(Simple Text Oriented Message Protocol)
    채팅 통신을 하기 위한 형식을 정의
    HTTP와 유사하게 간단히 정의되어 해석하기 편한 프로토콜

  3. 모든 것은 Server에 달렸다!
    당신이 채팅방에 입장하게 되면 당신은 친구들과 연결된 거시 아니라 모두가 같은 Socket Server에 연결된 것이다.

그렇기 때문에, 누군가가 메세지를 보낸다면 그것은 다른 친구한테 보낸게 아니라 서버에 전달하게 된 것이고 서버는 이를 다른 다른 사용자들에게 전달만 할 뿐이다.

여기서 문제가 발생하는데, WebSocket Server는 모든 통신을 추척하기 위한 "메모리 파워"가 중요하다. 이 말은 유저가 많아질수록, 더 많은 메모리가 필요하고, 메모리가 많이 필요할 수록 서버에 더 많은 돈을 써야함을 의미한다. 또한, 서버를 빠르게 유지해야하는데 이미 수많은 통신이 서버를 통해 이루어지고 있다면 이는 지연(Latency)를 유발할 수 있다. 그리고 이는 최악의 유저경험이 되는 것이다. 또한 서버에 문제가 생기면 누구도 대화 할 수 없다는 문제도 있다.
이러한 문제를 해결하기 위해 Socket Server가 아닌 브라우저끼리 연결하여 데이터를 주고 받는 것이 "WebRTC" 이다.

Spring Boot 환경에서의 WebSocket

요구사항 : 특정 게임방이 있고 각각의 플레이어 게임방에 입장하면 방장은 입장한 플레이어를 실시간으로 확인할 수 있게 구현해야하는 상황

  1. build.gradle 설정
implementation 'org.springframework.boot:spring-boot-starter-websocket'
  1. @Configuration이용한 WebSocketConfigurer 구현
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new RoomWebSocketHandler(new WebSocketRoomManager()), "/ws/room").setAllowedOrigins("*"); // 클라이언트 도메인을 설정 (CORS)
    }
}
  1. TextWebSocketHandler 구현
package com.dodam.dicegame.dicegame.sockethandler;

import com.dodam.dicegame.dicegame.vo.SocketPayloadVO;
import com.google.gson.Gson;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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;
import com.dodam.dicegame.dicegame.manager.WebSocketRoomManager;

import java.io.IOException;
import java.util.Set;

@Slf4j
@RequiredArgsConstructor
public class RoomWebSocketHandler extends TextWebSocketHandler {

    private final WebSocketRoomManager roomManager;
    private final Gson gson = new Gson();

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        log.info("WebSocket connection established: {}", session.getId());
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        log.info("Received message from {}: {}", session.getId(), message.getPayload());
        SocketPayloadVO socketPayloadVO = gson.fromJson(message.getPayload(), SocketPayloadVO.class);

        if ("joinRoom".equals(socketPayloadVO.getAction())) {
            roomManager.addSessionToRoom(socketPayloadVO.getRoomId(), session.getId(), session);
            broadcastToRoom(socketPayloadVO.getRoomId(), session.getId(), socketPayloadVO.getNickName() + "님이 입장하였습니다.");
        }
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        log.info("WebSocket connection closed: {}", session.getId());
        roomManager.removeSessionById(session.getId());
    }

    private void broadcastToRoom(String roomId, String selfSessionId, String message) {
        Set<String> sessionIds = roomManager.getSessionsInRoom(roomId); // 방에 있는 세션 목록 조회
        if (sessionIds == null) {
            log.warn("No sessions found for room {}", roomId);
            return;
        }

        sessionIds.forEach(sessionId -> {
            WebSocketSession session = roomManager.getSessionById(sessionId);
            if (isSkipSession(session, selfSessionId, sessionId))
                return;

            try {
                session.sendMessage(new TextMessage(message));
            } catch (IOException e) {
                throw new RuntimeException("Failed to send message", e);
            }
        });
    }

    private boolean isSkipSession(WebSocketSession session, String selfSessionId, String sessionId) {
        return session == null || !session.isOpen() || selfSessionId.equals(sessionId);
    }

}
@Slf4j
@Component
@Getter
@Setter
@NoArgsConstructor
public class WebSocketRoomManager {

    public final Map<String, Set<String>> roomSessionIdMap = new ConcurrentHashMap<>(); //roomId,sessionId
    public final Map<String, WebSocketSession> sessionMap = new ConcurrentHashMap<>(); //sessionId,WebSocketSession

    public void addSessionToRoom(String roomId, String sessionId, WebSocketSession session) {
        roomSessionIdMap.computeIfAbsent(roomId, k -> ConcurrentHashMap.newKeySet()).add(sessionId);
        sessionMap.put(sessionId, session);
    }

    public WebSocketSession getSessionById(String sessionId) {
        return sessionMap.get(sessionId);
    }

    public void removeSessionById(String sessionId) {
        sessionMap.remove(sessionId);
        roomSessionIdMap.values().forEach(sessions -> sessions.remove(sessionId));
    }

    public Set<String> getSessionsInRoom(String roomId) {
        return roomSessionIdMap.getOrDefault(roomId, ConcurrentHashMap.newKeySet());
    }
}
  1. Client 접속

https통신이면 wss지원

  1. 크롬 개발자 도구 확인

 

참고자료 및 출처

https://www.youtube.com/watch?v=MPQHvwPxDUw&list=PLgXGHBqgT2TvpJ_p9L_yZKPifgdBOzdVH&index=98

https://alnova2.tistory.com/915

https://velog.io/@wnduf8922/etc-WebSocket에-대하여

728x90
반응형

[IT대기업 합격자의 비밀노트-기술 면접 대비 CS전공 핵심요약집]는 주니어 개발자를 꿈꾸는 취업 준비생, 전공 기초 지식을 다시 정리하고 싶은 주니어 개발자, 기술 면접을 앞둔 개발자들에 유용한 내용들을 다루고 있습니다. 
각 목차 마다 개발자로써 알아야할 내용들이 핵심만 장리해 놓았습니다. 특히, 예상 면접 질문과 답변 방법을 제시하는 부분은 면접자 혹은 취업준비생들이 어떤 방향으로 답변을 해야하는지 모법답안을 제시해주고 있습니다. 

1장 운영체제 :  운영체제의 기본 개념을 살펴보며 프로세스, 스케줄링, 메모리 관리 전략 등을 체계적으로 다루고 있습니다. 뿐만 아니라 가상메모리, 캐시메모리와 같은 고급 주제에 대해서도 깊이 있게 다루어져 있습니다.
개발자가 구현한 프로그램들이 운영체제와 하드웨어 관점에서 알아야할 내용들을 잘 정리해 놓아서 좋았습니다.

2장 컴퓨터 네트워크 : 2장에서는 네트워크의 기본 구조를 이루는 네트워크 계층부터 TCP와 UDP, HTTP, 그리고 REST에 이르기까지 체계적으로 컴퓨터 네트워크 지식을 다루어 현업에서 필요한 지식을 습득할 수 있습니다.
특히, 웹 개발자로 성장하고 싶은 취업 준비생에게 정말 좋은 내용이라고 생각됩니다.

3장 데이터 베이스 : 3장에서는 다양한 데이터베이스의 종류부터 관계형 데이터베이스, 트랜잭션, 그리고 조인에 이르기까지의 핵심 주제를 다루어 독자들에게 폭넓은 데이터베이스 지식을 제공합니다.
3장은 웹 백엔드 개발자로 성장하려는 취업 준비생에게 가장 필요하다고 생각됩니다.

4장 자료구조 : 4장에서는 자료구조의 기초를 이루는 선형 자료구조를 다루며, 배열, 연결 리스트, 스택, 큐 등에 대한 개념과 활용법을 상세하게 설명합니다.

5장 알고리즘 : 알고리즘은 프로그래밍의 핵심이자 예술이라고 생각합니다. 5장에서는 다양한 알고리즘을 다루며, 정렬 알고리즘에 대한 기초를 제공합니다. 여러 가지 정렬 알고리즘의 특징과 성능을 비교하고, 각각의 상황에 맞는 적절한 알고리즘을 선택하는 능력을 키울수 있는 내용을 다루고 있습니다.

6장 부록 : 다양한 개발 분야별 예상 질문과 함께 객체 지향 프로그래밍, 자바, 인공지능 등 핵심 주제를 깊이 있게 다루어, 취업 준비생의 희망 분야에 따라서 선택적으로 읽을 수 있게 도와줍니다. 또한, 포트폴리오 작성 및 자기소개서 예시를 저자의 실제 작성 내용을 첨부하여 흥미롭게 읽었습니다.

또한, 이 책에 가장 좋았던 부분은 각 장마다 면접을 대비하여 예상 면접 질문과 예시 답변을 다루고 있는 부분이 었습니다. 책은 CS전공 지식에 대한 전반적인 이해를 기반으로 예상 질문에 적절한 대답을 할 수 있게 도와줍니다. 이 책을 읽으므로써 면접에서도 더욱 자신감을 갖게 될 것입니다.

이 책은 전반적으로 개발자을 하기위해서 필요한 CS지식을 심층적으로 다루어 취업준비생, 주니어 개발자, 이직을 고려하는 주니어 개발자까지 핵심 기술력 향상에 기여할 수 있게 도와줍니다. 
또한, 예상 면접 질문과 포트폴리오 작성 등 실전 대비에도 탁월한 도움이 되는 책으로 강력히 추천합니다.

 

728x90

+ Recent posts