[스프링] 웹채팅 구현(웹소켓)

손성우·2021년 11월 22일
3

스프링

목록 보기
4/9

WEB Socket

  • 웹 소켓 프로토콜인 RFC 6445는 Client와 Server사이에 전이중 방향 통신(Full Duplex)을 제공한다.
  • Spring 4.0에서 등장한 네트워크 서비스이다. 기존에 채팅을 구현하려면 일반적인 Java Socket을 사용해야 했다. Java Socket으로 소켓 통신의 과정을 일일이 구현해야 했다. HTTP 통신은 기본적으로 비연결성(Connectless) 통신이므로, Client에게 한 번 보내고 나면 연결이 끊겨 지속적으로 데이터를 주고 받을 수 없다. (임시 방편으로 Ajax를 사용한 비동기적 통신을 통해 주기적으로 한 페이지 안에서 Server한테 자신에게 보낼 정보가 있는지 요청(Request)하거나, 페이지가 이동될 때 마다 자신에게 온 정보가 있는지에 대한 질문을 요청에 포함할 수 있었다.)

+ TCP

TCP는 이진(Binary)데이터만 주고 받을 수 있으나, 웹 소켓은 Binary와 Text 데이터도 주고 받을 수 있다.

+ HTTP

  • 웹 소켓은 HTTP 호환이 가능하게 설계되었고, HTTP 요청으로 시작하나 두 Protocol의 아키텍쳐와 Application Programming Model은 매우 다르다.
  • HTTP와 REST에서 Application은 여러 URL(Uniform Resource Location)로 모델링 된다. 반면에 웹 소켓은 일반적으로 초기 연결을 위한 URL이 한 개만 존재한다. 결과적으로 모든 Application 내 메세지는 동일한 TCP 연결을 통해 흐른다. 이는 완전히 다른 '비동기식 이벤트' 중심의 메세지 전달 아키텍쳐를 나타낸다.
  • 웹 소켓은 HTTP와 달리 메세지 내용에 의미를 규정하지 않는 저수준 전송 프로토콜이다. 즉, Client와 Server가 메세지 시멘틱에 동의하지 않으면 메세지를 라우팅하거나 처리할 수 없다.

특징

  1. HTTP 통신의 단점 개선
  2. 영구적 양방향 통신
  3. HTML 5의 주요 API
    4 HTTP 프로토콜을 기반으로 하는 웹 브라우저의 웹 서버간 양방향 통신을 지원하기 위한 표준
  4. Client/Server가 실시간으로 데이터를 주고 받을 수 있다.

출처 : https://dev-gorany.tistory.com/3

채팅창 구현

로그인한 사용자끼리 동시에 여러명 채팅, 귓속말을 구현해보겠다.
MemberVO는 사용자 객체이므로 필요에 따라 커스텀하면 될 것이다.
1. pom.xml
웹 소켓의 데이터 통신은 JSON을 사용하기 때문에 JSON 라이브러리를 추가해야 한다.
(저는 편의상 GSON[구글에서 제공하는 JSON 라이브러리] 사용했습니다.)

<dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-websocket</artifactId>
           <version>${org.springframework-version}</version>
</dependency>
		
<dependency>
	<groupId>com.google.code.gson</groupId>
	<artifactId>gson</artifactId>
	<version>2.8.5</version>
</dependency>
<!-- GSON 말고 아래 JSON라이브러리 사용해도 상관x-->
<dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.9.6</version>
</dependency>
  1. web.xml
    servlet-mapping 클래스를 따로 등록해준다.
<servlet>
		<servlet-name>appServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<!-- 웹소켓관련 설정파일 추가-->        		 
			<param-value>/WEB-INF/spring/appServlet/servlet-context.xml
						 /WEB-INF/spring/config/websocketContext.xml
			</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
  1. websocketContext.xml
    web.xml에서 설정한 파일경로에 websocketContext.xml을 생성하고 아래 코드를 작성한다
    또는 namepsaces에서 beans, mvc, context, websocket를 체크해주고 bean을 등록한다.
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:beans="http://www.springframework.org/schema/beans"
   xmlns:context="http://www.springframework.org/schema/context"
   xmlns:websocket="http://www.springframework.org/schema/websocket"
   xsi:schemaLocation="http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd
      http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    
    <beans:bean id="echoHandler" class="com.spring.chatting.WebsocketEchoHandler" init-method="init" />
    <websocket:handlers>
        <websocket:mapping path="/chatting/multichatstart.action" handler="echoHandler" />
        
        <!-- websocket:handlers 태그안에서 아래처럼 websocket:handshake-interceptors에
             HttpSessionHandshakeInterceptor를 추가해주면 WebSocketHandler에 접근하기 전에 먼저 HttpSession에 접근하여 HttpSession에 저장된 값을 읽어 들여 WebSocketHandler에서 사용할 수 있도록 처리해줌. -->
        <websocket:handshake-interceptors>
            <beans:bean class="org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor" />
       </websocket:handshake-interceptors>
    </websocket:handlers>
    
