07_Framework
Socket?
A, B 양 끝 단을 이어주는 것
WebScoket?
Client(여러대) <----------> Server(1대)
A, B 실시간으로 소통하고 싶다면?
'Server'가 연결해줌 == 1 : 1 채팅
A, B, C, D 실시간으로 소통하고 싶다면?
'Server'가 연결해줌 == 1 : N 채팅
HTTP 요청?
html 문서 요청 <-> 응답
WebSocket protocol
1 : 1 채팅 (ex. 카카오톡)
+ Jackson-databind, Spring WebSocket, GSON 모듈 있어야함!
package edu.kh.project.main.model.websocket;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
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;
// TextWebSocketHandler: text 형식(ex.채팅)으로 주고받을 때 사용하는 Handler
public class TestWebSocketHandler extends TextWebSocketHandler{
// Set : 중복 X, 순서 X
// synchronizedSet : 동기화된 Set 객체 반환
private Set<WebSocketSession> sessions
= Collections.synchronizedSet(new HashSet<>());
// WebSocketSession : 클라이언트 - 서버 간 전이중통신을 담당하는 객체
// 클라이언트의 세션을 가로채서 저장하고 있음 // 세션? 접속한 클라이언트 1개당 생성
// - 클라이언트와 연결이 완료되고, 통신할 준비가 되면 실행
@Override // (가로 챈) 클라이언트 세션
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// 클라이언트 웹소켓 연결을 요청하면 sessions에
// 클라이언트와 전이중통신을 담당하는 객체 WebSocketSession을 추가
sessions.add(session);
}
// - 클라이언트로부터 텍스트 메세지를 받았을 때 실행
@Override // 클라이언트가 보낸 메세지(ex. Hi)
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
System.out.println("전달 받은 내용 : " + message.getPayload());
// /testSock으로 연결된 객체를 만든 클라이언트들(sessions)에게
// 전달받은 내용을 보냄
for(WebSocketSession s:sessions) {
s.sendMessage(message);
}
}
// - 클라이언트와 연결이 종료되면 실행
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
// sessions에서 나간 클라이언트의 정보를 제거
sessions.remove(session);
}
}
/* WebSocket
- 브라우저와 웹서버간의 전이중통신을 지원하는 프로토콜이다
- HTML5버전부터 지원하는 기능이다.
- 자바 톰캣7버전부터 지원했으나 8버전부터 본격적으로 지원한다.
- spring4부터 웹소켓을 지원한다.
(전이중 통신(Full Duplex): 두 대의 단말기가 데이터를 송수신하기 위해 동시에 각각 독립된 회선을 사용하는 통신 방식.
대표적으로 전화망, 고속 데이터 통신)
WebSocketHandler 인터페이스 : 웹소켓을 위한 메소드를 지원하는 인터페이스
-> WebSocketHandler 인터페이스를 상속받은 클래스를 이용해 웹소켓 기능을 구현
WebSocketHandler 주요 메소드
** 순서대로 실행 **
void afterConnectionEstablished(WebSocketSession session)
- 클라이언트와 연결이 완료되고, 통신할 준비가 되면 실행
void handlerMessage(WebSocketSession session, WebSocketMessage message)
- 클라이언트로부터 메세지가 도착하면 실행
void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus)
- 클라이언트와 연결이 종료되면 실행
void handleTransportError(WebSocketSession session, Throwable exception) -> 부가적인 사항
- 메세지 전송중 에러가 발생하면 실행
----------------------------------------------------------------------------
TextWebSocketHandler : WebSocketHandler 인터페이스를 상속받아 구현한 텍스트 메세지 전용 웹소켓 핸들러 클래스
handlerTextMessage(WebSocketSession session, TextMessage message)
- 클라이언트로부터 '텍스트 메세지'를 받았을때 실행 -> 텍스트 == 글자
*/
<?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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.3.xsd
http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />
<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />
<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 접두사 -->
<beans:property name="prefix" value="/WEB-INF/views/" />
<!-- 접미사 -->
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<!--
base-package 이하에 작성된 @Component 와
자식 어노테이션 @Controller, @Service, @Repository 이 붙어있는
클래스를 찾아서 Bean으로 등록
-->
<context:component-scan base-package="edu.kh.project" />
<!-- 트랜잭션 처리 시 @Transactional 어노테이션을 사용할 예정
해당 어노테이션을 인식하라는 설정이 필요함
@Transactional: 클래스 또는 메서드 수행 후 트랜잭션 처리를 하라고 알려주는 어노테이션
-->
<!-- @Transactinal 어노테이션 인식, 활성화 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- AOP Proxy를 이용한 관점 제어 자동화 -->
<aop:aspectj-autoproxy />
<!-- 게시판 인터셉터 관련 부분 -->
<interceptors>
<interceptor>
<!-- 인터셉터가 동작할 url 패턴 -->
<mapping path="/**"/>
<beans:bean id="boardTypeInterceptor" class="edu.kh.project.common.interceptor.BoardTypeInterceptor"/>
</interceptor>
</interceptors>
<!-- 웹소켓 처리 클래스를 bean으로 등록 -->
<beans:bean id="testHandler"
class="edu.kh.project.main.model.websocket.TestWebSocketHandler"/>
<!-- 어떤 주소로 웹소켓 요청이 오면 세션을 가로챌지 지정 -->
<websocket:handlers>
<websocket:mapping handler="testHandler" path="/testSock"/>
<!-- 요청 클라이언트의 세션을 가로채서 WebSocketSession에 넣어주는 역할 -->
<websocket:handshake-interceptors>
<beans:bean class="org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor"/>
</websocket:handshake-interceptors>
<!-- SockJS를 이용한 웹소켓 연결 요청임을 명시 -->
<websocket:sockjs/>
</websocket:handlers>
</beans:beans>
const loginFrm = document.getElementById("loginFrm");
const memberEmail = document.querySelector("#loginFrm input[name='memberEmail']");
const memberPw = document.querySelector("#loginFrm input[name='memberPw']");
if(loginFrm != null){
// 로그인 시도를 할 때
loginFrm.addEventListener("submit", e => {
// 이메일이 입력되지 않은 경우
// 문자열.trim() : 문자열 좌우 공백 제거
if(memberEmail.value.trim().length == 0){
alert("이메일을 입력해주세요.");
memberEmail.value = ""; // 잘못 입력된 값(공백) 제거
memberEmail.focus(); // 이메일 input태그에 초점을 맞춤
e.preventDefault(); // (기본이벤트 제거 : 제출 못하게하기)
return;
}
// 비밀번호가 입력되지 않은 경우
if(memberPw.value.trim().length == 0){
alert("비밀번호를 입력해주세요.");
memberPw.value = ""; // 잘못 입력된 값(공백) 제거
memberPw.focus(); // 이메일 input태그에 초점을 맞춤
e.preventDefault(); // 제출 못하게하기
return;
}
});
}
// -----------------------------------------------
// fetch API : 웹 브라우저에서 서버로 HTTP 요청을 하게해주는 최신 인터페이스
/**
* fetch(url)
* .then(response => response.json() / response.text()) // 파싱
* .then(data => console.log(data)) // 데이터 가공
* .catch(error => console.log(error));
*
* 첫 번째 then() 함수는 서버 요청에 대한 응답이 왔을때 실행됨
* - 응답받은 데이터가 반환되는 값이 단순 자료형 1개면 text(),
* 객체(Map)면 json() 으로 파싱(구문해석)한 후 다음 then() 함수로 넘겨준다.
*
*
* 두 번째 then() 함수는 response.json()/text()으로 상황에 맞게
* 데이터가 파싱 완료되면 실행.
* 파싱된 데이터가 전달되며, 이 값을 로직에 맞게 가공한다.
*
*
*/
// 닉네임이 일치하는 회원의 전화번호 조회
const inputNickname = document.getElementById("inputNickname");
const btn1 = document.getElementById("btn1");
const result1 = document.getElementById("result1");
btn1.addEventListener("click", () => {
// fetch API를 이용해서 ajax
// GET 방식 요청 (파라미터를 쿼리스트링으로 추가)
// Promise : 비동기 함수 호출 또는 연산이 완료되었을 때
// 이후에 처리할 함수나 에러를 처리하기 위한
// 함수를 설정하는 모듈
// -> 비동기 연산의 최종 결과 객체
fetch("/selectMemberTel?nickname=" + inputNickname.value)
.then( resp => resp.text() ) // 응답 객체(자료형 1일때)를 문자열 형식으로 파싱
.then( data => {
// 데이터 가공
console.log(data);
result1.innerText = data;
})
.catch( err => console.log(err) );
});
// fetch() API 를 이용한 POST 방식 요청
// 이메일을 입력받아 일치하는 회원의 정보를 조회
const inputEmail = document.getElementById("inputEmail");
const btn2 = document.getElementById("btn2");
const result2 = document.getElementById("result2");
btn2.addEventListener("click", () => {
// JSON.stringify() : JS 객체 -> JSON
// JSON.parse() : JSON -> JS 객체
// JSON : Javascript 객체 문법으로, 구조화된 데이터를 표현하기 위한
// 문자 기반의 표준 포맷이다.
// 서버에서 클라이언트로 데이터를 전송하여 표현하거나,
// 그 반대의 경우에 사용한다.
// GET 방식
fetch("/selectMember?email=" + inputEmail.value)
.then( resp => resp.json() ) // 응답 객체(자료형 1일때)를 문자열 형식으로 파싱
.then( data => {
// 데이터 가공
console.log(data);
result2.innerText = JSON.stringify(data);
})
.catch( err => console.log(err) );
/*
// POST 방식
let obj = {};
obj.email = inputEmail.value;
fetch("/selectMember", { // K:V 형식으로 작성해야 함
method : "POST",
headers : {"Content-Type" : "application/json"},
// 요청 보내는 자원을 명시
// -> js 객체를 json 형식으로 만들어 파라미터로 전달
//body : JSON.stringify({"email":inputEmail.value,"pw":inputpw.value}) // JS객체 형태 : { K : V }
body : JSON.stringify(obj) // JS객체 형태 : { K : V }
// 이렇게 보내는 방법도 가능!
//let obj = {};
//obj.email = inputEmail.value;
//obj.pw = inputPw.value;
//body : JSON.stringify(obj) // JS객체 형태 : { K : V }
})
.then(resp => resp.json()) // 응답 객체를 자바스크립트 객체 형태로
// 파싱하는것
.then(member => {
console.log(member); // javascript 객체
result2.innerText = JSON.stringify(member);
})
.catch( err => console.log(err) );
*/
});
// ----------------------------------------------------------------------
// 웹소켓 테스트
// 1. SockJS 라이브러리 추가
// 2. SockJS를 이용해서 클라이언트용 웹소켓 객체 생성
let testSock = new SockJS("/testSock");
function sendMessage(name, str) {
// 매개변수를 JS 객체에 저장
let obj = {}; // 비어있는 객체
obj.name = name; // 객체에 일치하는 key 가 없다면 자동으로 추가
obj.str = str;
// console.log(obj);
testSock.send( JSON.stringify(obj) ); // JS객체 -> JSON
}
// 웹소켓 객체(testSock)가 서버로 부터 전달받은 메세지가 있는 경우
testSock.onmessage = e => {
// e : 이벤트객체
// e.data : 전달 받은 메세지(JSON)
let obj = JSON.parse(e.data); // JSON -> JS객체
console.log(`보낸사람 : ${obj.name} / ${obj.str}`);
}
* Table 2개 생성
-- 채팅
CREATE TABLE "CHATTING_ROOM" (
"CHATTING_NO" NUMBER NOT NULL,
"CH_CREATE_DATE" DATE DEFAULT SYSDATE NOT NULL,
"OPEN_MEMBER" NUMBER NOT NULL,
"PARTICIPANT" NUMBER NOT NULL
);
COMMENT ON COLUMN "CHATTING_ROOM"."CHATTING_NO" IS '채팅방 번호';
COMMENT ON COLUMN "CHATTING_ROOM"."CH_CREATE_DATE" IS '채팅방 생성일';
COMMENT ON COLUMN "CHATTING_ROOM"."OPEN_MEMBER" IS '개설자 회원 번호';
COMMENT ON COLUMN "CHATTING_ROOM"."PARTICIPANT" IS '참여자 회원 번호';
ALTER TABLE "CHATTING_ROOM" ADD CONSTRAINT "PK_CHATTING_ROOM" PRIMARY KEY (
"CHATTING_NO"
);
ALTER TABLE "CHATTING_ROOM"
ADD CONSTRAINT "FK_OPEN_MEMBER"
FOREIGN KEY ("OPEN_MEMBER") REFERENCES "MEMBER";
ALTER TABLE "CHATTING_ROOM"
ADD CONSTRAINT "FK_PARTICIPANT"
FOREIGN KEY ("PARTICIPANT") REFERENCES "MEMBER";
DROP TABLE "MESSAGE";
CREATE TABLE "MESSAGE" (
"MESSAGE_NO" NUMBER NOT NULL,
"MESSAGE_CONTENT" VARCHAR2(4000) NOT NULL,
"READ_FL" CHAR DEFAULT 'N' NOT NULL,
"SEND_TIME" DATE DEFAULT SYSDATE NOT NULL,
"SENDER_NO" NUMBER NOT NULL,
"CHATTING_NO" NUMBER NOT NULL
);
COMMENT ON COLUMN "MESSAGE"."MESSAGE_NO" IS '메세지 번호';
COMMENT ON COLUMN "MESSAGE"."MESSAGE_CONTENT" IS '메세지 내용';
COMMENT ON COLUMN "MESSAGE"."READ_FL" IS '읽음 여부';
COMMENT ON COLUMN "MESSAGE"."SEND_TIME" IS '메세지 보낸 시간';
COMMENT ON COLUMN "MESSAGE"."SENDER_NO" IS '보낸 회원의 번호';
COMMENT ON COLUMN "MESSAGE"."CHATTING_NO" IS '채팅방 번호';
ALTER TABLE "MESSAGE" ADD CONSTRAINT "PK_MESSAGE" PRIMARY KEY (
"MESSAGE_NO"
);
ALTER TABLE "MESSAGE"
ADD CONSTRAINT "FK_CHATTING_NO"
FOREIGN KEY ("CHATTING_NO") REFERENCES "CHATTING_ROOM";
ALTER TABLE "MESSAGE"
ADD CONSTRAINT "FK_SENDER_NO"
FOREIGN KEY ("SENDER_NO") REFERENCES "MEMBER";
-- 시퀀스 생성
CREATE SEQUENCE SEQ_ROOM_NO NOCACHE;
CREATE SEQUENCE SEQ_MESSAGE_NO NOCACHE;