분류 Nodejs

WebSockets 튜토리얼 : Node and React로 실시간으로 이동하는 방법(1)

컨텐츠 정보

  • 조회 412 (작성일 )

본문

웹은 클라이언트와 서버 간의 전이중 (또는 양방향) 통신을 지원하기 위해 먼 길을 걸어 왔습니다. 이것은 WebSocket 프로토콜의 주요 의도입니다. 단일 TCP 소켓 연결을 통해 클라이언트와 서버간에 지속적인 실시간 통신을 제공합니다.


WebSocket 프로토콜에는 1.) 핸드 셰이크를 열고 2) 데이터 전송을 돕는 두 가지 의제가 있습니다. 

일단 서버와 클라이언트가 핸드 셰이크를 받으면 마음대로 오버 헤드를 줄이면서 서로에게 데이터를 보낼 수 있습니다.


https://blog.logrocket.com/websockets-tutorial-how-to-go-real-time-with-node-and-react-8e4693fbf843/ 


WebSocket 통신은 WS (포트 80) 또는 WSS (포트 443) 프로토콜을 사용하여 단일 TCP 소켓을 통해 이루어집니다. Opera Mini를 제외한 거의 모든 브라우저는 작성 당시 WebSockets에 대한 훌륭한 지원을 제공합니다.


지금까지의 이야기 


과거에는 게임이나 채팅 앱과 같은 실시간 데이터가 필요한 웹 앱을 만들려면 양방향 데이터 전송을 설정하기 위해 "HTTP 프로토콜의 남용"이 필요했습니다. 실시간 기능을 달성하는 데 여러 가지 방법이 사용되었지만 WebSocket만큼 효율적인 방법은 없었습니다. HTTP 폴링, HTTP 스트리밍, Comet, SSE — 모두 자체 단점이 있었습니다.


HTTP polling 


이 문제를 해결하기 위한 첫 번째 시도는 정기적으로 서버를 폴링하는 것입니다. HTTP 긴 폴링 수명주기는 다음과 같습니다.

  1. 클라이언트는 요청을 보내고 응답을 계속 기다립니다.
  2. 서버는 변경, 업데이트 또는 시간 초과가 있을 때까지 응답을 연기합니다. 서버에 클라이언트에게 반환 할 내용이 있을 때까지 요청은 "매달려"있었습니다.
  3. 서버 측에서 일부 변경 또는 업데이트가 발생하면 클라이언트에게 응답을 다시 보냅니다.
  4. 클라이언트는 새로운 긴 폴링 요청을 보내 다음 변경 세트를 수신합니다.

긴 폴링에는 헤더 오버 헤드, 대기 시간, 시간 초과, 캐싱 등 많은 허점이 있었습니다.


HTTP streaming 


이 메커니즘은 초기 요청이 무기한 열린 상태로 유지되기 때문에 네트워크 대기 시간의 어려움을 덜었습니다. 서버가 데이터를 푸시 한 후에도 요청이 종료되지 않습니다. HTTP 스트리밍의 처음 세 라이프 사이클 방법은 HTTP 폴링에서 동일합니다.


그러나 응답이 클라이언트로 다시 전송되면 요청이 종료되지 않습니다. 서버는 연결을 열어두고 변경이 있을 때마다 새 업데이트를 보냅니다.


Server-sent events (SSE) 


SSE를 사용하면 서버는 데이터를 클라이언트로 푸시합니다. 채팅 또는 게임 응용 프로그램은 SSE에 완전히 의존 할 수 없습니다. SSE의 완벽한 사용 사례는 다음과 같습니다 (예 : Facebook 뉴스 피드 : 새 게시물이 올 때마다 서버가 해당 게시물을 타임 라인으로 푸시함) SSE는 기존 HTTP를 통해 전송되며 열린 연결 수에 제한이 있습니다.


이러한 방법은 비효율적일 뿐만 아니라 코드가 개발자를 지치게 만들었습니다.


WebSocket이 약속 된 왕자 인 이유 


WebSocket은 기존 양방향 통신 기술을 대체하도록 설계되었습니다. 위에서 설명한 기존 방법은 전이중 실시간 통신과 관련하여 신뢰할 수 없거나 효율적이지 않습니다.


WebSocket은 SSE와 유사하지만 클라이언트에서 서버로 메시지를 다시 가져 오는데도 도움이 됩니다. 데이터가 단일 TCP 소켓 연결을 통해 제공되므로 연결 제한이 더 이상 문제가 되지 않습니다.


실용 튜토리얼 


소개에서 언급 했듯이 WebSocket 프로토콜에는 두 가지 의제가 있습니다. WebSockets가 이러한 의제를 어떻게 이행하는지 살펴 보겠습니다. 이를 위해 Node.js 서버를 분리하여 React.js로 구축 한 클라이언트에 연결하겠습니다.


안건 1 : WebSocket, 서버와 클라이언트 간 핸드 셰이크 설정 



서버 수준에서 핸드 셰이크 만들기 


단일 포트를 사용하여 HTTP 서버와 WebSocket 서버를 분리 할 수 ​​있습니다. 아래 요점은 간단한 HTTP 서버 작성을 보여줍니다. 일단 생성되면 WebSocket 서버를 HTTP 포트에 연결합니다 :