</beans:beans>
  1. WebsocketEchoHandler.java
    websocketContext.xml에서 bean에 등록한 패키지 경로에 따라 핸들러 클래스를 작성한다.
package com.spring.chatting;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import com.spring.board.model.MemberVO;

public class WebsocketEchoHandler extends TextWebSocketHandler{

	// === 웹소켓서버에 연결한 클라이언트 사용자들을 저장하는 리스트 === //
	private List<WebSocketSession> connectedUsers =  new ArrayList<>();
	
	// init-method
	public void init() throws Exception{
						
	}
	
	// === 클라이언트가 웹소켓서버에 연결했을때의 작업 처리하기 ===
    /*
       afterConnectionEstablished(WebSocketSession wsession) 메소드는 
              클라이언트가 웹소켓서버에 연결이 되어지면 자동으로 실행되는 메소드로서
       WebSocket 연결이 열리고 사용이 준비될 때 호출되어지는(실행되어지는) 메소드이다.
    */
	@Override
	public void afterConnectionEstablished(WebSocketSession wsession) throws Exception{
		// 웹소켓서버에 접속한 클라이언트의 IP Address 얻어오기
        /*
        
          Run --> Run Configuration 
              --> Arguments 탭
              --> VM arguments 속에 맨 뒤에
              --> 한칸 띄우고 -Djava.net.preferIPv4Stack=true 
                          을 추가한다.  
        */
		
       // 접속자 명단
		String connectingUserName = "「";
		
		for(WebSocketSession webSocketSession : connectedUsers) {
			Map<String, Object> map = webSocketSession.getAttributes();						
			MemberVO loginuser = (MemberVO) map.get("loginuser");
			// "loginuser" 은 HttpSession에 저장된 키 값으로 로그인 되어진 사용자이다.
			
			connectingUserName += loginuser.getName()+" "; 
			
		}// end of for--------------------
		
		connectingUserName += "」";
				
		for(WebSocketSession webSocketSession : connectedUsers) {
			webSocketSession.sendMessage(new TextMessage(connectingUserName)); 
		}
	}
	
