WebSocket을 이용하여 클라이언트 애플리케이션 작성하기

WebSocket은 ws 프로토콜을 기반으로 클라이언트와 서버 사이에 지속적인 완전 양방향 연결 스트림을 만들어 주는 기술입니다. 일반적인 웹소켓 클라이언트는 사용자의 브라우저일 것이지만, 그렇다고 해서 이 프로토콜이 플랫폼에 종속적이지는 않습니다.

참고: 우리에게는 작동하는 chat/server 시스템 예제 코드 조각이 있습니다. 이는 우리의 인프라가 WebSocket 예제들을 제대로 호스팅할 수 있는 환경이 되면 공유할 것입니다.

참고: 이 기능은 Web Worker에서 사용할 수 있습니다.

WebSocket 객체 생성하기

WebSocket 프로토콜을 사용하여 통신하기 위해서는 WebSocket객체를 생성해야 합니다. 이 객체는 자동으로 서버로의 연결을 열려고 할 것입니다.

WebSocket 생성자는 하나의 필수 파라미터와 하나의 선택 파라미터를 받습니다.

WebSocket WebSocket(
  in DOMString url,
  in optional DOMString protocols
);
url

연결할 URL으로, 이것은 WebSocket 서버가 응답할 URL이어야 합니다.

protocols Optional

하나의 프로토콜 문자열, 또는 프로토콜 문자열의 배열입니다. 이 문자열들은 서브 프로토콜을 지정하는데 사용되어, 하나의 서버가 여러 개의 WebSocket 서브 프로토콜을 구현할 수 있도록 해줍니다. 예를 들어, 하나의 서버가 처리하는 상호작용이 지정된 protocols에 따라 달라지도록 할 수 있습니다. 만약 프로토콜 문자열을 지정하지 않으면 빈 문자열을 넣은 것으로 간주됩니다.

생성자는 예외를 발생시킬 수 있습니다:

SECURITY_ERR

접속을 시도하고 있는 포트가 차단되었습니다.

연결 에러

만약 연결 시도 중 에러가 발생하면, 먼저 "error"란 이름의 이벤트가 WebSocket 오브젝트로 전달되고, 그로 인해 onerror 핸들러가 실행됩니다. 그 후에 연결이 종료되는 이유를 가리키는 CloseEvent 이벤트가 WebSocket 오브젝트로 전달되고, 그로 인해 onclose 핸들러가 실행됩니다.

Firefox 11부터는 보통 에러 메세지에 대한 설명이 Mozillia 플랫폼의 콘솔에 표시되며, CloseEvent로부터는 RFC 6455, Section 7.4에 정의되어 있는 연결 종료 코드를 받게 됩니다.

예제

이 간단한 예제는 새 웹소켓 오브젝트를 생성하여 ws://www.example.com/socketserver 서버에 접속하는것을 보여줍니다. 이 예제에서는 커스텀 프로토콜인 "protocolOne" 을 리퀘스트에 같이 지정합니다. (이 프로토콜을 지정하는 부분은 생략될 수 있습니다.)

js
var exampleSocket = new WebSocket(
  "ws://www.example.com/socketserver",
  "protocolOne",
);

반환된 exampleSocket 오브젝트의 exampleSocket. readyState 값은 CONNECTING 입니다. readyState 값은 연결이 수립되어 데이터가 전송 가능한 상태가 되면 OPEN 으로 변경됩니다.

만약 여러개의 프로토콜을 유연하게 대응할 수 있는 구조를 가지고 있다면, 연결 시에 배열을 통해 프로토콜의 목록을 지정할 수 있습니다.

js
var exampleSocket = new WebSocket("ws://www.example.com/socketserver", [
  "protocolOne",
  "protocolTwo",
]);

연결이 수립되면(readyStateOPEN 이 되었을 때), exampleSocket.protocol 값을 조사하여 서버가 어떤 프로토콜을 선택했는지 알아낼 수 있습니다.

