본문 바로가기
  • 저희는 평생 개발할 운명이걸랑요
오늘의 코드/SpaciousKitchen

[Socket.io와 WebSocket]

by SpaciousKitchen 2021. 5. 24.

Socket통신이란 ?

프로그램이 네트워크에서 데이터를 송수신할 수 있도록, 네트워크 환경에 연결할 수 있게 만들어진 연결부. 바로 네트워크 소켓(Socket)이다.

 

 

TCP/IP 소켓 프로그래밍(Socket Programming)

 

1.클라이언트 소켓(Client Socket)과 서버 소켓(Server Socket)

 

두 개의 시스템(또는 프로세스)이 소켓을 통해 네트워크 연결(Connection)을 만들기 위해서는
한 곳에서 그 대상이 되는 곳으로 연결을 요청해야 한다. IP 주소와 포트 번호로 식별되는 대상에게, 데이터 송수신을 위한 네트워크 연결을 수립할 의사를 전달 해야한다.

 

 

TCP의 연결 방식과 같이 Server가 받아드릴 준비가 되어있어야 연결이 진행된다.
서버 소켓은 클라이언트 소켓의 연결 요청을 받아들이는 역할만 수행할 뿐, 직접적인 데이터 송수신은 서버 소켓의 연결 요청 수락의 결과로 만들어지는 새로운 소켓을 통해 처리된다.

 

2.소켓 API(Socket API) 실행 흐름

 

클라이언트 소켓(Client Socket)

[1]생성(create)한 다음, 서버 측에 [2]연결(connect)서버 소켓에서 연결이 받아들여지면 데이터를 [3]송수신(send/recv)
하고, 모든 처리가 완료되면 소켓(Socket)을 [4]닫는다.(close)

서버 소켓(Server Socket)
 

소켓(Socket)을
 [1]생성(create) 서버가 사용할 IP 주소와 포트 번호를 생성한 소켓에 [2]결합(bind)한다. 그런 다음 클라이언트로부터 연결 요청이 수신되는지 [3]주시(listen)하고, 요청이 수신되면 요청을 [4]받아들여(accept) 데이터 통신을 위한 소켓을 생성한다.

일단 새로운 소켓을 통해 연결이 수립(ESTABLISHED)되면, 클라이언트와 마찬가지로 데이터를
 [5]송수신(send/recv)할 수 있습니다. 마지막으로 데이터 송수신이 완료되면, 소켓(Socket)을 [6]닫는다.(close).

 

3.클라이언트 소켓 프로그래밍. (Client Socket Programming)

 

클라이언트 소켓 생성. (socket())

소켓의 종류를 지정할 수 있는데, TCP 소켓을 위해서는 스트림(Stream) 타입, UDP 소켓을 위해서는 데이터그램(Datagram) 타입을 지정할 수 있습니다.
시점에는 어떠한 "연결 대상"에 대한 정보도 들어 있지 않습니다. 

 

연결 요청. (connect())

connect() API는 "IP주소"와 "포트 번호"로 식별되는 대상(Target)으로 연결 요청을 보냅니다.
connect() API는 블럭(Block) 방식으로 동작합니다. 즉, 연결 요청에 대한 결과(성공, 거절, 시간 초과 등)가 결정되기 전에는 connect()의 실행이 끝나지 않는다

데이터 송수신. (send()/recv())

connect() API 호출이 성공하면, 이제 send() / recv API를 통해 데이터를 주고 받을 수 있다.

연결된 소켓을 통해 데이터를 보낼 때는 send(), 데이터를 받을 때는 recv API를 사용합니다. 
두 API 모두 실행 결과(성공, 실패, 종료)가 결정되기 전까지는 API가 리턴되지 않습니다. 특히 recv()는 데이터가 수신되거나, 에러가 발생하기 전에는 실행이 종료되지 않기 때문에, 데이터 수신 작업을 생각만큼 단순하게 처리하기 쉽지 않습니다.