	// === 클라이언트가 웹소켓 서버로 메시지를 보냈을때의 Send 이벤트를 처리하기 ===
    /*
       handleTextMessage(WebSocketSession wsession, TextMessage message) 메소드는 
                 클라이언트가 웹소켓서버로 메시지를 전송했을 때 자동으로 호출되는(실행되는) 메소드이다.
                 첫번째 파라미터  WebSocketSession 은  메시지를 보낸 클라이언트임.
               두번째 파라미터  TextMessage 은  메시지의 내용임.
     */
	@Override
    protected void handleTextMessage(WebSocketSession wsession, TextMessage message) 
       throws Exception {
		
		// >>> 파라미터 WebSocketSession wsession은  웹소켓서버에 접속한 클라이언트임. <<<
        // >>> 파라미터 TextMessage message 은  클라이언트 사용자가 웹소켓서버로 보낸 웹소켓 메시지임. <<<
        
        // Spring에서 WebSocket 사용시 먼저 HttpSession에 저장된 값들을 읽어와서 사용하기
        /*
           먼저 /webapp/WEB-INF/spring/config/websocketContext.xml 파일에서
           websocket:handlers 태그안에 websocket:handshake-interceptors에
           HttpSessionHandshakeInterceptor를 추가해주면 
           WebSocketHandler 클래스를 사용하기 전에, 
           먼저 HttpSession에 저장되어진 값들을 읽어 들여, WebSocketHandler 클래스에서 사용할 수 있도록 처리해준다. 
          */ 
		
		Map<String, Object> map = wsession.getAttributes();
		MemberVO loginuser = (MemberVO) map.get("loginuser");
		
		MessageVO messagevo = MessageVO.converMessage(message.getPayload());
		/* 
        파라미터 message 는  클라이언트 사용자가 웹소켓서버로 보낸 웹소켓 메시지임
		message.getPayload() 은 클라이언트 사용자가 보낸 웹소켓 메시지를 String 타입으로 바꾸어주는 것이다.
		/Board/src/main/webapp/WEB-INF/views/tiles1/chatting/multichat.jsp 파일에서 
		 클라이언트가 보내준 메시지는 JSON 형태를 뛴 문자열(String) 이므로 이 문자열을 Gson을 사용하여 MessageVO 형태의 객체로 변환시켜서 가져온다.
		*/
		
		Date now = new Date();
		String currentTime = String.format("%tp %tl:%tM",now,now,now); 
        // %tp              오전, 오후를 출력
        // %tl              시간을 1~12 으로 출력
        // %tM              분을 00~59 으로 출력
		
		for(WebSocketSession webSocketSession : connectedUsers) {
			if("all".equals(messagevo.getType())) {
				// 나를 제외한 모두에게 보내기
				if(!wsession.getId().equals(webSocketSession.getId()) ){
					// wsession 은 메시지를 보낸 클라이언트임.
                    // webSocketSession 은 웹소켓서버에 연결된 모든 클라이언트중 하나임.
                    // wsession.getId() 와  webSocketSession.getId() 는 자동증가되는 고유한 숫자로 나옴 
					webSocketSession.sendMessage(
							new TextMessage("<span>"+ wsession.getRemoteAddress().getAddress().getHostAddress() +
									"</span>&nbsp;[<span style='font-weight:bold; cursor:pointer;' class='loginuserName'>"+ loginuser.getName() +
									"</span>]<br><div style='background-color: white; display: inline-block; max-width: 60%; padding: 7px; border-radius: 15%; word-break: break-all;'>" + 
									messagevo.getMessage() + "</div> <div style='display: inline-block; padding: 20px 0 0 5px; font-size: 7pt;'>"+currentTime+"</div> <div>&nbsp;</div>"));
				} 
			} else { 
				// 채팅할 대상이 "전체"가 아닌 특정대상(지금은 귓속말대상 IP address 임) 일 경우
				String hostAddress = webSocketSession.getRemoteAddress().getAddress().getHostAddress(); 
                // webSocketSession 은 웹소켓서버에 연결한 모든 클라이언트중 하나이며, 그 클라이언트의 IP address를 알아오는 것임.  
			      
			     if (messagevo.getTo().equals(hostAddress)) { 
			         // messageVO.getTo() 는 클라이언트가 보내온 귓속말대상 IP address 임.
			          webSocketSession.sendMessage(
			                  new TextMessage("<span> 귓속말"+ wsession.getRemoteAddress().getAddress().getHostAddress() +"</span>&nbsp;[<span style='font-weight:bold; cursor:pointer;' class='loginuserName'>" +loginuser.getName()+ "</span>]<br><div style='background-color: white; display: inline-block; max-width: 60%; padding: 7px; border-radius: 15%; word-break: break-all; color: red;'>" + messagevo.getMessage() +"</div> <div style='display: inline-block; padding: 20px 0 0 5px; font-size: 7pt;'>"+currentTime+"</div> <div>&nbsp;</div>" ));  
			                                                                                                                                                                                                                                                                                                                                            /* word-break: break-all; 은 공백없이 영어로만 되어질 경우 해당구역을 빠져나가므로 이것을 막기위해서 사용한다. */
			          break; // 지금의 특정대상(지금은 귓속말대상 IP address 임)은 1개이므로 
			                 // 특정대상(지금은 귓속말대상 IP address 임)에게만 메시지를 보내고  break;를 한다.
			      }
				
			}
		}
		
	}
	
	// === 클라이언트가 웹소켓서버와의 연결을 끊을때 작업 처리하기 ===
    /*
    afterConnectionClosed(WebSocketSession session, CloseStatus status) 메소드는 
              클라이언트가 연결을 끊었을 때 
              즉, WebSocket 연결이 닫혔을 때(채팅페이지가 닫히거나 채팅페이지에서 다른 페이지로 이동되는 경우) 자동으로 호출되어지는(실행되어지는) 메소드이다.
    */

