WebSocket은 서버와 클라이언트 간에 양방향 통신을 가능하게 하는 프로토콜이다.
HTTP 프로토콜에서는 클라이언트에서 요청을 보내면 서버에서 응답을 하고 연결이 끊어졌지만, WebSocket을 사용하면 클라이언트와 서버 간에 계속해서 연결을 유지하면서 양방향으로 데이터를 주고받을 수 있다.
클라이언트에서는 WebSocket 객체를 만들고 서버와의 연결을 설정한 다음 데이터를 전송할 수 있다.
서버에서는 클라이언트와의 연결을 수신하고 연결을 유지하면서 데이터를 전송할 수 있다.
java
└─ WEBSOCKET
│ WebsocketApplication.java
│
├─config
│ WebSocketConfig.java
│
├─controller
│ ChatController.java
│
└─handler
ChatHandler.java
resources
└─ static
│ ├─ css
│ │ └─ styles.css
│ └─ js
│ └─ main.js
└─ templates
├─ websocket_index.html
└─ websocket_chatroom.html
웹소켓 프로토콜은 기본적으로 Text, Binary 타입을 지원하기 때문에 필요에 따라 * TextWebSocketHandler, BinaryWebSocketHandler 를 상속하여 구현해주면 된다.
import lombok.extern.slf4j.Slf4j;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.BinaryMessage;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Component
public class ChatHandler extends TextWebSocketHandler {
Map<String, WebSocketSession> sessionMap = new HashMap<>(); //웹소켓 세션을 담아둘 맵
Map<String, String> userMap = new HashMap<>(); //사용자
/* 클라이언트가 소켓 연결시 동작 */
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
log.info("SESSION > {} 연결되었습니다.", session.getId());
log.info("SESSION.toString() > {}",session.toString());
super.afterConnectionEstablished(session);
sessionMap.put(session.getId(), session);
JSONObject obj = new JSONObject();
obj.put("type", "getId");
obj.put("sessionId", session.getId());
//클라이언트에게 메세지 전달
session.sendMessage(new TextMessage(obj.toJSONString()));
}
/* 클라이언트로부터 메시지 수신시 동작 */
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String msg = message.getPayload();
log.info("===============Message=================");
log.info("{}", msg);
log.info("===============Message=================");
JSONObject obj = jsonToObjectParser(msg);
for(String key : sessionMap.keySet()) {
// 세션을 찾아서
WebSocketSession wss = sessionMap.get(key);
if(userMap.get(wss.getId()) == null) {
userMap.put(wss.getId(), (String)obj.get("userName")); // 세션 ID - userName 저장
}
JSONArray usersArray = new JSONArray();
usersArray.addAll(userMap.values());
obj.put("users", usersArray);
obj.put("userCount", usersArray.size());
//클라이언트(모든접속자) 에게 메시지 전달
wss.sendMessage(new TextMessage(obj.toJSONString()));
}
log.info("클라이언트로 보낼 obj : {}" , obj);
}
/* 바이너리 메시지 발송 */
@Override
public void handleBinaryMessage(WebSocketSession session, BinaryMessage message) {
//ByteBuffer 객체는 메시지의 실제 데이터를 담고 있음
ByteBuffer byteBuffer = message.getPayload();
// 데이터 확인
log.info("===============handleBinaryMessage=================");
log.info("SESSION {}", session);
log.info("BYTE BUFFER {}", byteBuffer);
//1. 클라이언트에 이미지만 전달 할 경우
/*
for (String key : sessionMap.keySet()) {
WebSocketSession wss = sessionMap.get(key);
try {
wss.sendMessage(new BinaryMessage(byteBuffer)); //버퍼 발송
} catch (IOException e) {
e.printStackTrace();
}
}
*/
// 2. 클라이언트에 이미지와 그 외의 정보를 함께 넘겨줄 경우
JSONObject obj = new JSONObject();
obj.put("type", "file");
obj.put("userCount", sessionMap.size()); // 사용자 수
obj.put("userName", userMap.get(session.getId())); // 사용자 정보
obj.put("sessionId", session.getId()); // 세션 ID 등의 추가 정보 추가
obj.put("imageData", Base64.getEncoder().encodeToString(byteBuffer.array())); // 이미지 데이터를 Base64로 인코딩하여 추가
for (String key : sessionMap.keySet()) {
WebSocketSession wss = sessionMap.get(key);
try {
wss.sendMessage(new TextMessage(obj.toString())); // JSON 객체를 문자열로 변환하여 메시지 전송
} catch (IOException e) {
e.printStackTrace();
}
}
log.info("클라이언트로 보낼 obj : {}" , obj);
}
/* 클라이언트가 소켓 종료시 동작 */
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
log.info("{} 연결이 종료되었습니다.", session.getId());
super.afterConnectionClosed(session, status);
sessionMap.remove(session.getId());
String userName = userMap.get(session.getId());
for(String key : sessionMap.keySet()) {
WebSocketSession wss = sessionMap.get(key);
if(wss == session) continue;
JSONObject obj = new JSONObject();
obj.put("type", "close");
obj.put("userName", userName);
obj.put("userCount", sessionMap.size());
wss.sendMessage(new TextMessage(obj.toJSONString()));
}
userMap.remove(session.getId());
}
/**
* JSON 형태의 문자열을 JSONObejct로 파싱
*/
private static JSONObject jsonToObjectParser(String jsonStr) throws Exception{
JSONParser parser = new JSONParser();
JSONObject obj = null;
obj = (JSONObject) parser.parse(jsonStr);
return obj;
}
}
원래 이미지만 전달 하려면 아래와 같이하면 되는데
이미지
@Override
public void handleBinaryMessage(WebSocketSession session, BinaryMessage message) {
//바이너리 메시지 발송
ByteBuffer byteBuffer = message.getPayload();
//이미지를 발송한다.
for (String key : sessionMap.keySet()) {
WebSocketSession wss = sessionMap.get(key);
try {
wss.sendMessage(new BinaryMessage(byteBuffer)); //버퍼 발송
} catch (IOException e) {
e.printStackTrace();
}
}
} // method
@Slf4j
@Configuration
@EnableWebSocket // 웹소켓 활성화
@RequiredArgsConstructor
public class WebSocketConfig implements WebSocketConfigurer {
//WebSocket 요청 처리를 구성하는 콜백 메서드를 정의
private final ChatHandler chatHandler; //
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// WebSocketHandler 추가
registry.addHandler(chatHandler, "/chat")
.setAllowedOriginPatterns("*"); //도메인이 다른 서버에서도 접속 가능하도록 CORS : setAllowedOprigins(" * ");를 추가해줍니다.
}
}
const socket = new WebSocket('ws://localhost:8080/chat');
// 웹 소켓 연결 이벤트
socket.onopen = function (event) {
console.log("웹소켓 서버와 연결에 성공했습니다.");
};