위의 예제에서 ws 는 http 를 대체합니다. 비슷하게 wss 는 https 를 대체합니다. 웹소켓 연결은 HTTP 업그레이드 메카니즘에 의해 수행되기 때문에 HTTP 서버 주소 지정에 대한 프로토콜 업그레이드 요청은 암시적입니다. (ws://www.example.com 또는 wss://www.example.com. 같이)

서버에 데이터 전송하기

한번 연결이 수립되면 이제부터는 서버로 데이터를 전송할 수 있습니다. 이것을 하기 위해서는 단순히 WebSocket 오브젝트의 send() 호출하여 보내고 싶은 메세지를 지정하기만 하면 됩니다.:

js
exampleSocket.send("Here's some text that the server is urgently awaiting!");

보낼 수 있는 데이터는 String , Blob, 또는 ArrayBuffer 입니다.

참고: 버전 11 아래의 Firefox는 String 데이터 전송만을 지원합니다.

연결을 맺는것은 비동기 작업이고 실패하기 쉬운 작업이기 때문에, WebSocket 오브젝트를 생성하자마자 send() 로 데이터 전송을 시도하는것은 성공하지 않을 가능성이 있습니다. 우리는 연결이 수립된 이후에만 데이터를 전송하도록 하기 위해 onopen 핸들러를 정의하고, 이 위에서 작업합니다.

js
exampleSocket.onopen = function (event) {
  exampleSocket.send("Here's some text that the server is urgently awaiting!");
};

데이터 전송에 JSON 사용하기

JSON 을 사용하면 서버에 복잡한 데이터를 편리하게 보낼 수 있습니다. 예를 들어, 채팅 프로그램이 서버와 JSON으로 캡슐화된 패킷 데이터를 주고받는 프로토콜을 구현한것을 상상해 볼 수 있습니다.:

js
// Send text to all users through the server
function sendText() {
  // Construct a msg object containing the data the server needs to process the message from the chat client.
  var msg = {
    type: "message",
    text: document.getElementById("text").value,
    id: clientID,
    date: Date.now(),
  };

  // Send the msg object as a JSON-formatted string.
  exampleSocket.send(JSON.stringify(msg));

  // Blank the text input element, ready to receive the next line of text from the user.
  document.getElementById("text").value = "";
}

서버로부터 데이터 수신하기

WebSockets는 event-driven API 입니다; 메세지가 수신되면 "message" 이벤트가 onmessage 함수로 전달되게 됩니다. 아래와 같은 코드를 작성하여 수신되는 데이터를 받아볼 수 있습니다.:

js
exampleSocket.onmessage = function (event) {
  console.log(event.data);
};

JSON 오브젝트를 받아서 처리하기

상단의 데이터 전송에 JSON 사용하기 에서 작업한 코드와 연관되는 클라이언트를 생각해 봅시다. 클라이언트에서 받을 수 있는 패킷들의 목록은 다음과 같을 것 입니다.:

  • 로그인 핸드쉐이크
  • 메세지 텍스트
  • 유저 목록 업데이트

위의 메세지들을 받아서 처리하는 코드는 아래와 같을 것 입니다.:

js
exampleSocket.onmessage = function (event) {
  var f = document.getElementById("chatbox").contentDocument;
  var text = "";
  var msg = JSON.parse(event.data);
  var time = new Date(msg.date);
  var timeStr = time.toLocaleTimeString();

  switch (msg.type) {
    case "id":
      clientID = msg.id;
      setUsername();
      break;
    case "username":
      text =
        "<b>User <em>" +
        msg.name +
        "</em> signed in at " +
        timeStr +
        "</b><br>";
      break;
    case "message":
      text = "(" + timeStr + ") <b>" + msg.name + "</b>: " + msg.text + "<br>";
      break;
    case "rejectusername":
      text =
        "<b>Your username has been set to <em>" +
        msg.name +
        "</em> because the name you chose is in use.</b><br>";
      break;
    case "userlist":
      var ul = "";
      for (i = 0; i < msg.users.length; i++) {
        ul += msg.users[i] + "<br>";
      }
      document.getElementById("userlistbox").innerHTML = ul;
      break;
  }

  if (text.length) {
    f.write(text);
    document.getElementById("chatbox").contentWindow.scrollByPages(1);
  }
};

여기서 우리는 JSON.parse() 를 통해 JSON 오브젝트를 JavaScript 오브젝트로 변환합니다. 그 다음 콘텐츠에 따라 분기하고 처리하는 로직을 가집니다.

Text data format

웹소켓을 통해 전달되는 텍스트들은 UTF-8 포멧을 가집니다.

연결을 종료하기

웹 소켓 사용을 마쳤다면 close() 메소드를 호출해 연결을 종료합니다.:

js
exampleSocket.close();

연결을 닫아버리기 전에 bufferedAmount 어트리뷰트를 조사하여 아직 네트워크에 전달되지 않은 데이터가 있는지 검사하는것도 좋은 방법입니다.

보안에 대한 고려 사항

웹소켓은 혼합된 연결 환경에서 이용되어서는안됩니다. 예를들어 HTTPS를 이용해 로드된 페이지에서 non-secure 웹소켓 연결을 수립하는것(또는 반대) 처럼 말입니다. 몇몇 브라우저들은 이를 강제로 금지하고 있습니다. Firefox 버전 8이상도 이를 금지합니다.