	@Override
    public void afterConnectionClosed(WebSocketSession wsession, CloseStatus status) 
       throws Exception {
		
		// 파라미터 WebSocketSession wsession 은 연결을 끊은 웹소켓 클라이언트임.
        // 파라미터 CloseStatus 은 웹소켓 클라이언트의 연결 상태.
		
		Map<String, Object> map = wsession.getAttributes();
		MemberVO loginuser = (MemberVO) map.get("loginuser");
		
		// 웹소켓 서버에 연결되어진 클라이언트 목록에서 연결은 끊은 클라이언트는 삭제시킨다.
		connectedUsers.remove(wsession);
		
		for(WebSocketSession webSocketSession : connectedUsers) {
			
			// 나를 제외한 모두에게 보내기
			if(!wsession.getId().equals(webSocketSession.getId()) ){
				// wsession 은 메시지를 보낸 클라이언트임.
                // webSocketSession 은 웹소켓서버에 연결된 모든 클라이언트중 하나임.
                // wsession.getId() 와  webSocketSession.getId() 는 자동증가되는 고유한 숫자로 나옴 
				webSocketSession.sendMessage(new TextMessage(wsession.getRemoteAddress().getAddress().getHostAddress() +" [<span style='font-weight:bold;'>" +loginuser.getName()+ "</span>]" + "님이 <span style='color: red;'>퇴장</span>했습니다."));
			} 
			
		}
		
		///// ===== 접속을 끊을시 접속자명단을 알려주기 위한 것 시작 ===== /////
        String connectingUserName = "「";
        
        for (WebSocketSession webSocketSession : connectedUsers) {
            Map<String, Object> map2 = webSocketSession.getAttributes();
            MemberVO loginuser2 = (MemberVO)map2.get("loginuser");  
           // "loginuser" 은 HttpSession에 저장된 키 값으로 로그인 되어진 사용자이다.
   
            connectingUserName += loginuser2.getName()+" "; 
        }// end of for------------------------------------------
        
        connectingUserName += "」";
        
        for (WebSocketSession webSocketSession : connectedUsers) {
            webSocketSession.sendMessage(new TextMessage(connectingUserName));
        }// end of for------------------------------------------
        ///// ===== 접속해제시 접속자명단을 알려주기 위한 것 끝 ===== /////
		
	}
	
	
	
	
}
  1. 채팅창 화면 .jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<%-- #174. 웹채팅 jsp --%>

<meta charset="UTF-8">
<title>채팅</title>
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-10 offset-md-1">
   <div id="chatStatus"></div>
   <div class="my-3">
   - 상대방의 대화내용이 검정색으로 보이면 채팅에 참여한 모두에게 보여지는 것입니다.<br>
   - 상대방의 대화내용이 <span style="color: red;">붉은색</span>으로 보이면 나에게만 보여지는 1:1 귓속말 입니다.<br>
   - 1:1 채팅(귓속말)을 하시려면 예를 들어, 채팅시 보이는 172.30.1.45[이순신] ▶ ㅎㅎㅎ 에서 이순신을 클릭하시면 됩니다.
   </div>
   <input type="text" id="to" placeholder="귓속말대상IP주소"/>
   <br/>
   ♡ 귓속말대상 : <span id="privateWho" style="font-weight: bold; color: red;"></span>
   <br>
   <button type="button" id="btnAllDialog" class="btn btn-secondary btn-sm">귀속말대화끊기</button>
   <br><br>
   현재접속자명단:<br/>
   <div id="connectingUserList" style=" max-height: 100px; overFlow: auto;"></div>
   
   <div id="chatMessage" style="max-height: 500px; overFlow: auto; margin: 20px 0;"></div>

   <input type="text"   id="message" class="form-control" placeholder="메시지 내용"/>
   <input type="button" id="btnSendMessage" class="btn btn-success btn-sm my-3" value="메시지보내기" />
   <input type="button" class="btn btn-danger btn-sm my-3 mx-3" onClick="javascript:location.href='<%=request.getContextPath() %>/index.action'" value="채팅방나가기" />