const webSocketsServerPort = 8000;
const webSocketServer = require('websocket').server;
const http = require('http');
// http 서버 및 웹 소켓 서버를 회전시킵니다.
const server = http.createServer();
server.listen(webSocketsServerPort);
const wsServer = new webSocketServer({
  httpServer: server
});

WebSocket 서버가 생성되면 클라이언트로부터 요청을 수신 할 때 핸드 셰이크를 수락해야 합니다. 브라우저에서 요청을받을 때 고유 한 사용자 ID로 코드에 연결된 모든 클라이언트를 객체로 유지합니다.


// 이 개체에서 모든 활성 연결을 유지하고 있습니다
const clients = {};

// 이 코드는 모든 사용자에 대해 고유 한 사용자 ID를 생성합니다.
const getUniqueID = () => {
  const s4 = () => Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
  return s4() + s4() + '-' + s4();
};

wsServer.on('request', function(request) {
  var userID = getUniqueID();
  console.log((new Date()) + ' Recieved a new connection from origin ' + request.origin + '.');
  // You can rewrite this part of the code to accept only the requests from allowed origin
  const connection = request.accept(null, request.origin);
  clients[userID] = connection;
  console.log('connected: ' + userID + ' in ' + Object.getOwnPropertyNames(clients))
});

연결이 수락되면 어떻게 됩니까? 


연결을 설정하기 위해 일반 HTTP 요청을 보내는 동안 요청 헤더에서 클라이언트는 * Sec-WebSocket-Key *를 보냅니다. 서버는 이 값을 인코딩 및 해시하고 사전 정의 된 GUID를 추가합니다. 서버가 전송 한 핸드 셰이크의 "* Sec-WebSocket-Accept *"에 생성 된 값을 반영합니다.


서버에서 요청이 수락되면 (제작에서 필요한 유효성 검사 후) 핸드 셰이크 상태 코드 101로 이행됩니다. 브라우저에서 상태 코드 101 이외의 것이 표시되면 WebSocket 업그레이드가 실패하고 일반 HTTP 시맨틱이 나타납니다. 따르십시오.


* Sec-WebSocket-Accept * 헤더 필드는 서버가 연결을 수락 할 것인지 여부를 나타냅니다. 또한 응답에 "업그레이드 *"헤더 필드가 없거나 "업그레이드 *"가 웹 소켓과 같지 않으면 WebSocket 연결에 실패한 것입니다.


성공적인 서버 핸드 셰이크는 다음과 같습니다.


HTTP GET ws://127.0.0.1:8000/ 101 Switching Protocols
Connection: Upgrade
Sec-WebSocket-Accept: Nn/XHq0wK1oO5RTtriEWwR4F7Zw=
Upgrade: websocket

클라이언트 레벨에서 핸드 셰이크 만들기 


클라이언트 수준에서 서버와 연결을 설정하기 위해 서버에서 사용하는 것과 동일한 WebSocket 패키지를 사용하고 있습니다 (Web IDL의 WebSocket API는 W3C에 의해 표준화 됨). 서버가 요청을 수락하면 브라우저 콘솔에 "WebSocket Client Connected"가 표시됩니다.


서버에 연결하기 위한 초기 스캐 폴드는 다음과 같습니다.


import React, { Component } from 'react';
import { w3cwebsocket as W3CWebSocket } from "websocket";

const client = new W3CWebSocket('ws://127.0.0.1:8000');

class App extends Component {
  componentWillMount() {
    client.onopen = () => {
      console.log('WebSocket Client Connected');
    };
    client.onmessage = (message) => {
      console.log(message);
    };
  }
  
  render() {
    return (
      <div>
        Practical Intro To WebSockets.
      </div>
    );
  }
}

export default App;

핸드 셰이크를 설정하기 위해 클라이언트가 다음 헤더를 보냅니다.


HTTP GET ws://127.0.0.1:8000/ 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: vISxbQhM64Vzcr/CD7WHnw==
Origin: http://localhost:3000
Sec-WebSocket-Version: 13

이제 클라이언트와 서버가 상호 핸드 셰이크에 연결되었으므로 WebSocket 연결은 메시지를 수신 할 때 메시지를 전송하여 WebSocket 프로토콜의 두 번째 의제를 충족시킬 수 있습니다.


아젠다 2 : 실시간 메시지 전송 


1*Nzx8FJa3bTZSri9BED3I1g.gif?ssl=1 

컨텐츠 수정의 실시간 스트리밍. 


사용자가 함께 문서를 수정하고 편집 할 수 있는 기본 실시간 문서 편집기를 코딩하겠습니다. 두 가지 이벤트를 추적하고 있습니다.

  1. 사용자 활동 : 사용자가 참여하거나 떠날 때마다 메시지를 연결된 다른 모든 클라이언트에게 브로드 캐스트 합니다.
  2. 컨텐츠 변경 : 편집기의 컨텐츠가 변경 될 때마다 연결된 다른 모든 클라이언트에게 브로드 캐스트 됩니다.

이 프로토콜을 사용하면 메시지를 이진 데이터 또는 UTF-8로 송수신 할 수 있습니다 (N.B. UTF-8 전송 및 변환은 오버 헤드가 적습니다).


소켓 이벤트 (onopen, onclose 및 message)를 잘 이해하고 있다면 WebSocket을 이해하고 구현하는 것은 매우 쉽습니다. 용어는 클라이언트와 서버 측에서 동일합니다.