728x90
★ Socket
- 네트워크상에서 호스트간의 통신하는 규약(약속) - 인터페이스
- 여러 언어들이 Socket 구현 > 클래스 or 객체 제공
■ 웹통신, Ajax 통신 <-> 소켓 통신
- 단방향 통신(편지) <-> 양방향 통신(전화)
■ WebSocket
- HTML5
- JavaScript에서 소켓 통신 지원
- Spring -> WebSocket을 지원하는 API 제공
★ 새 프로젝트 생성
NEW > SPRING Legacy Project > Spring MVC Project > WebSocketTest > com.test.websocket > Finish
■ 기초 셋팅 작업
- pom.xml -> 의존성 2개 더 추가
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
</dependency>
■ 파일 생성
- com.test.controller > SocketController.java
- com.test.server > SocketServer.java
- views > test.jsp
- 스캔 추가 -> servlet-context.xml
<context:component-scan base-package="com.test.controller" />
<context:component-scan base-package="com.test.server" />
■ 코드 작성
- Controller 구성 -> SocketController.java
@Controller
public class SocketController {
@GetMapping("/test.do")
public String test() {
return "test";
}
}
- test.jsp -> 페이지 구성 및 소켓
<body>
<!-- test.jsp -->
<h1>WebSocket <small>연결 테스트</small></h1>
<div>
<button type="button" class="in" id="btnConnect">연결하기</button>
<button type="button" class="out" id="btnDisconnect">종료하기</button>
</div>
<hr>
<div>
<input type="text" class="long" id="msg">
<button type="button" class="add" id="btnEcho">에코 테스트</button>
</div>
<div class="message full"></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
//1. 소켓 생성
//2. 서버 접속(연결)
//3. 통신
//4. 서버 접속 종료
const uri = 'ws://localhost/websocket/server.do';
let ws;
$('#btnConnect').click(function(){
//1. 웹소켓 생성 > 자동으로 연결 요청
ws = new WebSocket(uri);
//웹소켓 서버에게 연결 요청이 성공하면 발생하는 이벤트 > 콜백 함수
ws.onopen = function(evt) {
log('서버와 연결되었습니다.');
};
ws.onclose = function(evt) {
log('서버와 종료되었습니다.');
};
ws.onmessage = function(evt) {
log(evt.data); //서버가 보낸 메시지
};
ws.onerror = function(evt) {
log('에러가 발생했습니다.');
}
});
$('#btnDisconnect').click(function() {
//연결된 소켓을 중단
ws.close();
log('연결을 종료합니다.');
});
$('#btnEcho').click(function() {
//연결된 소켓으로 서버에게 데이터 전송하기
//ws.CONNECTING : 연결중
//ws.OPEN : 열림 > 통신 가능 상태
//ws.CLOSING : 닫는중
//ws.CLOSED : 닫힘
if(ws.readyState == ws.OPEN) {
//ws.send('안녕하세요.');
ws.send($('#msg').val());
log('메시지를 전달했습니다.');
}
});
function log(msg) {
$('.message').prepend(`<div>[\${new Date().toLocaleTimeString()}] \${msg}</div>`);
}
</script>
- 소켓 서버 -> SocketServer.java
//프로그램 <- (통신) -> 프로그램
//카톡 <- -> 카톡서버
//브라우저 <- -> 톰캣
//상대편의 고유한 주소 인식!! > IP 주소(NIC) + 포트번호 > EndPoint(종단점)
//클라이언트(WebSocket) 연결을 받아주는 역할 > 웹소켓 서버
//192.168.30.31:80/server.do
@ServerEndpoint("/server.do")
public class SocketServer {
//1.클라이언트가 연결 요청을 하기를 기다림 > 자동 수락
@OnOpen
public void handleOpen() {
System.out.println("클라이언트가 접속했습니다.");
}
//클라이언트가 연결을 종료하면 발생
@OnClose
public void handleClose() {
System.out.println("클라이언트와 연결이 종료됐습니다.");
}
//클라이언트가 서버에게 메시지를 전송했을 때 발생
@OnMessage
public String handleMessage(String msg) { //클라이언트가 보낸 메시지
System.out.println("클라이언트 메시지: " + msg);
return "(응답)" + msg; //클라이언트에게 보내는 메시지
}
@OnError
public void handleError(Throwable e) {
System.out.println("에러 발생 : " + e.getMessage());
}
}
- 실행 결과
★ 알림 서비스
- 관리자가 접속자에게 공지 > 알림 표시 구현
- 파일 생성
- com.test.server > NoticeServer.java
- views > admin.jsp
> user.jsp
- 컨트롤러 추가 -> SocketController.java
@GetMapping("/admin.do")
public String admin() {
return "admin";
}
@GetMapping("/user.do")
public String user() {
return "user";
}
- 소켓 서버 > NoticeServer.java
@ServerEndpoint("/noticeserver.do")
public class NoticeServer {
private static List<Session> sessionList;
static {
sessionList = new ArrayList<Session>();
}
@OnOpen
public void handleOpen(Session session) {
System.out.println("연결 성공");
sessionList.add(session);
}
@OnMessage
public void handleMessage(String msg, Session session) {
clearSession();
System.out.println("메시지: " + msg); //공지사항
//관리자 > (전송) > 공지사항 > (수신) > 서버 > (전송) > 모든 유저
for(Session s : sessionList) {
if(session != s)
try {
//관리자 제외한 나머지 유저들에게만 메시지 전달
s.getBasicRemote().sendText(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
@OnClose
public void handleClose() {
}
@OnError
public void handleError(Throwable e) {
}
public void clearSession() {
Iterator<Session> iter = sessionList.iterator();
while (iter.hasNext()) {
if (!(iter.next()).isOpen()) {
iter.remove(); //접속이 끊긴 클라이언트 소켓(세션)을 제거
}
}
}
}
- 관리자 페이지 -> admin.jsp
<body>
<!-- admin.jsp -->
<h1>알림 서비스 <small>관리자</small></h1>
<div>
<textarea id="msg"></textarea>
<button type="button" id="btnSend" class="add">보내기</button>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
const uri = 'ws://localhost/websocket/noticeserver.do';
let ws;
$(document).ready(function() { //페이지 초기화
ws = new WebSocket(uri);
ws.onopen = function(evt) {
console.log('서버와 연결됨');
};
ws.onmessage = function(evt) {
};
ws.onclose = function(evt) {
};
ws.onerror = function(evt) {
};
});
$('#btnSend').click(function() {
ws.send($('#msg').val());
});
</script>
- 사용자 페이지 -> user.jsp
<body>
<!-- user.jsp -->
<h1>알림 서비스 <small>유저</small></h1>
<div id="msgBox"></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
function showMsgBox(msg) {
$('#msgBox').text(msg);
$('#msgBox').css({
bottom: '20px',
opacity: 1
});
setTimeout(function() {
$('#msgBox').css({
bottom: '-200px',
opacity: 0
});
}, 5000);
}
//showMsgBox();
const uri = 'ws://localhost/websocket/noticeserver.do';
let ws;
$(document).ready(function() {
ws = new WebSocket(uri);
ws.onopen = function(evt) {
console.log('연결 성공');
};
ws.onmessage = function(evt) {
console.log(evt.data);
showMsgBox(evt.data);
};
ws.onclose = function(evt) {
console.log('연결 종료');
};
ws.onerror = function(evt) {
};
});
</script>
- 실행 결과
★ 채팅 서비스
- 파일 생성
- com.test.server > ChatServer.java
- com.test.domain > ChatDTO.java
- views > index.jsp : 입장 페이지
> chat.jsp : 대화 페이지
- 컨트롤러 추가 -> SocketController.java
@GetMapping("/index.do")
public String index() {
return "index";
}
@GetMapping("/chat.do")
public String chat() {
return "chat";
}
- 소켓 서버 -> ChatServer.java
@ServerEndpoint("/chatserver.do")
public class ChatServer {
private static List<Session> sessionList;
static {
sessionList = new ArrayList<Session>();
}
@OnOpen
public void handleOpen(Session session) {
System.out.println("연결 성공");
sessionList.add(session);
}
@OnMessage
public void handleMessage(String msg, Session session) {
System.out.println(msg);
//JSON <- (변환) -> Java Object
//- GSON
Gson gson = new Gson();
ChatDTO dto = gson.fromJson(msg, ChatDTO.class);
if (dto.getCode().equals("1")) {
//새로운 유저가 접속했습니다. > 모든 사람에게 알림(방금 접속한 사람빼고)
for (Session s : sessionList) {
if(s != session) {
try {
s.getBasicRemote().sendText(msg);
} catch (Exception e) {
e.printStackTrace();
}
}
}
} else if (dto.getCode().equals("2")) {
//누군가가 퇴장 > 나머지 사람들에게 알려줌
sessionList.remove(session); //서버측에서의 퇴장
for (Session s : sessionList) {
try {
s.getBasicRemote().sendText(msg);
} catch (Exception e) {
e.printStackTrace();
}
}
} else if (dto.getCode().equals("3")) {
//대화 메시지 > 모두에게 전달
for (Session s : sessionList) {
if(s != session) {
try {
s.getBasicRemote().sendText(msg);
} catch (Exception e) {
e.printStackTrace();
}
}
}
} else if (dto.getCode().equals("4")) {
//이모티콘 메시지 > 모두에게 전달
for (Session s : sessionList) {
if(s != session) {
try {
s.getBasicRemote().sendText(msg);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
@OnClose
public void handleClose() {
}
@OnError
public void handleError(Throwable e) {
}
public void clearSession() {
Iterator<Session> iter = sessionList.iterator();
while (iter.hasNext()) {
if (!(iter.next()).isOpen()) {
iter.remove(); //접속이 끊긴 클라이언트 소켓(세션)을 제거
}
}
}
}
- DTO -> ChatDTO.java
@Data
public class ChatDTO {
private String code;
private String sender;
private String receiver;
private String content;
private String regdate;
}
- 입장 페이지 -> index.jsp
<body>
<!-- index.jsp -->
<h1>WebSocket <small>Chat</small></h1>
<div>
<div class="group">
<label>대화명</label>
<input type="text" name="name" id="name" class="short">
</div>
</div>
<div>
<button type="button" class="in">들어가기</button>
<button type="button" class="in" data-name="강아지">강아지</button>
<button type="button" class="in" data-name="고양이">고양이</button>
<button type="button" class="in" data-name="사자">사자</button>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
$('.in').click(function() {
let name = $(event.target).data('name');
if(name==null || name==undefined) {
name = $('#name').val();
}
let child = window.open('/websocket/chat.do', 'chat', 'width=360 height=500');
$('.in').css('opacity', .5);
$('.in').prop('disabled', true);
child.addEventListener('load', function() {
//child.connect($('#name').val());
child.connect(name);
});
});
</script>
- 채팅 페이지 -> chat.jsp
<body>
<!-- chat.jsp -->
<div id="main">
<div id="header">
<h2>
WebSocket <small></small>
</h2>
</div>
<div id="list"></div>
<input type="text" id="msg" placeholder="대화 내용을 입력하세요.">
</div>
<script
src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dayjs@1.11.8/dayjs.min.js
"></script>
<script>
//창 닫을 때
$(window).on('beforeunload', function() {
$(opener.document).find('.in').css('opacity', 1);
$(opener.document).find('.in').prop('disabled', false);
//저 나가요~라고 서버에게 메시지 보내기
let chat = {
code: '2',
sender: window.name,
receiver: '',
content: '',
regdate: dayjs().format('YYYY-MM-DD HH:mm:ss')
};
ws.send(JSON.stringify(chat));
});
window.onkeydown = function() {
if (event.keyCode == 116) {
event.preventDefault();
return false;
}
};
/*
서버 <- (대화) -> 클라이언트
- code: 상태코드
1: 새로운 유저가 들어옴
2: 기존 유저가 나감
3: 메시지 전달
4: 이모티콘 전달
- sender: 보내는 유저명
- receiver: 받는 유저명
- content: 메시지
- regdate: 날짜/시간
*/
//채팅 준비
const url = 'ws://localhost/websocket/chatserver.do';
let ws;
var name;
function connect(name) {
window.name = name;
$('#header small').text(name);
//연결하기
ws = new WebSocket(url);
ws.onopen = function(evt) {
log('서버 연결 성공');
//내가 접속했다고 다른 사람들에게 알리기
let chat = {
code: '1',
sender: window.name,
receiver: '',
content: '',
regdate: dayjs().format('YYYY-MM-DD HH:mm:ss')
};
ws.send(JSON.stringify(chat));
print('', '입장했습니다.', 'me', 'state', chat.regdate);
$('#msg').focus();
};
ws.onmessage = function(evt) {
log('메시지 수신');
let chat = JSON.parse(evt.data);
log(chat.code + ':' + chat.sender);
if (chat.code == '1') {
//다른 사람 입장
print('',`[\${chat.sender}]님이 입장했습니다.`, 'other', 'state', chat.regdate);
} else if (chat.code == '2') {
//다른 사람 퇴장
print('',`[\${chat.sender}]님이 퇴장했습니다.`, 'other', 'state', chat.regdate);
} else if (chat.code == '3') {
//대화 수신
print(chat.sender, chat.content, 'other', 'msg', chat.regdate);
} else if (chat.code == '4') {
//대화 수신
printEmoticon(chat.sender, chat.content, 'other', 'msg', chat.regdate);
}
};
ws.onclose = function(evt) {
};
ws.onerror = function(evt) {
};
}
function log(msg) {
console.log(`[\${new Date().toLocaleTimeString()}]` + msg)
}
function print(name, msg, side, state, time) {
let temp = `
<div class="item \${state} \${side}">
<div>
<div>\${name}</div>
<div>\${msg}</div>
</div>
<div>\${time}</div>
</div>
`;
$('#list').append(temp);
scrollList();
}
function printEmoticon(name, msg, side, state, time) {
let temp = `
<div class="item \${state} \${side}">
<div>
<div>\${name}</div>
<div style="background-color:#FFF;border:0;"><img src="/websocket/resources/emoticon/\${msg}.png" </div>
<div>\${time}</div>
</div>
`;
$('#list').append(temp);
setTimeout(scrollList, 100);
}
$('#msg').keydown(function(evt) {
if (evt.keyCode == 13) {
let chat = {
code: '3',
sender: window.name,
receiver: '',
content: $('#msg').val(),
regdate: dayjs().format('YYYY-MM-DD HH:mm:ss')
};
//이모티콘 전송
if($('#msg').val().startsWith('/')) {
chat.code = '4';
chat.content = chat.content.split(' ')[0];
}
ws.send(JSON.stringify(chat));
if(chat.code == '3'){
print(window.name, chat.content, 'me', 'msg', chat.regdate);
} else if (chat.code == '4') {
printEmoticon(window.name, chat.content, 'me', 'msg', chat.regdate);
}
$('#msg').val('').focus();
}
});
function scrollList() {
$('#list').scrollTop($('#list')[0].scrollHeight + 500);
}
</script>
- 날짜 Library 사용 -> day.js 사용
moment.js
day.js
- 실행 결과
728x90
'Spring' 카테고리의 다른 글
Spring Boot STEP 2 - 기본적인 CRUD 사용 (0) | 2023.06.26 |
---|---|
Spring Boot STEP 1 - 기초 셋팅 및 실행 (0) | 2023.06.26 |
Spring STEP 14 - RESTful Service (0) | 2023.06.22 |
Spring STEP 13 - Board 2 (0) | 2023.06.22 |
Spring STEP 13 - Board (0) | 2023.06.21 |