</div>
</div>
</div>
<script>
<!--
// === !!! WebSocket 통신 프로그래밍은 HTML5 표준으로써 자바스크립트로 작성하는 것이다. !!! === //
// WebSocket(웹소켓)은 웹 서버로 소켓을 연결한 후 데이터를 주고 받을 수 있도록 만든 HTML5 표준이다.
// 그런데 이러한 WebSocket(웹소켓)은 HTTP 프로토콜로 소켓 연결을 하기 때문에 웹 브라우저가 이 기능을 지원하지 않으면 사용할 수 없다. 
/*
   >> 소켓(Socket)이란? 
  - 어떤 통신프로그램이 네트워크상에서 데이터를 송수신할 수 있도록 연결해주는 연결점으로써 
    IP Address와 port 번호의 조합으로 이루어진다. 
      또한 어떤 하나의 통신프로그램은 하나의 소켓(Socket)만을 가지는 것이 아니라 
      동일한 프로토콜, 동일한 IP Address, 동일한 port 번호를 가지는 수십개 혹은 수만 개의 소켓(Socket)을 가질 수 있다.

   =================================================================================================  
      클라이언트  소켓(Socket)                           서버  소켓(Socket)
      211.238.142.70:7942 ◎------------------------------------------◎  211.238.142.77:9090
      
           클라이언트는 서버인 211.238.142.77:9090 소켓으로 클라이언트 자신의 정보인 211.238.142.70:7942 을 
           보내어 연결을 시도하여 연결이 이루어지면 서버는 클라이언트의 소켓인 211.238.142.70:7942 으로 데이터를 보내면서 통신이 이루어진다.
    ================================================================================================== 
           
            소켓(Socket)은 데이터를 통신할 수 있도록 해주는 연결점이기 때문에 통신할 두 프로그램(Client, Server) 모두에 소켓이 생성되야 한다.

  Server는 특정 포트와 연결된 소켓(Server 소켓)을 가지고 서버 컴퓨터 상에서 동작하게 되는데, 
  이 Server는 소켓을 통해 Cilent측 소켓의 연결 요청이 있을 때까지 기다리고 있다(Listening 한다 라고도 표현함).
  Client 소켓에서 연결요청을 하면(올바른 port로 들어왔을 때) Server 소켓이 허락을 하여 통신을 할 수 있도록 연결(connection)되는 것이다.
*/
-->
      
      $(function(){
    	  $("#mycontent").css({"background-color":"#cce0ff"});
    	  // 웹브라우저의 주소창의 포트까지 가져오기
    	  var url = window.location.host;
    	  var pathname = window.location.pathname; // '/'부터 오른쪽에 있는 모든 경로
    	  // console.log(url+pathname);
    	  var appCtx = pathname.substring(0, pathname.lastIndexOf("/") );
    	  
    	  var root = url+appCtx;
    	// 192.168.200.104:22222/chatting
    	// 웹소켓통신을 하기위해서는 http:// 을 사용하는 것이 아니라 ws:// 을 사용해야 한다.
    	  var wsUrl = "ws://" + root + "/multichatstart.action";
    	  var websocket = new WebSocket(wsUrl);
    	  
    	// >> ====== !!중요!! Javascript WebSocket 이벤트 정리 ====== << //
    	   /*   -------------------------------------
    	                  이벤트 종류             설명
    	        -------------------------------------
    	           onopen        WebSocket 연결
    	           onmessage     메시지 수신
    	           onerror       전송 에러 발생
    	           onclose       WebSocket 연결 해제
    	    */
    	    
    	var messageObj = {};
    	// 웹소켓에  최초로 연결이 되어졌을 경우에 실행되어지는 콜백함수 정의
		websocket.onopen = function(){
			$("div#chatStatus").text('정보: 웹소켓에 연결이 성공됨');
			messageObj = { message : "채팅방에 <span style='color: red;'>입장</span>했습니다",
						   type : "all",
						   to : "all"
						};
	
            websocket.send(JSON.stringify(messageObj));
			
		};  
		// 메시지 수신지 콜백함수 정의 
		websocket.onmessage = function(event){
			console.log('onmessage');
             // 자음 ㄴ 임                 
			if(event.data.substr(0,1)=="「" && event.data.substr(event.data.length-1)=="」") {
				$("div#connectingUserList").html(event.data);
			}
			else {
				$("div#chatMessage").append(event.data);
				$("div#chatMessage").append("<br>");
				$("div#chatMessage").scrollTop(9999999);
			}
		};
		
		// 메세지 보내기
		var isOnlyOneDialog = false; // 귓속말 대화임을 지정. true 이면 귀속말, false 이면 모두에게 공개되는 말
		$("#btnSendMessage").click(function(){
			
			if($("#message").val() != ""){
				
				// ==== 자바스크립트에서 replace를 replaceAll 처럼 사용하기 ====
                // 자바스크립트에서 replaceAll 은 없다.
                // 정규식을 이용하여 대상 문자열에서 모든 부분을 수정해 줄 수 있다.
                // 수정할 부분의 앞뒤에 슬래시를 하고 뒤에 gi 를 붙이면 replaceAll 과 같은 결과를 볼 수 있다.
                var messageVal = $("input#message").val();
                messageVal = messageVal.replace(/<script/gi, "&lt;script"); 
                // 스크립트 공격을 막으려고 한 것임.
                
                messageObj = { message : messageVal,
			       type : "all",
				to : "all"
				};
                <%-- 
                messageObj = {};
                messageObj.message = messageVal;
                messageObj.type = "all";
                messageObj.to = "all";                
                --%>
                
                var to = $("input#to").val();
                if ( to != "" ) {
                    messageObj.type = "one";
                    messageObj.to = to;
                }
                
                websocket.send(JSON.stringify(messageObj));                
             // 위에서 자신이 보낸 메시지를 웹소켓을 보낸 다음에 자신이 보낸 메시지 내용을 웹페이지에 보여지도록 한다.
             
                var now = new Date();
                var ampm = "오전 ";
                var hours = now.getHours();
                
                if(hours > 12) {
                   hours = hours - 12;
                   ampm = "오후 ";
                }
                
                if(hours == 0) {
                   hours = 12;
                }
                if(hours == 12){
                	ampm = "오후"
                }
                
                var minutes = now.getMinutes();
              if(minutes < 10) {
                 minutes = "0"+minutes;
              }
              
                var currentTime = ampm + hours + ":" + minutes;
                if(isOnlyOneDialog == false){ // 귓속말이 아닌 경우
                	$("#chatMessage").append("<div style='background-color: #ffff80; display: inline-block; max-width: 60%; float: right; padding: 7px; border-radius: 15%; word-break: break-all;'>" + messageVal + "</div> <div style='display: inline-block; float: right; padding: 20px 5px 0 0; font-size: 7pt;'>"+currentTime+"</div> <div style='clear: both;'>&nbsp;</div>");                    
                } else {
                	$("div#chatMessage").append("<div style='background-color: #ffff80; display: inline-block; max-width: 60%; float: right; padding: 7px; border-radius: 15%; word-break: break-all; color:red;'>" + messageVal + "</div> <div style='display: inline-block; float: right; padding: 20px 5px 0 0; font-size: 7pt;'>"+currentTime+"</div> <div style='clear: both;'>&nbsp;</div>");                  
                }
                $("#chatMessage").scrollTop(9999999);
                $("input#message").val("");
                $("input#message").focus();
			  }
		});
		//  메세지 입력후 엔터
		$("#message").keyup((event) => {
			if(event.keyCode == 13){
				$("#btnSendMessage").click();
			}
		});
		
		// 귀속말대화끊기 버튼은 처음에는 보이지 않도록 한다.
        $("button#btnAllDialog").hide();
        $(document).on("click",".loginuserName",function(){
            /* class loginuserName 은 
               com.spring.chatting.websockethandler.WebsocketEchoHandler 의 
               protected void handleTextMessage(WebSocketSession wsession, TextMessage message) 메소드내에
               178번 라인에 기재해두었음.
            */
            var ip = $(this).prev().text();
         //   alert(ip);
             $("input#to").val(ip); 
             
             $("span#privateWho").text($(this).text());
             $("button#btnAllDialog").show();
             
             isOnlyOneDialog = true; // 귀속말 대화임을 지정.
         });
		
     	// 귀속말대화끊기 버튼을 클릭한 경우는 전체대상으로 채팅하겠다는 말이다. 
        $("button#btnAllDialog").click(function(){
              $("input#to").val("");
              $("span#privateWho").text("");
              $(this).hide();
              
              isOnlyOneDialog = false; // 귀속말 대화가 아닌 모두에게 공개되는 대화임을 지정.
        });
			
      });
