[webSocket] 채팅프로그램만들기

첸첸·2021년 11월 15일
0

webSocket in java

목록 보기
8/9

이전 포스팅을 기반으로 간단한 웹채팅프로그램을 만들어봤다.자바스크립트를 사용해서 클라이언트를 만들었는데, 이전 포스팅대로 만들면...서버와 연결이 안됐다. 거의 몇시간 삽질을 하고 나서 찾으니 스프링부트에서는 자바스크립트에서 기본제공하는 웹소켓을 사용하지 않는다고 한다. 사용을 하려면 Application 클래스에 다음과 같이 ServerEndpointExporter를 bean으로 등록해주어야했다.(SockJS를 사용하는 경우에는 상관없다.)

Application


package com.example.websocket;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@SpringBootApplication
public class WebsocketApplication {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
    public static void main(String[] args) {
        SpringApplication.run(WebsocketApplication.class, args);
    }

}

객체생성


Encoder와 Decoder를 사용해 볼 거여서, 사용자 아이디와 메세지를 하나의 객체로 만들어줬다.

package com.example.websocket.vo;

import lombok.Data;

@Data
public class ChatUser {

    private String userId;
    private String message;
}

Ecoder : 객체를 json 형식의 String으로 변환


인코더의 경우 Encode.Text를 구현해서 생성하면 된다.

package com.example.websocket.websocket;

import com.example.websocket.vo.ChatUser;

import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfig;

import com.google.gson.Gson;

public class ChatEncoder implements Encoder.Text<ChatUser> {
  @Override
  public String encode(ChatUser chatUser) throws EncodeException {
      Gson gson = new Gson();
      return  gson.toJson(chatUser);
  }

  @Override
  public void init(EndpointConfig endpointConfig) {
      
  }

  @Override
  public void destroy() {

  }
}

Decoder : json형식의 String을 객체로 변환


Decoder는 Decoder.Text를 구현해서 생성. 여기서 중요한 것은 willDecode메서드의 return 값을 true로⭐ 놓아야한다는 것이다! 아니면..디코딩이 안돼 형식이 틀리다는 에러값을 출력받을 것이다..

package com.example.websocket.websocket;

import com.example.websocket.vo.ChatUser;
import com.google.gson.Gson;

import javax.websocket.DecodeException;
import javax.websocket.Decoder;
import javax.websocket.EndpointConfig;

public class ChatDecoder implements Decoder.Text<ChatUser> {

  @Override
  public ChatUser decode(String s) throws DecodeException {
      Gson gson = new Gson();
      return gson.fromJson(s,ChatUser.class);
  }

  @Override //decode메소드 이전에 실행
  public boolean willDecode(String s) {
      return true;
  }

  @Override
  public void init(EndpointConfig endpointConfig) {

  }

  @Override
  public void destroy() {

  }
}

Endpoint


package com.example.websocket.websocket;

import com.example.websocket.vo.ChatUser;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
@Component
@ServerEndpoint(value = "/chat/{userId}", encoders = {ChatEncoder.class}, decoders = {ChatDecoder.class})
//value에는 연결할 url "/chat으로 시작하는 모든 url이 연결 가능하다. 
//예) /chat/id1, /chat/id2 모두 가능
// {userId}는 @pathParam을 통해 파라미터로 사용가능
//사용할 encoder와 decoder 설정
public class ChatEndPoint {
  private Session session;
  private static Set<ChatEndPoint> users = new CopyOnWriteArraySet<>();

  @OnOpen
  public void chatOpen(Session session, @PathParam(value="userId") String userId) {
      this.session = session;
      users.add(this);;
      ChatUser chatUser = new ChatUser();
      chatUser.setUserId(userId);
      chatUser.setMessage(userId + "님이 입장하셨습니다.");
      broadcast(chatUser);
      System.out.println("chat opened - user : " + userId + "entered");
  }

  @OnMessage
  public void chatMessage(ChatUser chatUser,  @PathParam(value="userId") String userId) {
      System.out.println(chatUser.getUserId() + " : " + chatUser.getMessage());
      broadcast(chatUser);
  }

  @OnClose
  public void chatClose(Session session, @PathParam(value="userId") String userId) {
      users.remove(this);
      System.out.println("chat close - user :" + userId + "leaved");
  }

  @OnError
  public void chatError (Session session, Throwable throwable, @PathParam(value="userId") String userId) {
      users.remove(this);
      System.out.println("chat close - user :" + session.getId() + "leaved by coummunication error");
   }

  public void broadcast(ChatUser chatUser) {
      try {
          for (ChatEndPoint user : users) {
              user.session.getBasicRemote().sendObject(chatUser);
          }
      } catch (Exception e) {
          System.out.println("failed to broadcast");
      }
  }
}

CopyOnWriteArraySet<>는 읽기 작업이 추가나 수정보다 빈번하고, 순회 중 스레드 간 간섭을 방지해야하는 응용 프로그램에 가장 적합

클라이언트

<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>WebSocket Chat Application in Java</title>
</head>
<body>
<script type="text/javascript">
var uri = "ws://localhost:80/chat/";
var websocket;
var output;
var cotent

var userId = prompt("what's your name?");

var sendMessageObj = {};

function init() {
  output = document.getElementById("output");
  cotent = document.getElementById("cotent");
  connect()
}

function connect() {
  if (!websocket) {
    websocket = new WebSocket(uri + userId);
    websocket.onopen = function (evt) {
      writeToScreen(userId + "님 반갑습니다. 채팅을 시작겠습니다.")
    };
    websocket.onmessage = function (evt) {
      var chatUser = JSON.parse(evt.data)
      writeToScreen(chatUser.userId + " : " + chatUser.message);

    };
    websocket.onerror = function (evt) {
      onError(evt)
    };
  }
}

function disconnect() {
  if (!websocket) websocket.close();
  writeToScreen(userId + "님이 퇴장하셨습니다.")
}

function send_message() {
  var message = cotent.value;
  sendMessageObj.userId = userId;
  sendMessageObj.message = message;
  websocket.send(JSON.stringify(sendMessageObj));
}


function onError(evt) {
  writeToScreen('ERROR: ' + evt.data);
}

function writeToScreen(message) {
  var pre = document.createElement("p");
  pre.style.wordWrap = "break-word";
  pre.innerHTML = message;
  output.appendChild(pre);
}
window.addEventListener("load", init, false);
</script>
<h1 style="text-align: center;">WebSocket Chat Program</h1><br>
<div style="text-align: center;">
<form action="">
  <input id="cotent" name="message" type="text" placeholder ="채팅을 시작해보세요!"><br>
  <input onclick="send_message()" value="Send" type="button">
  <input onclick="disconnect()" value="exit" type="button">
</form>
</div>
<div id="output"></div>
</body>
</html>

0개의 댓글