send()의 경우 데이터를 보내는 주체가 자기 자신이기 때문에, 얼마만큼의 데이터를 보낼 것인지, 언제 보낼 것인지를 알 수 있습니다. 하지만 데이터를 수신하는 경우, 통신 대상이 언제, 어떤 데이터를 보낼 것인지를 특정할 수 없기 때문에 recv() API가 한번 실행되면 언제 끝날지 모르는 상태가 되는 것입니다.

그래서 데이터 수신을 위한 recv() API는 별도의 스레드에서 실행합니다. 소켓의 생성과 연결이 완료된 후, 새로운 스레드를 하나 만든 다음 그곳에서 recv()를 실행하고 데이터가 수신되길 기다리는 것이죠.

소켓 닫기. (close())

더 이상 데이터 송수신이 필요없게되면, 소켓을 닫기 위해  close()API를 호출합니다. 
close() 에 의해 닫힌 소켓은 더 이상 유효한 소켓이 아니기 때문에, 해당 소켓을 사용하여 데이터를 송수신할 수 없습니다.

 

4.서버 소켓 프로그래밍. (Server Socket Programmng)

 

 

서버 소켓 생성. (socket())

서버 소켓을 사용하려면 최초에 소켓을 생성해야 합니다. 

 

서버 소켓 바인딩. (bind())

운영체제에서는 소켓들이 중복된 포트 번호를 사용하지 않도록, 내부적으로 포트 번호와 소켓 연결 정보를 관리합니다. 그리고 
bind() API는 해당 소켓이 지정된 포트 번호를 사용할 것이라는 것을 운영체제에 요청하는 API인 것이죠. 만약 지정된 포트 번호를 다른 소켓이 사용하고 있다면, bind() API는 에러를 리턴합니다.


서버 소켓은 고정된 포트 번호를 사용합니다.그 포트 번호로 클라이언트의 연결 요청을 받아들이죠. 그래서 운영체제가 특정 포트 번호를 서버 소켓이 사용하도록 만들기 위해 소켓과 포트 번호를 결합(bind)해야 하는데, 이 때 사용하는 API가 바로 bind인 것입니다.

 

클라이언트 연결 요청 대기. (listen())

 클라이언트에 의한 연결 요청이 수신될 때까지 기다리는 것인데요, listenAPI 가 그 역할을 수행합니다.


isten() API가 대기 상태에서 빠져나오는 경우는 크게 두 가지입니다. 클라이언트 요청이 수신되는 경우와, 에러가 발생(소켓 close() 포함)하는 경우이다.  listen()의 리턴 값으로 판단할 수 있는 것은 클라이언트 연결 요청이 수신되었는지(SUCCESS), 그렇지 않고 에러가 발생했는지(FAIL) 뿐이다.

대신 클라이언트 연결 요청에 대한 정보는 시스템 내부적으로 관리되는 큐(Queue)에서 쌓이게 되는데, 이 시점에서 클라이언트와의 연결은 아직 완전히 연결되지 않은(not ESTABLISHED state) 대기 상태입니다.

대기 중인 연결 요청을 큐(Queue)로부터 꺼내와서, 연결을 완료하기 위해서는 accept() API를 호출해야 합니다.

 

클라이언트 연결 수립. (accept())

최종적으로 연결 요청을 받아들이는 역할을 수행하는 것은 accept() API 입니다.

실질적인 데이터 송수신은 accept API에서 생성된, 연결(Connection)이 수립(Established)된 소켓(Socket)을 통해 처리됩니다.

 

데이터 송수신. (send()/recv())

 클라이언트 소켓 처리 과정과 동일

 

소켓 연결 종료. (close())

클라이언트 소켓 처리 과정과 마찬가지로 소켓을 닫기 위해서는 close() API를 호출한다.
 최초 socket() API를 통해 생성한 서버 소켓에 더해, accpet() API 호출에 의해 생성된 소켓도 관리해야한다.

 

 

1.HTTP와 Socek통신의 차이 

HTTP통신

 

 


Client의 요청(Request)이 있을 때만 서버가 응답(Response)하여 해당 정보를 전송하고 곧바로 연결을 종료하는 단방향 통신 방식
Http 통신은 실시간 연결이 아닌, 필요한 경우에만 Server로 접근하는 콘텐츠 위주의 데이터를 사용할 때 용이하다.

 

