본문 바로가기

Spring 4/WebSocket Multiuser chat

Spring 4 Multiuser Chat

Spring 4 WebSocket을 이용한 다중 이용자 채팅 예


채팅방에 접속한 이용자 중에서 특정 이용자를 선택하여 메시지를 전송할 수 있고 다른 이용자가 접속할 때마다 실시간에 채팅 중인 이용자의 화면에 이용자 리스트가 갱신된다

JSON 포맷으로 통신하고 서버측에서는 JSON문자열의 생성 및 파싱을 위해 JSON-Simple 라이브러리를 사용했다

이 코드는 완성된 채팅 시스템과는 거리가 멀고 스프링 4에서 제공하는 웹소켓 라이브러리를 단순하게 테스트하는 용도로 작성한 것이다


서블릿 설정파일의 웹소켓 관련 내용

  	 <beans:bean id="chatHandler" class="org.kdea.websocket.ChatHandler"/>
  	    
    <websocket:handlers>
        <websocket:mapping handler="chatHandler" path="/chat-ws" />
        <websocket:handshake-interceptors>
	       <beans:bean class="org.kdea.interceptor.WebsocketHandshakeInterceptor"/>
		</websocket:handshake-interceptors>
    </websocket:handlers>



아래는 콘트롤러 클래스인데, 웹소켓을 사용할 때 스프링의 콘트롤러 클래스가 꼭 필요한 것은 아니지만 인터셉터를 사용하여 채팅 폼에 접속하는 이용자의 로그인 여부를 검사하려면 필요하다


ChatController.java

package org.kdea.java;

import java.util.*;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@Controller
@RequestMapping(value="/chat")
public class ChatController 
{
	@RequestMapping(value="/form", method=RequestMethod.GET)
	public String form(){
		return "/ws/chatform";
	}

}



WEB-INF/views/ws/chatform.jsp

JSON문자열을 이용하여 통신하며 서버로부터 메시지와 접속자 리스트를 수신하여 화면에 출력한다

<%@page import="java.util.List"%>
<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Websocket Client</title>
<script type="text/javascript" src="<c:url value="/resources/jquery-2.2.2.min.js"/>"></script>
<script type="text/javascript">
function connect() {
    var ws = new WebSocket("ws://192.168.8.32:8088/SpringWeb/chat-ws");
 
    ws.onopen = function () {
        $('#chatStatus').text('Info: connection opened.');
 
        $('input[name=chatInput]').on('keydown', function(evt){
            if(evt.keyCode==13){
            	var userList = [];
            	$('.receiver:checked').each(function(idx){
            		userList[idx] = $(this).val();
            	});

                var msg = $('input[name=chatInput]').val();
                var obj = {};
                obj.receiver = userList;
                obj.msg = msg;
                var str = JSON.stringify(obj);

                ws.send(str);
                $('input[name=chatInput]').val('');
            }
        });
    	alert('웹소켓 서버에 연결되었습니다');
    };
    ws.onmessage = function (event) {
    	var obj = eval('('+event.data+')');
    	if('userList' in obj) {
    		chatList(obj.userList);
    	}else{
    		$('textarea').eq(0).prepend(obj.msg+'\n');
    	}
    };
    ws.onclose = function (event) {
        $('#chatStatus').text('Info: connection closed.');
    };
};

function chatList(userList) {
	$('#userList').empty();
	for(var i=0;i<userList.length;i++) {
		$('#userList').append(userList[i]);
		var cb = $("<input type='checkbox' class='receiver' value='"+userList[i]+"' >");
		$('#userList').append(cb).append('<br>');
	}
}
</script>
</head>
<body>
<p>
<button type="button"  onclick="connect();">서버 접속</button>
<div id='chatStatus'></div>
<textarea name="chatMsg" rows="5" cols="40"></textarea>
<p>
메시지 입력 : <input type="text" name="chatInput">
 <div id="userList">
 </div>
</body>
</html>


ChatHandler.java

지정된 이용자에게만 메시지를 전달하고 새로 접속한 이용자가 있을 경우에는 모든 접속자에게 접속자 리스트를 새로 전달한다. 모든 통신에 JSON문자열을 이용한다

package org.kdea.websocket;

import java.io.IOException;
import java.util.*;

import org.json.simple.*;
import org.json.simple.parser.*;
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.TextWebSocketHandler;

public class ChatHandler extends TextWebSocketHandler 
{
	static List<WebSocketSession> sessions = new ArrayList<>();
	
	public ChatHandler(){
		System.out.println("웹소켓 핸들러 생성됨");
	}
	
	@Override
	public void afterConnectionEstablished(WebSocketSession session) throws Exception {
		System.out.println("afterConnectionEstablished()");
		sessions.add(session);
		informNewUser();
	}

	@Override
	protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
		System.out.println("handleTextMessage()");
		String sender = (String) session.getAttributes().get("userId");
		String content = message.getPayload();
		JSONParser jp = new JSONParser();
		JSONObject jo = (JSONObject) jp.parse(content);
		String msg = (String)jo.get("msg");
		msg = sender+":"+msg;
		JSONObject jsObj = new JSONObject();
		jsObj.put("msg", msg);
		
		JSONArray ja = (JSONArray)jo.get("receiver");
		
		for(int i=0;i<sessions.size();i++) {
			String uid = (String) sessions.get(i).getAttributes().get("userId");
			for(int k=0;k<ja.size();k++) {
				String recvId = (String) ja.get(k);
				if(recvId.equals(uid)) {
					sessions.get(i).sendMessage(new TextMessage(jsObj.toJSONString()));
				}
			}
		}
	}
	
	private void informNewUser() {
		JSONArray ja = new JSONArray();
		for(int i=0;i<sessions.size();i++) {
			String uid = (String) sessions.get(i).getAttributes().get("userId");
			ja.add(uid);
		}
		JSONObject jo = new JSONObject();
		jo.put("userList", ja);
		String jsonStr = jo.toJSONString();
		try{
			for(int i=0;i<sessions.size();i++) {
					sessions.get(i).sendMessage(new TextMessage(jsonStr));
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	@Override
	public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
		System.out.println("afterConnectionClosed()");
		sessions.remove(session);
	}

	@Override
	public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
		System.out.println("handleTransportError()");
	}
	
}



WebSocketHandshakeInterceptor.java

package org.kdea.interceptor;

import java.util.*;

import javax.servlet.http.*;
 
import org.springframework.http.server.*;
import org.springframework.web.socket.*;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
 
public class WebsocketHandshakeInterceptor extends HttpSessionHandshakeInterceptor
{
    @Override
    public boolean beforeHandshake(ServerHttpRequest request,
            ServerHttpResponse response, WebSocketHandler wsHandler,
            Map<String, Object> attributes) throws Exception 
    {
        System.out.println("Before Handshake");
        ServletServerHttpRequest ssreq = (ServletServerHttpRequest) request;
        HttpServletRequest req =  ssreq.getServletRequest();
        String id = (String)req.getSession().getAttribute("id");
        attributes.put("userId", id);

        return super.beforeHandshake(request, response, wsHandler, attributes);
    }
 
    @Override
    public void afterHandshake(ServerHttpRequest request,
            ServerHttpResponse response, WebSocketHandler wsHandler,
            Exception ex) 
    {
        System.out.println("After Handshake");
        super.afterHandshake(request, response, wsHandler, ex);
    }
 
}