[Java] 웹 소켓(WebSocket) - broadcast(session 다루기)


Development note/Java  2015.02.15 23:51

안녕하세요. 명월입니다.


이번 포스트는 저번 포스트에 이어서 Websocket에 대해 공부하는데 저번에는 1:1 통신이면 이번에는 1:n 통신에 대해서 알아보겠습니다.


링크 - [Java / 자바] 웹 소켓 (WebSocket)

저번 포스트에서는 단순히 Websocket을 연결, 닫기, 메시지 보내기, 에러처리에 대한 함수를 사용하였는데 이번 포스트는 socket연결의 세션에 대한 공부가 중점이겠습니다.

웹 브라우저와 웹 서버 간에 로그인 등의 정보를 유지하기 위해선 세션이라는 형태의 웹 자원이 있습니다. 이 세션 안에는 서블릿에서 setSession이란 함수로 여러 형태의 정보를 담을 수 있는데 웹 소켓에도 그런 것이 존재합니다. 그런데 주의할 점은 우리가 웹 서버를 통해서 브라우저랑 통신하지만 서블릿 세션과 웹 소켓 세션은 다릅니다.

이 점이 웹 소켓의 최대의 난점이기도 합니다. ajax의 경우는 같은 http프로토콜을 타기 때문에 같은 세션을 사용하지만 웹 소켓을 그러지를 못하기 때문에 로그인하였다 하더라고 웹 소켓은 따로 로그인(?)을 해야 하는 처리가 필요합니다. 이 부분에 대해서는 다음에 중점적으로 공부하고 이번에는 단순히 웹 세션을 이용해 브로드캐스트하는 방법에 대해서 알아보겠습니다.

import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonWriter;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;


@ServerEndpoint("/broadsocket")
public class broadsocket {
  //유저 집합 리스트
  static List<Session> sessionUsers = Collections.synchronizedList(new ArrayList<>());
  
  /**
   * 웹 소켓이 접속되면 유저리스트에 세션을 넣는다.
   * @param userSession 웹 소켓 세션
   */
  @OnOpen
  public void handleOpen(Session userSession){
    sessionUsers.add(userSession);
  }
  /**
   * 웹 소켓으로부터 메시지가 오면 호출한다.
   * @param message 메시지
   * @param userSession
   * @throws IOException
   */
  @OnMessage
  public void handleMessage(String message,Session userSession) throws IOException{
    String username = (String)userSession.getUserProperties().get("username");
    //세션 프로퍼티에 username이 없으면 username을 선언하고 해당 세션을으로 메시지를 보낸다.(json 형식이다.)
    //최초 메시지는 username설정
    if(username == null){
      userSession.getUserProperties().put("username", message);
      userSession.getBasicRemote().sendText(buildJsonData("System", "you are now connected as " + message));
      return;
    }
    //username이 있으면 전체에게 메시지를 보낸다.
    Iterator<Session> iterator = sessionUsers.iterator();
    while(iterator.hasNext()){
      iterator.next().getBasicRemote().sendText(buildJsonData(username,message));
    }
  }
  /**
   * 웹소켓을 닫으면 해당 유저를 유저리스트에서 뺀다.
   * @param userSession
   */
  @OnClose
  public void handleClose(Session userSession){
    sessionUsers.remove(userSession);
  }
  /**
   * json타입의 메시지 만들기
   * @param username
   * @param message
   * @return
   */
  public String buildJsonData(String username,String message){
    JsonObject jsonObject = Json.createObjectBuilder().add("message", username+" : "+message).build();
    StringWriter stringwriter =  new StringWriter();
    try(JsonWriter jsonWriter = Json.createWriter(stringwriter)){
        jsonWriter.write(jsonObject);
    };
    return stringwriter.toString();
  }
}

웹 소켓은 기본적으로 저번 포스트같이 OnOpen, OnClose, OnMessage는 같습니다. 다른 부분을 찾으라고 하면 OnOpen부분에서 session을 받는 부분이 있습니다. 이 세션을 받아서 맴버 변수인 List에 담았습니다.