</script>
</body>
</html>
profile
백엔드 개발자를 꿈꾸며 공부한 내용을 기록하고 있습니다.

20개의 댓글

comment-user-thumbnail
2024년 10월 16일

The on-line world might be bogged downwards with the help of counterfeit web logs without a proper personal message nonetheless put up was basically awesome not to mention value typically the read through. Regards for the purpose of showing this unique when camping. Sanitär Rheinfelden

답글 달기
comment-user-thumbnail
2024년 10월 21일

Hey, I am so thrilled I found your blog, I am here now and could just like to say thank for a tremendous post and all round interesting website. Please do keep up the great work. I cannot be without visiting your blog again and again. kinggame

답글 달기
comment-user-thumbnail
2024년 10월 21일

Your blog provided us with valuable information to work with. Each & every tips of your post are awesome. Thanks a lot for sharing. Keep blogging..메이플 대리

답글 달기
comment-user-thumbnail
2024년 10월 23일

Positive site, where did u come up with the information on this posting? I'm pleased I discovered it though, ill be checking back soon to find out what additional posts you include. 4rabet casino

답글 달기
comment-user-thumbnail
2024년 10월 23일

Your blog provided us with valuable information to work with. Each & every tips of your post are awesome. Thanks a lot for sharing. Keep blogging.. 구매대행

