6. 드디어 WebSocket!

Dev StoryTeller·2021년 1월 2일
1

이제 드디어 WebSocket에 대한 이야기를 해보려고 한다.

1. WebSocket 등장

앞서 살펴봤듯이, HTML, AJAX 등 아무리해도 해결못하는 고질적인 문제가 하나 있었다. 바로

서버는 요청이 없으면 응답을 못한다는 것!

이런 상황에서 양쪽 모두가 통신을 주고받을 수 있는 규약이 만들어졌는데, 이것이 바로 양방향 통신 프로토콜, 웹소켓(WebSocket)이다.


2. WebSocket이란?

HTTP와 같은 통신 프로토콜으로, 양방향 통신이라는 것 빼고는 모두 동일하다.
(API는 W3C에서 관장하고, 프로토콜은 IETF에서 관장한다고 한다)

HTTP와 마찬가지로 IP와 PORT(80)로 통신을 하며, Upgrade 헤더를 통해 웹서버에 요청을 한다.

GET /... HTTP/1.1  
Upgrade: WebSocket  
Connection: Upgrade

3. 웹소켓 통신 과정

여느 통신과 동일하게 웹소켓 또한 핸드셰이킹(연결) -> 서로 통신 -> 연결 종료의 3단계가 존재한다

다만 조금 다른점이 있는데, WebSocket을 사용하기 위해선 기존 HTTP 프로토콜에서 전환을 해야한다
이 과정이 바로 연결 단계에서 이뤄지는데, 요약하자면 다음과 같다

1. 핸드 셰이킹(프로토콜 전환 및 연결)
2. 데이터 통신
3. 연결 종료

이에 대해 한단계씩 천천히 알아보자


1. 핸드 셰이킹

WebSocket 핸드 셰이킹 부분 패킷을 덤프 떠보면 다음과 같이 주고 받는다
(직접적인 관련이 없는 부분은 생략)

// 요청 패킷
GET / HTTP 1.1
Host: /* 페이지의 호스트 주소(+포트) */
Connection: Upgrade
Upgrade: websocket
Origin: /*요청한 페이지(클라이언트) 주소*/
Sec-WebSocket-Key: /* 연결을 위한 키 값 */


// 응답 패킷
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: /* 연결 키 값 */
WebSocket-Origin: /* 요청한 클라이언트의 주소 */
WebSocket-Location: ws://(페이지의 호스트 주소)/

뭔가 많지만, 사실 내용은 간단하다.
모두 살펴보진 않고, 중요 부분만을 살펴볼 것이다.
우선 요청 패킷부터 알아보자.


  • 요청 패킷

    1. Upgrade
    : 이미 설정된 커넥션을 다른 프로토콜로 업그레이드할 때 사용한다
    (HTTP/2.0으로 바꿈, WebSocket으로 바꿈 등)


    2. Sec-WebSocket-Key
    : 모든 연결은 커넥션을 맺기 전에 서로를 인증한다
    이런 인증을 하기 위해 클라이언트가 제공하는 임의의 키이다

  • 응답 패킷

    이번엔 응답 패킷을 살펴보자
    1. 101 상태 코드
    : Switching Protocols라고 한다 Upgrade로 프로토콜을 바꿀 때 사용하는 상태 코드이다


    2. Sec-WebSocket-Accept
    : 클라이언트의 키 값으로 서버가 계산한 키
    이 값이 올바르지 않다면 클라이언트는 잘못된 연결로 인식하고, 연결을 끊어버린다

2. 데이터 통신

WebSocket 연결이 완료되면, 자연스레 프로토콜도 http(https) -> ws(wss)로 바뀐다
이 뿐만이 아니라 데이터를 메세지로 주고 받는데, 여기서 메세지란 여러개의 프레임들로 구성되어 있다
이 프레임은 다음과 같이 생겼다

      0                   1                   2                   3
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
     +-+-+-+-+-------+-+-------------+-------------------------------+
     |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
     |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
     |N|V|V|V|       |S|             |   (if payload len==126/127)   |
     | |1|2|3|       |K|             |                               |
     +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
     |     Extended payload length continued, if payload len == 127  |
     + - - - - - - - - - - - - - - - +-------------------------------+
     |                               |Masking-key, if MASK set to 1  |
     +-------------------------------+-------------------------------+
     | Masking-key (continued)       |          Payload Data         |
     +-------------------------------- - - - - - - - - - - - - - - - +
     :                     Payload Data continued ...                :
     + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
     |                     Payload Data continued ...                |
     +---------------------------------------------------------------+

(출처: https://www.rfc-editor.org/rfc/rfc6455#section-5.1)

자세한 부분은 출처를 참고하고 여기서는 필요한 부분만 보자면,
opcode와 Payload Data라는 것이다

opcode는 전송할 데이터에 대한 해석이 담긴 부분으로, 다음 7가지가 있다

  • 0x0 프레임이 아직 끝나지 않음

  • 0x1 단순 텍스트 프레임

  • 0x2 바이너리 프레임(이미지, 파일 등)

  • 0x8 연결 종료

  • 0x9, 0xA ping을 나타냄

  • 0x3-7, 0xB-F 예약된 컨트롤 프레임

강조 표시된 4가지만 보면 되며, 위의 opcode를 보고 데이터가 어떤 의미인지를 알 수 있다

Payload Data실제 우리가 전송한 데이터가 담기는 부분이다

이 부분의 패킷을 잘 정리해서 보면 다음과 같다

// 보낸 메시지
> Welcome!
> Hello,
> WebSocket!

// 패킷 내용
Client -> Server
	: FIN=True, Opcode=0x1, Payload="Welcome!"
Client -> Server
	: FIN=False, Opcode=0x0, Payload="Hello,"
Client -> Server
	: FIN=True, Opcode=0x1, Payload="WebSocket!"

(참고: https://wiki.wireshark.org/WebSocket)

FIN은 프레임이 마지막인지 여부를 나타낸다
즉, FIN=True, Opcode=0x1 이란, 단순 텍스트 메시지를 끝까지 보냈다는 것을 알 수 있다

그리고 세션으로 연결되기에 서로 연결이 잘 되고있는지 ping을 주고받으며 확인하는데,
이를 heartbeat 패킷이라고 한다


3. 연결 종료

현재 연결 프로토콜은 WebSocket 프로토콜이기 때문에, 연결 종료 또한 메시지로 전송된다
순서는 서버가 연결을 종료한다는 프레임을 보내면, 클라이언트가 Close 프레임을 보내 응답한다

이때 Close 프레임opcode에 0x8이, payloadData에 종료 상태 코드가 담긴다


4. 간단히 웹소켓 구현해보기

간단한 코드를 구현해 보도록 하자.

if ('WebSocket' in window) {
    // 소켓 연결
    var webSock = new WebSocket(“ws://localhost:80”);
    
    // 메시지 송신
    webSock.send(“message”);
    
    // 메시지 수신
    var recvData = e.data;
    console.log(recvData);
    
    // 웹소켓 종료
    webSock.close();
}

정말 초간단 압축하여 작성해 보았다.
실제 과정은 복잡하지만, 코드는 누구나 이해할 수 있는 코드이다.
조금 생소한 것이 ws://~~일텐데, wswebsocket의 약자 ~~주소로 WebSocket 통신하겠다는 의미이다.


이제야 웹소켓에 대해 간단히 알아보았다.
다음 시간에는 웹소켓을 스프링으로 알아보는 시간을 가져보도록 한다!

profile
개발을 이야기하는 개발자입니다.

0개의 댓글