그리고 메시지를 받으면 List에 담긴 세션들을 불러와서 루프로 sendText 하는 형태입니다.


클라이언트 소스입니다.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
  <!-- 메시지 표시 영역 -->
  <textarea id="messageTextArea" readonly="readonly" rows="10" cols="45"></textarea><br />
  <!-- 송신 메시지 텍스트박스 -->
  <input type="text" id="messageText" size="50" />
  <!-- 송신 버튼 -->
  <input type="button" value="Send" onclick="sendMessage()" />
  <script type="text/javascript">
    //웹소켓 초기화
    var webSocket = new WebSocket("ws://localhost:8080/WebSocketEx/broadsocket");
    var messageTextArea = document.getElementById("messageTextArea");
    //메시지가 오면 messageTextArea요소에 메시지를 추가한다.
    webSocket.onmessage = function processMessge(message){
      //Json 풀기
      var jsonData = JSON.parse(message.data);
      if(jsonData.message != null) {
        messageTextArea.value += jsonData.message + "\n"
      };
    }
    //메시지 보내기
    function sendMessage(){
      var messageText = document.getElementById("messageText");
      webSocket.send(messageText.value);
      messageText.value = "";
    }
  </script>
</body>
</html>

클라이언트 소스는 저번 포스트에서 작성한 부분과 크게 다른 부분은 없습니다. (여기서 필요 없는 onopen, onclose, onerror은 생략했습니다.)

WebSocketEx.zip



댓글 7개가 달렸습니다.
댓글쓰기
  1. 다율파파
    2015.04.03 10:46 |  수정/삭제  댓글쓰기

    명월님... 여기 보물 창고네요^^
    앞으로 자주 들리겠습니다.

    소중한 지식 나눔을 감사드립니다.

  2. TSM
    2015.06.03 19:20 |  수정/삭제  댓글쓰기

    안녕하세요 완전 초보 입니다.ㅠ 이클립스 톰캣을 연동하고 다이나믹 웹 프로젝트로 생성하여 이 소스를 이용하는게 맞나요?
    그리고 맞다면 저 소스들은 프로젝트 어디에 넣어야 실행이 되는건지 알 수 있을까요?
    제가 해본것은 다이나믹웹프로젝트로 생성하여 JSP파일은 WebContent에 넣고 java파일은 src에 넣어 구동하였는데 HTTP Status 404 - The requested resource is not available. 에러가 나서요.
    클라이언트 따로 서버 따로 Run 해서 하는 개념같은데 Server쪽 소스는 어디다가 어떻게 넣어야할지 몰라서 질문 올려봅니다 감사합니다.ㅠ

    • 明月 v명월v
      2015.06.17 01:03 신고 |  수정/삭제

      안녕하세요. 블로그 방문감사합니다.
      java는 소스폴더에 jsp는 webcontents 폴더에 넣으시면 됩니다.
      이 소스는 단순히 서블릿으로 기동된 것이라 기본 다이나믹 프로젝트에 복사하셔도 무방합니다.

  3. 소윤파파
    2016.10.15 16:54 |  수정/삭제  댓글쓰기

    좋은 정보 감사합니다.

    session Id를 변경할 수 있나요?
    소켓 커넥션이 생성되었을 때는 onOpen 함수가 호출되면서 이미 session의 정보가 다 들어와있더라구요.
    세션 맺기전에 id를 지정할 순 없는지, 혹은 이후에 수정할 수는 없는걸까요?(Session 클래스에서는 setId는 없어서........)

  4. 정광
    2016.12.18 20:40 |  수정/삭제  댓글쓰기

    클라이언트와 웹소켓서버 포트가 다르면 안되나요?? 되게하는방법은없나요?

  5. 이승재
    2017.03.27 19:31 |  수정/삭제  댓글쓰기

    혹시...웹 소켓을 사용할 때, Web 서버
    @onMessage의 어노테이션 메소드로 파라미터를 전달하려하면 어떻게 해야하나요?ㅜㅜ