Spring Sockets & Sock js

0

SpringBoot

목록 보기
6/12

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로 연다. 라이브러리도 추가안함. 브라우저가 자체 지원해준다.
  • ws는 websocket, http와 비슷함. wss ≒ https
  • replyEcho?bno=1234 처럼 쿼리스트링도 넣을 수 있다.
  • ws.onopen = 이벤트 핸들러(리스너) 커넥션이 연결됐을 때 찾음
  • ws.onmessage, ws.onclose, ws.onerror : 커넥션 하고 (ws.onopen 쪽으로) 들어가는게 좋다.
    • 커넥션도 안됐는데 onmessage를 받는건 이상하다. 하지만 밖으로 빼도 됨. 어차피 메시지가 왔을 때 message가 왔을 때 event가 발생되는 것이고, close, error도 동일하다.
  • setTimeout( function() {connect(); }, 1000); : close가 일어났다면 1초에 한번씩 커넥션을 맺겠다.

전체공지 메시지 보내기

  • sendMessage 메서드를 이용해서 TextMessage를 보낸다. 이때 message.getPayload()를 이용해서 메시지의 내용을 보내기
    • payload : 전송되는 실질적인 데이터(data)를 의미

댓글 작성 실시간 알림 기능 구현하기

  • 로그인한 사용자에 대한 WebSocketSession값을 Map으로 받아오고, 메시지를 보낼 대 getId메서드를 새로 만들어서 로그인한 사용자의 값을 받아옴

    이후 아래 (handleTextMessage 메서드 내부) 실행


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')로 해당 구독을 받는 것!!
      ➡︎ 토픽 구독방식!

  • 메시지를 json으로 보내줘야함. JSON.stringify 사용!

출처 : https://youtu.be/gQyRxPjssWg

profile
백엔드를 공부하고 있습니다.

0개의 댓글