websocket으로 채팅방 만드는 방법

 

채팅방 번호로 방을 구분하고

일반적인 문자 채팅을 구현한다.

 

 

pom.xml 에 아래 라이브러리를 추가

1
2
3
4
5
6
<dependency>
              <groupId>javax.websocket</groupId>
              <artifactId>javax.websocket-api</artifactId>
              <version>1.1</version>
</dependency>
 
cs

 

 

컨트롤러단

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Array;
import java.net.InetAddress;
import java.net.Socket;
import java.net.URLEncoder;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
 
 
 
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import org.springframework.stereotype.Controller;
 
 
 
 
@Controller
@ServerEndpoint("/lms/chat")
public class lmsChatController extends Socket {
    private static final List<Session> session = new ArrayList<Session>();
    private static final HashMap<StringString[]> chatIdMap = new HashMap<StringString[]>(); // 접속자의 정보를 저장
    
    @OnOpen
    public void open(Session newUser) {
        System.out.println("connected");
        
        session.add(newUser);
        System.out.println("현재 접속중인 유저 수 : " + session.size() + " : "+newUser.getId());
        
        
    }
 
    @OnMessage
    public void getMsg(Session recieveSession, String msg) {
        System.out.println("getMsg : " + msg +" : "+recieveSession.getId());
        String[] arrChatIdMap;
        
        // 0 명령
        // 1 이름
        // 2 학과명
        // 3 학과코드
        String[] arrMsg = msg.split(":,:");
        
        System.out.println("정보 : " + arrMsg[0+" : "+ arrMsg.length);
        switch(arrMsg[0]) {
        
        case "CONNECT":
            
            chatIdMap.put(recieveSession.getId(), arrMsg);
            arrChatIdMap = chatIdMap.get(recieveSession.getId());
            System.out.println("첫 접속정보 : " + session.size() + " : "+chatIdMap.size() + " : "+recieveSession.getId());
            connectlog(chatIdMap);
             String sConnUserName ="";
              for (Entry<StringString[]> entrySet : chatIdMap.entrySet()) {
                 String[] arrChatIdInfo= entrySet.getValue();
                 if(arrChatIdInfo[3].equals(arrChatIdMap[3])) {
                     sConnUserName+=arrChatIdInfo[1]+"+,+";
                 }
                 //System.out.println("AA : "+entrySet.getKey() + " : " + sConnUserName);
             }
             sConnUserName=sConnUserName.substring(0, sConnUserName.length()-3);
            
             for (int i = 0; i < session.size(); i++) {
                  try {
                            if(session.get(i).isOpen()) {
                                System.out.println("접속자 정보 : " + i);
                                System.out.println("CON:,:"+sConnUserName+":,:"+arrChatIdMap[1]+"님이 접속하였습니다"+":,:"+arrChatIdMap[3]);
                                session.get(i).getBasicRemote().sendText("CON:,:"+sConnUserName+":,:"+arrChatIdMap[1]+"님이 접속하였습니다"+":,:"+arrChatIdMap[1]+":,:"+arrChatIdMap[2]+":,:"+arrChatIdMap[3]);
                            }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            
            break;
            
        case "MESSAGE":
        default:
             arrChatIdMap = chatIdMap.get(recieveSession.getId());
             for (int i = 0; i < session.size(); i++) {
                    if (! recieveSession.getId().equals(session.get(i).getId())) {
                        try {
                            if(session.get(i).isOpen()) {
                                session.get(i).getBasicRemote().sendText("U:,:"+arrChatIdMap[1]+":,:"+ msg+":,:"+arrChatIdMap[1]+":,:"+arrChatIdMap[2]+":,:"+arrChatIdMap[3] ); // 너가 보낸거
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    } else {
                        try {
                            if(session.get(i).isOpen()) {
                                session.get(i).getBasicRemote().sendText("I:,:"+arrChatIdMap[1]+":,:"+ msg+":,:"+arrChatIdMap[1]+":,:"+arrChatIdMap[2]+":,:"+arrChatIdMap[3] ); // 내가 보낸거
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            break;
        }     
       
    }
    
    @OnClose
    public void close(Session recieveSession) {
        int sessionSize = session.size();
        if (sessionSize != 0) {
            try {
                 for (int i = 0; i < sessionSize; i++) {
                    // System.out.println("get : "+i+":"+recieveSession.getId() + " : "+session.size());
                     if (session.get(i).isOpen()) {
                         String[] arrChatIdMap = chatIdMap.get(recieveSession.getId());
                         //0 구분
                         //1 참여자 정보
                         //2 메시지
                         //3 학과번호
                         //4 학과명
                          String sConnUserName ="";
                          for (Entry<StringString[]> entrySet : chatIdMap.entrySet()) {
                             String[] arrChatIdInfo= entrySet.getValue();
                             System.out.println("AA : "+arrChatIdInfo[3+ " : " + arrChatIdMap[3]);
                             if(arrChatIdInfo[3].equals(arrChatIdMap[3])) {
                                     sConnUserName+=arrChatIdInfo[1]+"+,+";                                
                             }
                         }
                         sConnUserName=sConnUserName.substring(0, sConnUserName.length()-3);
                         
                         
                         System.out.println("DCON:,:"+sConnUserName+":,:"+arrChatIdMap[1]+"님이 접속을 종료하였습니다"+":,:"+arrChatIdMap[3]+":,:"+arrChatIdMap[1]);
                         session.get(i).getBasicRemote().sendText("DCON:,:"+sConnUserName+":,:"+arrChatIdMap[1]+"님이 접속을 종료하였습니다"+":,:"+arrChatIdMap[1]+":,:"+arrChatIdMap[2]+":,:"+arrChatIdMap[3]);
                     }
                 }
                List<Integer> nRemoveNum=new ArrayList<Integer>();
                for (int i = 0; i < sessionSize; i++) {
                    System.out.println("close : "+i+":"+recieveSession.getId() + " : "+session.size());
                    if (! session.get(i).isOpen()) {
                        nRemoveNum.add(i);
                        chatIdMap.remove(recieveSession.getId());
                    }
                }
                for(int forInt:nRemoveNum) {
                    session.remove(forInt);
                }
                
                
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }    
    
    @OnError
    public void error(Session recieveSession, Throwable error) {
        System.out.println("error : "+error.toString()+" : "+recieveSession.getId());
        if(chatIdMap.containsKey(recieveSession.getId()))
        {
            chatIdMap.remove(recieveSession.getId());
        }
    }
    
    private void connectlog(HashMap<StringString[]> log) {
        
         for (Entry<StringString[]> entrySet : log.entrySet()) {
             String[] logtest= entrySet.getValue();
             String bb = entrySet.getKey()+" = ";
             for(String a:logtest) {
                 bb+=a+" : ";
             }
             System.out.println("AA : "+entrySet.getKey() + " : " + bb);
         }
        
    }
    
}
 
cs

 

 

 

HTML 단

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Chat</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</head>
<style>
    * {
    padding: 0;
    margin: 0;
    box-sizing: border-box;
}
 
{
    text-decoration: none;
}
 
.wrap {
    padding: 40px 0;
    background-color: #A8C0D6;
}
 
.wrap .chat {
    display: flex;
    align-items: flex-start;
    padding: 20px;
}
 
.wrap .chat .icon {
    position: relative;
    overflow: hidden;
    width: 50px;
    height: 50px;
    border-radius: 50%;
    background-color: #eee;
}
 
.wrap .chat .icon i {
    position: absolute;
    top: 10px;
    left: 50%;
    font-size: 2.5rem;
    color: #aaa;
    transform: translateX(-50%);
}
 
.wrap .chat .textbox {
    position: relative;
    display: inline-block;
    max-width: calc(100% - 70px);
    padding: 10px;
    margin-top: 7px;
    font-size: 13px;
    border-radius: 10px;
}
 
.wrap .chat .textbox::before {
    position: absolute;
    display: block;
    top: 0;
    font-size: 1.5rem;
}
 
.wrap .ch1 .textbox {
    margin-left: 20px;
    background-color: #ddd;
}
 
.wrap .ch1 .textbox::before {
    left: -15px;
    content: "◀";
    color: #ddd;
}
 
.wrap .ch2 {
    flex-direction: row-reverse;
}
 
.wrap .ch2 .textbox {
    margin-right: 20px;
    background-color: #F9EB54;
}
 
.wrap .ch2 .textbox::before {
    right: -15px;
    content: "▶";
    color: #F9EB54;
}
 
.wrap .ch3 {
    flex-direction: row-reverse;
}
 
.wrap .ch3 .textbox {
    margin-left: 50%;
    /* background-color: #9d5830; */
}
/* 
.wrap .ch3 .textbox::before {
    right: -15px;
    color: #9d5830;
} */
 
 
</style>
<body onload="HtmlOnload();">
 
 
<div class="chatTitle">
    <h1>[과목채팅]</h1>
    <h3 id="SmstNm_h1"></h1>
    <h3 id="DertNm_h1"></h1>
    <h5 id="DertInfo_h1"></h1>
</div>
<!-- 채팅 입력창 -->
<div style="width: 70%; float: left; overflow-y: scroll; height:90%;">
    <div class="wrap"></div>
    <div id="chatFocus">
    </div>
</div>
 
<div style="float: right; width: 30%; top: 0;">
    참여자
<div class="container mt-3" id="nameTable" style="width: 100%; height: 90%; overflow-y: scroll;">
    
    <table class="table table-striped" style="width: 100%;">
        <!-- 현재 접속자 -->
        <tbody class="connecterName"> 
          <tr>
            <td>John</td>
          </tr>
        </tbody>
      </table>
</div>
</div>
 
<div class="msgArea" style="position: fixed; bottom: 0%; width: 100%;">
    <div class="input-group input-group-lg mb-3">
    <input type="text" placeholder="보낼 메세지를 입력하세요." onkeyup="if(window.event.keyCode==13){sendMsg()}" class="chatMsgText form-control">
    <span class="input-group-text"  onclick="sendMsg()">전송</span>
    <!-- <button type="button" class="btn btn-primary" value="전송" class="sendBtn" onclick="sendMsg()">전송</button> -->
    </div>
    
</div>
 
 
 
 
 
</body>
<script>
        var hostUrl = document.location.protocol +"//"+ document.location.host;
        console.log(hostUrl)
        let ws;//      var ws = new WebSocket('ws://localhost:8080');
        
        function getParameter(name) {
            name = name.replace(/[\[]/"\\[").replace(/[\]]/"\\]");
            var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
                results = regex.exec(location.search);
            return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
            
        }
 
 
 
 
        let sName    =  getParameter("NAME");
        let sDertNm    =  getParameter("DERT_NM"); // 과목명
        let sDertCd    =  getParameter("DERT_CD"); // 과목코드 (방번호)
        let sSmstNm    =  getParameter("SMST_NM"); // 학년도학기
        let sDertInfo    =  getParameter("DERT_INFO"); // 과목기본정보
        
 
        function connect() {
            let sHost = document.location.host;
            ws = new WebSocket("ws://"+sHost+"/kcu/lms/chat");
 
            console.log('connect');
            ws.onopen = function() {
                // subscribe to some channels
                
                let sChatVistor = "CONNECT"+":,:"+sName+":,:"+sDertNm+":,:"+sDertCd;
                console.log(sChatVistor);
                ws.send(sChatVistor);
            };
 
            // 메시지 수신
            ws.onmessage = function(e) {
                console.log('Message:', e.data);
                // 0 U,I,CON,DCON
                // 1 이름
                // 2 메시지
                // 3 학과명
                // 4 학과코드
                let arrMsg = e.data.split(":,:");
                console.log('a : '+sDertCd +" : "+arrMsg[5]);
                
                if(sDertCd!=arrMsg[5]){
                        return;
                }
 
                let sChatMsgDiv="";
                if(arrMsg[0]=="U"// 너가 보낸 메시지
                {
                    
                    sChatMsgDiv='<div class="chat ch1">';
                    sChatMsgDiv+='<div class="icon" style="text-align: center;">'+arrMsg[1]+'<i class="fa-solid fa-user"></i></div>';
                    sChatMsgDiv+='<div class="textbox">'+arrMsg[2]+'</div>';
                    sChatMsgDiv+='</div>';
 
                }else if(arrMsg[0]=="I"){ // 내가 보낸 메시지
                    sChatMsgDiv='<div class="chat ch2">';
                    sChatMsgDiv+='<div class="icon" style="text-align: center;">'+arrMsg[1]+'<i class="fa-solid fa-user"></i></div>';
                    sChatMsgDiv+='<div class="textbox">'+arrMsg[2]+'</div>';
                    sChatMsgDiv+='</div>';
                }
                else  if(arrMsg[0]=="CON"){ // 접속
                    sChatMsgDiv='<div class="chat ch3">';
                    sChatMsgDiv+='<div class="textbox">'+arrMsg[2]+'</div>';
                    sChatMsgDiv+='</div>';
 
                    let sChatVistorNames="";
                    sChatVistorNames+='<table class="table table-striped" style="width: 100%;">';
                    sChatVistorNames+='<tbody class="connecterName">';
                    let arrNames = arrMsg[1].split("+,+"); // 참여자들이 String에 묶에서 들어오는것 품
                    for(let i=0;i<arrNames.length;i++){ // 참여자 목록을 넣기 위함
                        sChatVistorNames+="<tr>";
                        sChatVistorNames+="<td>"+arrNames[i]+"</td>";
                        sChatVistorNames+="</tr>";
                    }
                    sChatVistorNames+='</tbody></table>';
                    document.getElementById('nameTable').innerHTML=sChatVistorNames;
 
                }
                else  if(arrMsg[0]=="DCON"){ // 접속 종료
                    console.log('DCON : ');
                    sChatMsgDiv='<div class="chat ch3">';
                    sChatMsgDiv+='<div class="textbox">'+arrMsg[2]+'</div>';
                    sChatMsgDiv+='</div>';
 
                    let sChatVistorNames="";
                    sChatVistorNames+='<table class="table table-striped" style="width: 100%;">';
                    sChatVistorNames+='<tbody class="connecterName">';
                    let arrNames = arrMsg[1].split("+,+");// 참여자들이 String에 묶에서 들어오는것 품
                    let bOutNameFlag=false// 접속 종료시 동명이인일 경우를 대비하여 한개의 이름만 빼게 함
                    for(let i=0;i<arrNames.length;i++){
 
                        console.log('CCC : '+arrNames[i] +" : "+arrMsg[3]);// 접속 종료자의 이름을 하나만 없앤다.
                        if(bOutNameFlag){
                            arrMsg[3]="";
                        }
 
                        if((arrMsg[3]!=arrNames[i])){ // 종료자를 제외한 모든 참여자 추가
                            sChatVistorNames+="<tr>";
                            sChatVistorNames+="<td>"+arrNames[i]+"</td>";
                            sChatVistorNames+="</tr>";
                        }
                        else{
                            bOutNameFlag=true;
                        }
                    }
                    sChatVistorNames+='</tbody></table>';
                    document.getElementById('nameTable').innerHTML=sChatVistorNames;
 
                }
                else{
                    return;
                }
                document.querySelector('.wrap').insertAdjacentHTML("beforeend",sChatMsgDiv);
                document.getElementById("chatFocus").scrollIntoView();
 
            };
 
            
            ws.onclose = function(e) {
                console.log('Socket is closed. Reconnect will be attempted in 1 second.', e.reason);
                setTimeout(function() {
                connect();
                }, 1000);
            };
 
            ws.onerror = function(err) {
                console.error('Socket encountered error: ', err.message, 'Closing socket');
                ws.close();
            };
        }
 
        function HtmlOnload() {
            connect();
            document.getElementById("DertNm_h1").innerHTML =sDertNm;
            document.getElementById("SmstNm_h1").innerHTML =sSmstNm;
            document.getElementById("DertInfo_h1").innerHTML =sDertInfo;
        }
        
 
 
        function sendMsg() {
            let chatMsgText=document.querySelector('.chatMsgText').value;
            document.querySelector('.chatMsgText').value="";
            console.log("SENd : "+chatMsgText);
            if(chatMsgText!="")
            {
                ws.send(chatMsgText);
            }
        }
       
</script>
</html>
cs

 

 

 

session값 저장과 함께 사용자를 구분할 chatIdMap 을 생성한다.

들어오는 내용은 (:,:)로 split하여 보낸사람의 정보를 받는다.

 

.../lmsChat.html?NAME=황재철&DERT_NM=위험물질론&DERT_CD=FD201701 

처음 접속할때 HTML에서 @OnMessage를 한번 보낸다.

예) CONNECT:,:황재철:,:위험물질론:,:FD201701 

1 접속할때 접속했다는걸 보내기 위한 구분

2 접속자 이름

3 채팅방 이름

4. 채팅방 구분번호

 

 

그뒤 참여자 목록을 (+,+)로 구분하여 같이 묶어서 보낸다.

참여했습니다., 종료했습니다. 들어올때 참여자를 삭제 추가 하여 참여자 정보를 유지한다.

 

메시지는 이름하고 내가 보낸건지 상대가 보낸건지 구분하여 전달하고 좌우 표시한다.

 

위 파라미터에서 DERT_CD가 동일해야 같은 방에 들어있는걸로 동작됨

 

졸작 수준의 채팅 완료