Websocket

Spring Websocket

위키백과 보기

웹소켓(WebSocket)은 하나의 TCP 접속에 전이중 통신 채널을 제공하는 컴퓨터 통신 프로토콜이다. 전이중 통신이란 양방향 동시 통신을 말한다. HTTP 통신과는 구별되며, HTTP 통신을 기반으로 수립된다.

웹소켓을 이용하여 기존의 홈페이지에서 구현할 수 없는 실시간 처리가 가능하도록 할 수 있다. 대표적으로 구현된 예시는 다음과 같다.

  • 주식과 같은 시세 거래 사이트

  • 실시간 채팅 사이트

  • SNS의 실시간 알림

  • Gmail의 실시간 메일 수신

프로젝트 생성

프로젝트는 Spring Legacy ProjectSpring MVC Project 로 생성하여 진행한다. (인터넷을 찾아보면 거의 모든 예제가 Spring boot로 되어 있다)

프로젝트 정보

프로젝트 정보는 다음과 같이 설정한다.

  • 프로젝트명 : websocket

  • Top-level package : com.hacademy.ws

의존성 추가

웹소켓을 사용하기 위해서는 다음 의존성이 필요하다.

  • org.springframework.spring-websocket : 스프링에서 웹소켓을 지원하는 라이브러리

  • com.fasterxml.jackson.core.jackson-databind : Object와 JSON string 간의 변환을 담당하는 라이브러리

pom.xml에 다음과 같이 추가한다.

<dependencies>
    <!-- 웹소켓 라이브러리 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-websocket</artifactId>
        <version>4.3.25.RELEASE</version>
    </dependency>
		
    <!-- json 제어 -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.10.1</version>
    </dependency>
</dependencies>

버전은 maven repository에서 검색하여 적절한 버전을 사용한다.

WebSocket 서버 생성

WebSocket 서버를 다음과 같이 생성한다.

com.hacademy.ws.server.WebSocketBasicServer.java

@Slf4j
//public class WebSocketBasicServer implements WebSocketHandler{
public class WebSocketBasicServer extends TextWebSocketHandler{
	
    /**
    * 접속 시 실행되는 메소드
    * - session : 사용자의 웹소켓 정보(HttpSession이 아님!)
    */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        log.info("접속");
        log.info("session : {}", session);
    }

    /**
    * 메세지 수신 시 실행될 메소드
    * - session : 사용자(전송한 사용자)의 웹소켓 정보(HttpSession이 아님!)
    * - message : 사용자가 전송한 메세지 정보
    * 		- payload : 실제 보낸 내용
    * 		- byteCount : 보낸 메세지 크기(byte)
    * 		- last : 메세지 종료 여부
    */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        log.info("수신");
        log.info("session : {}", session);
        log.info("message : {}", message);
    }

    /**
    * 사용자 접속 종료시 실행되는 메소드
    * - session : 사용자의 웹소켓 정보(HttpSession이 아님!)
    * - status : 접속이 종료된 원인과 관련된 정보
    */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        log.info("종료");
        log.info("session : {}", session);
        log.info("status : {}", status);
    }

}

WebSocket Server 등록

웹소켓은 사용자 요청에 의해 생성되므로 DispatcherServlet 설정에 작성해야 한다. 등록을 위해서는 spring-websocket 관련된 namespace가 필요하다.

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans ...>
    <beans:bean id="basicServer" class="com.hacademy.ws.server.WebSocketBasicServer"></beans:bean>
    <websocket:handlers>
        <!-- 접속주소는 ws://localhost:8080/websocket/basicServer -->
        <websocket:mapping handler="basicServer" path="/basicServer"/>
    </websocket:handlers>
</beans:bean>

WebSocket Client Page 생성

웹소켓 서버에 접속할 클라이언트 페이지를 구현한다. 주소는 /websocket/basic으로 설정한다.

com.hacademy.ws.controller.WebSocketClientController

package com.hacademy.ws.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/websocket")
public class WebSocketClientController {
	
    @GetMapping("/basic")
    public String basic() {
        return "websocket/basic";
    }
	
}

