WebSocket

HyoKwangRyu·2020년 4월 18일
3

What

full-duplex communivation 을 제공하는 통신 프로토콜의 한 종류.
HTTP 헤더를 업그레이드 하여 핸드셰이크 하기때문에, HTTP와 호환이 됨.
TCP포트 80(암호화의 경우 443)을 통해 통신함.

Why

real-time service를 위해서.
하지만 기존의 HTTP는..?

Limit of HTTP

Connectionless

HTTP는 클라이언트가 요청을 보내고 서버가 응답을 주면, 연결이 끊어짐.
동일한 요청에 대해 같은 헤더 파일을 보냄.

Stateless

클라이언트와 서버가 서로 연결되어 있지 않기 떄문에, 각각의 통신은 독립적.
이전에 있었던 통신이 이후에 일어날 통신에 영향을 미치지 않음.
이전 통신에 대해 알 수도 없음.
(사실 세션이나 쿠키 같은것들이 있다)

위 두가지 특성은 실시간 통신에서의 한계점일 뿐,
기본적으로 상황에따라 장점이기도하고 단점이기도 함.

이러한 이유로 HTTP는 실시간 상호작용성이 안좋음.
하지만 몇가지 트릭으로 실시간 통신을 비슷하게 따라한 방식들이 나타남.

HTTP Polling

클라이언트가 요청을 주기적으로 계속 보내는 방식.
하지만 클라이언트가 많아지면?
불필요한 통신들이 많아지면서, 핸드셰이크 부터 통신까지 비용이 계속 늘어남.

HTTP Long Polling

폴링이 너무 안좋아서 나온 롱 폴링.
클라이언트가 요청 보내고 서버는 바로 응답을 보내지 않고 기다림.
서버에서 이벤트가 일어날 경우 응답을 보냄.
리스폰스를 받으면 HTTP 연결을 끊음.
클라이언트는 서버의 응답을 받으면 다시 요청을 보냄.

HTTP Streaming

클라이언트가 요청을 보내고, 서버는 계속해서 응답을 보내면서 연결을 끊지 않는 방식.
그렇다면 실시간 스트리밍이 되지 않느냐?
아님. 문제가 있음.
스트리밍을 하면서 TCP포트로 읽기와 쓰기를 동시에 할 수 없음.
연결을 끊지 않고 계속 유지하기 때문에 서버는 계속해서 클라로 메세지를 보낼 수 있지만, 클라이언트는 서버에 요청을 할 수 없음.
스트리밍 중 요청을 보내고 싶다면, TCP외에 다른 포트를 이용해야함.

And then.. WebSocket

웹소켓은 HTTP 기반으로 하면서 HTTP의 문제점을 해결하는 것을 목표로함.

Connection

socket connection을 유지하면서, 송수신 동시 처리 가능.
HTTP를 업그레이드해 이용하므로 최초 접속시 HTTP 핸드셰이크를 이용.

Stateful

최초에 접속하면 계속 연결 상태를 유지하기 때문에, 네트워크 비용 아낄 수 있음.

How

최초 핸드셰이크가 성공적으로 끝나면 HTTP를 웹소켓 프로토콜로 바꾸는 protocol switching이 실행됨.
웹소켓을 위한 새로운 소켓이 만들어지고 이소켓을 이용해 통신함(ws, wss)
데이터의 구조는 텍스트와 바이너리 모두 양방향 통신이 가능

Issues

Socket connection cost

서버와 클라이언트간의 socket 연결을 하는 것 자체가 비용이 큼.(트래픽이 많은 서버 같은 경우 CPU부담이 될 수 있다)

Abnormal disconnection

Stateful한 만큼 서버와 클라이언트 연결을 계속 유지해야하고, 비정상적으로 연결이 끊어졌을 경우 대응해야함.

What we use

자소설닷컴은 채팅 서비스를 위해 Pusher를 이용함.
채팅 메시지를 주고 받는 코드를 보여드리자면..
서버 - Ruby on Rails
클라이언트 - AngularJS, Swift, Java

서버

PusherClient.trigger(Chat.pusher_channel(chat_id), 'message', message)

argument는 순서대로
채널: String - 소켓 내에서 Subscribe 할 채널
이벤트: String - 채널 내에서 원하는 이벤트를 바인딩 할 수 있음
데이터: Hash - 주고 받을 데이터

웹 클라이언트

var channel = "<%=ENV['CHAT_CHANNEL']%>_" + chat.id
chatChannel = pusher.subscribe(channel);
chatChannel.bind('message', function(data) {
   ...
}

채널을 subscribe한 후, bind해주면 콜백으로 해당 채널, 이벤트의 데이터를 받아볼 수 있음

iOS

let channel = PusherService.sharedInstance.pusher.subscribe(Helper.chatChannel + String(describing: chatId))
channel.bind(eventName: "message") { data in
  ...
}

웹과 동일. (Android는 제가 개발하지 않아서 생략합니다)

빼먹었는데, 공통적으로 Pusher.connect() 해야합니다.
서버쪽은 trigger 하고, 클라이언트는 subscribe, bind 해주면 됩니다.


모든 이미지의 출처는 구글 입니다.




블로그를 시작했어요
앞으로도 실서비스의 코드를 보여드리면서 포스팅 하려 해요
잘 부탁드려요

profile
Backend Developer

1개의 댓글

comment-user-thumbnail
2020년 4월 18일

첫 찬준 포스팅이군여 ^^

답글 달기