답글 달기
comment-user-thumbnail
2024년 10월 24일

Very informative post! There is a lot of information here that can help any business get started with a successful social networking campaign. Click here

답글 달기
comment-user-thumbnail
2024년 10월 27일

I’ve been searching for some decent stuff on the subject and haven't had any luck up until this point, You just got a new biggest fan!.. 삼척 룸

답글 달기
comment-user-thumbnail
2024년 10월 27일

I’ve been surfing online more than 5 hours today, yet I never found any interesting article like yours without a doubt. It’s pretty worth enough for me. Thanks... 해외 축구 무료 보기

답글 달기
comment-user-thumbnail
2024년 10월 28일

Hey There. I found your blog using msn. This is a very well written article. I’ll be sure to bookmark it and come back to read more of your useful info. Thanks for the post. I’ll definitely return. łóżka piętrowe

답글 달기
comment-user-thumbnail
2024년 10월 29일

I found that site very usefull and this survey is very cirious, I ' ve never seen a blog that demand a survey for this actions, very curious... code promo 1xbet bénin

답글 달기
comment-user-thumbnail
2024년 10월 30일

I think this is an informative post and it is very useful and knowledgeable. therefore, I would like to thank you for the efforts you have made in writing this article.발로란트 대리

답글 달기
comment-user-thumbnail
5일 전

I visit your blog regularly and recommend it to all of those who wanted to enhance their knowledge with ease. The style of writing is excellent and also the content is top-notch. Thanks for that shrewdness you provide the readers! แทงบอลออนไลน์

답글 달기
comment-user-thumbnail
3일 전

Use 1xbet promo code: 1XBIG777 to benefit from a very interesting welcome bonus in 2024. You can get a 200% bonus up to $130 on sports and up to $1,500 and 150 free spins on the casino. The code is to be used when you register, it can be used no matter which country in Africa you live in, so take advantage of it. 1xbet egypt

답글 달기
comment-user-thumbnail
2일 전

When you use a genuine service, you will be able to provide instructions, share materials and choose the formatting style. バーチャルオフィス 渋谷 格安

답글 달기
comment-user-thumbnail
2일 전

Wow i can say that this is another great article as expected of this blog.Bookmarked this site.. وان ایکس بت

답글 달기
comment-user-thumbnail
2일 전

Thanks, that was a really cool read! وان ایکس بت

답글 달기
comment-user-thumbnail
2일 전

I found so many interesting stuff in your blog especially its discussion. From the tons of comments on your articles, I guess I am not the only one having all the enjoyment here! keep up the good work... 1xbet giris

답글 달기
comment-user-thumbnail
2일 전

This is my first time i visit here. I found so many interesting stuff in your blog especially its discussion. From the tons of comments on your articles, I guess I am not the only one having all the enjoyment here keep up the good work سایت بت فوروارد

답글 달기
comment-user-thumbnail
2일 전

I found so many interesting stuff in your blog especially its discussion. From the tons of comments on your articles, I guess I am not the only one having all the enjoyment here! keep up the good work... سایت بت فوروارد

답글 달기
comment-user-thumbnail
2일 전

I found so many interesting stuff in your blog especially its discussion. From the tons of comments on your articles, I guess I am not the only one having all the enjoyment here! keep up the good work... سایت یاس بت

답글 달기