/WEB-INF/views/websocket/basic.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<script>
    //소켓 변수
    var socket;
    
    //웹소켓 접속 함수
    function connect(){
        var uri = "ws://localhost:8080/websocket/basicServer";
        socket = new WebSocket(uri);
		
        //연결이 되었는지 안되었는지 확인할 수 있도록 예약 작업(콜백)을 설정
        socket.onopen = function(){
            console.log("서버와 연결되었습니다");
        };
        socket.onclose = function(){
            console.log("서버와 연결이 종료되었습니다");
        };
        socket.onerror = function(){
            console.log("서버 연결 과정에서 오류가 발생했습니다");
        };
        socket.onmessage = function(){
            console.log("메세지가 도착했습니다");
        };
    }
    //웹소켓 종료 함수
    function disconnect(){
        socket.close();
    }
    //메세지 전송 함수 : 입력된 글자를 불러와서 서버에 전송
    function send(){
	        var text = document.querySelector("#chat-input").value;
		    if(!text){
			    return;
		    }
		
		    socket.send(text);
		    document.querySelector("#chat-input").value = "";//창 지우기
    }
</script>

<h1>Basic Websocket Example</h1>

<button onclick="connect();">접속</button>
<button onclick="disconnect();">종료</button>

<hr>
<input type="text" id="chat-input">
<button onclick="send();">전송</button>

코드 설명

웹소켓 서버

웹소켓 서버는 세 개의 메소드로 구성되어 있다. 이 메소드들은 상속받은 클래스인 TextWebSocketHandler를 통해 확인할 수 있다.

  • public void afterConnectionEstablished(WebSocketSession session)

  • public void afterConnectionClosed(WebSocketSession session, CloseStatus status)

  • protected void handleTextMessage(WebSocketSession session, TextMessage message)

afterConnectionEstablished

클라이언트와의 웹소켓 연결이 수립된 직후에 실행되는 핸들러 메소드이다. 실행 시 매개변수로 WebSocketSession이 제공되며 클라이언트 정보와 관리 명령들이 존재한다.

사용자 접속 시 서버에서 수행해야 할 작업이 있다면 이곳에서 처리한다.(ex : 사용자 등록, 접속 알림 등)

afterConnectionClosed

클라이언트와의 웹소켓 연결이 종료된 직후에 실행되는 핸들러 메소드이다. 실행 시 매개변수로 WebSocketSessionCloseStatus가 제공되며 이는 클라이언트 정보와 종료 상태에 대한 정보를 가지고 있다.

handleTextMessage

클라이언트가 연결된 웹소켓을 통해 전송한 텍스트 메세지를 수신한 직후에 실행되는 핸들러 메소드이다. WebSocketSessionTextMessage가 제공되며 이는 클라이언트 정보와 전송 메세지 정보를 가지고 있다.

WebSocketSession

Spring API Reference 보기

웹소켓 세션의 정보를 제공하는 클래스. 웹소켓 연결을 통해 메세지를 전송하거나 연결을 종료할 수 있다.

CloseStatus

Spring API Reference 보기

웹소켓 종료 상태 코드와 원인을 제공하는 클래스. 제공되는 상태 코드는 RFC 6455 규약을 따른다.

TextMessage

Spring API Reference 보기

웹소켓에서 전송된 텍스트 메세지 정보를 가진 클래스. byte크기, 분할여부, payload를 확인할 수 있다.

웹소켓 서버 등록

웹소켓 서버를 등록하기 위해서는 DispatcherServlet 설정파일인 servlet-context.xml에 websocket 태그를 사용해야 한다.

<beans:bean id="basicServer" class="com.hacademy.ws.server.WebSocketBasicServer"></beans:bean>

위의 코드를 이용하여 WebSocketBasicServer를 Spring bean으로 등록하고, 아래 코드를 이용하여 WebSocket 서버로 설정한다.

<websocket:handlers>
    <!-- 접속주소는 ws://localhost:8080/websocket/basicSer -->
    <websocket:mapping handler="basicServer" path="/basicServer"/>
</websocket:handlers>

mapping에는 등록된 bean의 id를 작성하고, path에는 현재 프로젝트의 기본 웹소켓 접속주소인 ws://localhost:8080/websocket을 제외한 뒷부분의 주소를 작성한다.

따라서 basicServer의 접속 주소는 다음과 같다. ws://localhost:8080/websocket/basic

웹소켓 클라이언트

웹소켓 클라이언트 페이지에서는 Javascript WebSocket API를 사용한다. Mozila Reference 보기를 눌러서 확인할 수 있다.

