Spring Sockets
Spring 에서의 적용
- handler가 TextWebSocketHandler 를 상속받는데, TextWebSocketHandler를 보면 AbstractWebSocketHandler라는 추상클래스를 상속받았고, 이 추상클래스는 WebSocketHandler라는 인터페이스의 구현체이다.
AbstractWebSocketHandler는 BinaryWebSocketHandler와 TextWebSocketHandler를 자식으로 가진다.
↳ 이미지 또는 영상을 지속적으로 올리고싶거나 그걸 스트리밍해서 보고싶으면 Binary를 사용하고, 텍스트만 필요하다면 Text핸들러를 상속받아 사용하면 된다.
- 세 메서드를 모두 구현해야 한다.
@Override public void afterConnectionEstablished(WebSocketSession session) { // 연결됐을 때마다 탐 }
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { // 텍스트를 보냈을 때 마다 탐
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { // 닫혔을 때마다 탐
}
>### read.jsp
![](https://velog.velcdn.com/images/serringg/post/6e188594-a197-4d77-a0b3-788c862674b6/image.png)
```js
var ws = new WebSocket("ws://localhost:8080/replyEcho?bno=1234");
// 소켓을 ws로 연다. 라이브러리도 추가안함. 브라우저가 자체 지원해준다.
wss ≒ https
replyEcho?bno=1234
처럼 쿼리스트링도 넣을 수 있다.ws.onmessage
, ws.onclose
, ws.onerror
: 커넥션 하고 (ws.onopen 쪽으로) 들어가는게 좋다.setTimeout( function() {connect(); }, 1000);
: close가 일어났다면 1초에 한번씩 커넥션을 맺겠다.전체공지 메시지 보내기
- sendMessage 메서드를 이용해서 TextMessage를 보낸다. 이때
message.getPayload()
를 이용해서 메시지의 내용을 보내기
- payload : 전송되는 실질적인 데이터(data)를 의미
댓글 작성 실시간 알림 기능 구현하기
ReplyEchoHandler
public class ReplyEchoHandler extends TextWebSocketHandler { // 이미지. 영상을 지속적으로 올리고싶거나 그걸 보고싶으면 Binary를 사용
List<WebSocketSession> sessions = new ArrayList<>(); // 소켓 세션을 관리, 나중에 맵으로 관리. 모든 유저가 들어있음
Map<String, WebSocketSession> userSessions = new HashMap<>(); // WebSocketSession에 sendMessage할것이기 때문에 인자로 필요하다.
@Override
public void afterConnectionEstablished(WebSocketSession session) {
System.out.println("afterConnectionEstablished" + session);
sessions.add(session); // 접속 돼있는 세션(유저)들을 모두 담음, 관리
String senderId = getId(session);
userSessions.put(senderId, session); // senderId에 로그인했으면 userId가 올 것이고 로그인하지 않았으면 session id가 올 것이므로 그냥 put
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { // TextWebSocketHandler이기 때문에 message를 TextMessage
System.out.println("handleTextMessage" + session + ":" + message);
// String senderId = session.getId(); // 브라우저를 2개띄우면 2개가 생기듯 다 구별됨
String senderId = getId(session); // 로그인한 사용자는 WebSocketSession에 있는게 아니라 HttpSession에 존재하기 때문에 따로 빼줘야한다.
// for (WebSocketSession sess : sessions) { // 전체 공지
// sess.sendMessage(new TextMessage(senderId+ ":" + message.getPayload())); // message를 그냥 보낼수도 있지만 가공해서 보내기, message.getPayload()가 진짜 메시지 내용
// }
/* protocol
* json이 제일 좋지만 간단하게 string으로 진행. cmd, 댓글 작성자, 게시글 작성자, bno (reply, user2, user1, 234) , cmd는 설정할수있음
*/
String msg = message.getPayload();
if (StringUtils.isNullOrEmpty(msg)) {
String[] strs = msg.split(",");
if(strs != null && strs.length == 4) { // index out of bound 가 뜰까봐 처리해줌
String cmd = strs[0];
String replyWriter = strs[1];
String boardWrite = strs[2];
String bno = strs[3];
WebSocketSession boardWriterSession = userSessions.get(boardWrite);
if("reply".equals(cmd) && boardWriterSession != null) {
TextMessage tmpMsg = new TextMessage( replyWriter + "님이 " + "<a href='/board/read?bno=" + bno + "'>"+ bno + "번 게시글에 댓글을 달았습니다.");
// 댓글이 작성되면 프론트에서 alert창 띄우고 그걸 누르면 그 게시글로 이동할 수 있도록 href처리해줌
boardWriterSession.sendMessage(tmpMsg);
}
}
}
}
private String getId(WebSocketSession session) {
Map<String, Object> httpSession = session.getAttributes(); // httpSession에 있던 것들을 모두 실어서 map에 넣어줌
User loginUser = (User) httpSession.get(Session.LOGIN);
if (null == loginUser) {
return session.getId(); // 로그인하지 않았다면 WebSocketSession id를 보내고,
} else {
return loginUser.getId();
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
System.out.println("afterConnectionClosed" + session + ":" + status);
}
}
Spring Boot에서의 적용 52:43
@Configuration // spring boot이 설정이라는 걸 알 수있게 해줌 @EnableWebSocket public class SbpWebSocketConfig implements WebSocketConfigurer { // spring mvc에서는 WebMvcConfigurer을 구현했음, 불필요한 다른 설정들을 해주니까 상속받음 @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(new ReplyEchoHandler(), "/replyEcho") // replyEcho : 9090 .setAllowedOrigins("*"); // 다른 설정은 ReplyEchoHandler와 동일하기 때문에 가져옴. } }
socket readyState
1: open, 3:close.
Sock js 1:05:10
- 핸들러를 똑같이 쓰는데,
.withSockJs()
만 붙여주면 됨
- 비슷한데 얘는 SockJs라는 클래스를 사용하는 것임. (얘도 ws readyStatus줘야됨. 안에 코드 더 넣기)
- onopen안에 onmessage, onclose를 넣어놓으면 onopen이 실행됐을 때 이벤트리스너가 등록되는 것인데, 밖에 꺼내놓으면 미리 이벤트리스너를 등록을 해놓고 가져다 쓰는 것! 이 방식이 좀 더 낫다.
STOMP 1:11:35
- 스프링에 종속적이지만 가장 가볍고, (구독방식이므로 하나의 토픽만보고있으면 됨) push 방식으로 사용가능.
@Configuration @EnableWebSocketMessageBroker public class SbpWebSocketMessageBrokerConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/stompTest").setAllowedOrigins("*").withSockJS(); // stomp는 sockjs기반으로 돌기 때문에 withSockJS()가 있어야함 } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker("/topic"); // topic방식과 queue방식이 있는데 topic 사용할 것 // topic이 여러개 있을 수 있는데, /topic 아래에 넣을 수 있음 registry.setApplicationDestinationPrefixes("/"); // 톰캣 modules에 설정한 path그대로 사용 } }
- topic이 여러개 있을 수 있는데, 이는 클라이언트에서 설정
- 서버에서 endpoint로 설정한 "/stompTest" 로 sockJS와 연결
- Stomp.over(sock) : sock위에서 stomp가 돌아간다.
- /TTT: topic은 아니고 controller인데
- controller에 박아줌. stomp가 왜 가볍냐면 소켓에 바로 붙는게 아니라 컨트롤러한테 요청을 하기 때문임 -> 구독 방식 -> 소켓과 강하게 연결돼있는 방식이 아님! 메시지를 보낼때는 controller가 받아서 sendTo로 다시
"/topic/message"
라는 곳으로 보내준다.- 클라이언트에서 보낼때는
client.send('/TTT')
로 보내고, 받을 때(구독)은client.subscribe('topic/message')
로 해당 구독을 받는 것!!
➡︎ 토픽 구독방식!
출처 : https://youtu.be/gQyRxPjssWg