Server와 Client가 특정 Port를 통해 연결을 성립하고 있어 실시간으로 양방향 통신을 하는 방식이다.
Server 역시 Client로 요청을 보낼 수 있으며 계속 연결을 유지하는 연결지향형 통신이기 때문에 실시간 통신이 필요한 경우에 자주 사용
된다.

 


 

 

WebSocket의 탄생

웹 페이지를 만들려면 브라우저와 웹 서버 사이에 더 자유로운 양방향 메시지 송수신(bi-directional full-duplex communication)이

필요했고 그래서 HTML5 표준안의 일부로 WebSocket API(이후 WebSocket)가 등장했다.

소켓을 이용하여 자유롭게 데이터를 주고 받을 수 있다. 즉 기존의 요청-응답 관계 방식보다 더 쉽게 데이터를 교환할 수 있다.

 

 

브라우저는 "Upgrade: websocket" 헤더 등과 함께 랜덤하게 생성한 키를 서버에 보낸다. 웹 서버는 이 키를 바탕으로 토큰을 생성한 후 브라우저에 돌려준다. 이런 과정으로 WebSocket 핸드쉐이킹이 이루어진다.

HTML5 웹소켓은 매우 유용한 기술이지만, 브라우저별로 지원하는 웹소켓 버전이 다르며 오래된 브라우저의 경우 아예 지원하지 않는다. 따라서 자바스크립트를 이용하여 브라우저에 상관없이 실시간 웹을 구현할 수 있는 Socket.IO를 좀 더 많이 사용하고 있다.

WebSocket예제

 

if ('WebSocket' in window) {  
    var oSocket = new WebSocket(“ws://localhost:80”);

    oSocket.onmessage = function (e) { 
        console.log(e.data); 
    };

    oSocket.onopen = function (e) {
        console.log(“open”);
    };

    oSocket.onclose = function (e) {
        console.log(“close”);
    };

    oSocket.send(“message”);
    oSocket.close();
}

 

 new WebSocket() 메서드로 웹서버와 연결한다.

1.생성된 WebSocket 인스턴스를 이용하여 소켓에 연결할 때(onopen),

2.소켓 연결을 종료할 때(onclose), 메시지를 받았을 때(onmessage) 등의 이벤트를 각각 정의할 수 있다.

3.서버에 메시지를 보내고 싶을 때에는 send() 메서드를 이용한다. 

 

 

 

Socket.io

Socket.IO는 node.js 기반으로 만들어진 기술로, 거의 모든 웹 브라우저와 모바일 장치를 지원하는 실시간 웹 애플리케이션 지원 라이브러리이다. WebSocket, FlashSocket, AJAX Long Polling, AJAX Multi part Streaming, IFrame, JSONP Polling 등 현존하는 대부분의 실시간 웹 기술들을 추상화해 놓았다. 다시 말해, Socket.IO는 자바스크립트를 이용하여 브라우저 종류에 상관없이 실시간 웹을 구현할 수 있도록 한 기술

// 80 포트로 소켓을 연다
var io = require('socket.io').listen(80);

// connection이 발생할 때 핸들러를 실행한다.
io.sockets.on('connection', function (socket) {  
// 클라이언트로 news 이벤트를 보낸다.
    socket.emit('news', { hello: 'world' });

// 클라이언트에서 my other event가 발생하면 데이터를 받는다.
socket.on('my other event', function (data) {  
        console.log(data);
    });
});

 

<script src="/socket.io/socket.io.js"></script>  
<script>  
// localhost로 연결한다.
var socket =  
  io.connect('http://localhost');

// 서버에서 news 이벤트가 일어날 때 데이터를 받는다.
socket.on('news',  
  function (data) {
    console.log(data);
  //서버에 my other event 이벤트를 보낸다.
    socket.emit('my other event', 
      { my: 'data' });
});
</script>  

  

브라우저에서 클라이언트 페이지를 열면 클라이언트 콘솔에는 "{ hello : 'world' }"가, 서버 콘솔에는 "{ my : 'data' }"가 출력되는 것을 확인할 수 있을 것이다.

 

 

 

댓글