웹소켓 연결 함수

웹소켓의 연결 작업을 처리할 수 있도록 connect 함수를 생성한다.

function connect(){

}

웹소켓 주소 생성

connect 함수에 웹소켓 연결을 위한 주소 변수를 생성한다. 웹소켓의 주소는 ws로 시작한다. (sockjs를 사용하면 http로 접속이 가능하지만 지금 문서에서는 다루지 않는다)

var uri = "ws://localhost:8080/websocket/basicServer";

이식성을 높이기 위해서 다음과 같이 계산하여 작성할 수 있다.

var uri = "ws://";
uri += location.host;
uri += location.pathname.substring(0, location.pathname.indexOf("/", 2));
uri += "/basicServer";

jsp 페이지에서는 EL을 사용하여 다음과 같이 context path 계산이 가능하다.

var uri = "ws://";
uri += location.host;
uri += "${pageContext.request.contextPath}";
uri += "/basicServer";

웹소켓 연결 생성

웹소켓 연결은 다음 코드 형태로 생성이 가능하다.

var socket = new WebSocket(주소 [,옵션]);

주소는 접속 가능한 웹소켓 주소를 작성하며, 옵션은 생략이 가능하다. 예제 코드에서는 socket 변수의 활용범위를 넓히기 위하여 다음과 같이 생성하였다.

var socket;
function connect(){
    var uri = "...주소 생략...";
    socket = new WebSocket(uri);
}

다음과 같이 window에 등록하여 사용해도 된다(단, 이름 충돌에 유의해야 한다).

function connect(){
    var uri = "...주소 생략...";
    window.socket = new WebSocket(uri);
}

콜백 함수 설정

connect 함수에서 웹소켓을 생성한 뒤 다음과 같은 콜백 함수를 설정할 수 있다.

  • onopen : 웹소켓 연결이 수립된 후 실행될 함수

  • onclose : 웹소켓 연결이 종료된 후 실행될 함수

  • onerror : 웹소켓 연결 오류 발생 후 실행될 함수

  • onmessage : 웹소켓을 통해 메세지가 수신되었을 경우 실행될 함수

콜백 함수는 예약작업이라고 생각하면 이해하기 쉬우며, 필요하지 않을 경우 설정하지 않아도 된다. 이 문서의 클라이언트 예제파일에서는 console.log 함수를 이용하여 콘솔 메세지만 출력하도록 구성하였다.

//연결이 되었는지 안되었는지 확인할 수 있도록 예약 작업(콜백)을 설정
socket.onopen = function(){
    console.log("서버와 연결되었습니다");
};
socket.onclose = function(){
    console.log("서버와 연결이 종료되었습니다");
};
socket.onerror = function(){
    console.log("서버 연결 과정에서 오류가 발생했습니다");
};
socket.onmessage = function(){
    console.log("메세지가 도착했습니다");
};

웹소켓 종료 함수 생성

웹소켓을 원하는 시점에 연결 종료할 수 있도록 disconnect 함수를 생성한다.

function disconnect(){

}

웹소켓 연결 종료

웹소켓 연결 종료는 disconnect 함수에서 다음 코드로 수행한다.

socket.close();

연결/종료 버튼 생성

연결과 종료 버튼을 다음과 같이 생성하여 각각의 함수와 연결한다.

<button onclick="connect();">접속</button>
<button onclick="disconnect();">종료</button>

메세지 전송 함수 생성

웹소켓 연결 후 메세지 전송을 위하여 send 함수를 생성한다.

function send(){

}

메세지 작성창/전송버튼 구현

메세지 작성창은 input 태그로, 전송버튼은 button 태그로 구현한다.

<input type="text" id="chat-input">
<button onclick="send();">전송</button>

전송버튼을 누르면 send 함수가 실행되도록 작성한다.

메세지 전송

메세지 전송을 위해서는 입력창의 값을 알아야 한다.

var text = document.querySelector("#chat-input").value;

입력창의 값이 없을 경우는 전송하지 않는다.

if(!text){
    return;
}

실제 전송을 수행한다.

socket.send(text);

전송 후 입력창에 작성되어 있는 글자를 삭제한다.

document.querySelector("#chat-input").value = "";//창 지우기

Last updated