> For the complete documentation index, see [llms.txt](https://docs.sysout.co.kr/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.sysout.co.kr/web/back-end/what-is-spring-framework/etc/websocket.md).

# Websocket

## Spring Websocket

[위키백과 보기](https://ko.wikipedia.org/wiki/%EC%9B%B9%EC%86%8C%EC%BC%93)

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

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

* 주식과 같은 시세 거래 사이트
* 실시간 채팅 사이트
* SNS의 실시간 알림
* Gmail의 실시간 메일 수신

## 프로젝트 생성

프로젝트는 `Spring Legacy Project` → `Spring 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에 다음과 같이 추가한다.

```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](https://mvnrepository.com/)에서 검색하여 적절한 버전을 사용한다.

## WebSocket 서버 생성

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

> com.hacademy.ws.server.WebSocketBasicServer.java

```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가 필요하다.

![servlet-context.xml에 websocket namespace 활성화](/files/-MdFoUsOckJ1Bi5xW0JM)

```xml
<?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

```java
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

```html
<%@ 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**

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

### **handleTextMessage**

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

### **WebSocketSession**

[Spring API Reference 보기](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/socket/WebSocketSession.html)

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

### **CloseStatus**

[Spring API Reference 보기](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/socket/CloseStatus.html)

웹소켓 종료 상태 코드와 원인을 제공하는 클래스.\
제공되는 상태 코드는 [RFC 6455](https://tools.ietf.org/html/rfc6455#section-7.4.1) 규약을 따른다.

### **TextMessage**

[Spring API Reference 보기](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/socket/TextMessage.html)

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

### 웹소켓 서버 등록

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

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

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

```xml
<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 보기](https://developer.mozilla.org/ko/docs/Web/API/WebSocket)를 눌러서 확인할 수 있다.

### **웹소켓 연결 함수**

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

```javascript
function connect(){

}
```

### **웹소켓 주소 생성**

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

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

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

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

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

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

### **웹소켓 연결 생성**

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

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

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

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

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

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

### **콜백 함수 설정**

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

* onopen : 웹소켓 연결이 수립된 후 실행될 함수
* onclose : 웹소켓 연결이 종료된 후 실행될 함수
* onerror : 웹소켓 연결 오류 발생 후 실행될 함수
* onmessage : 웹소켓을 통해 메세지가 수신되었을 경우 실행될 함수

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

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

### **웹소켓 종료 함수 생성**

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

```javascript
function disconnect(){

}
```

### **웹소켓 연결 종료**

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

```javascript
socket.close();
```

### **연결/종료 버튼 생성**

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

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

### **메세지 전송 함수 생성**

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

```javascript
function send(){

}
```

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

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

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

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

### **메세지 전송**

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

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

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

```javascript
if(!text){
    return;
}
```

실제 전송을 수행한다.

```javascript
socket.send(text);
```

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

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