WebSocket

  • http 나 https 는 연결형 프로토콜이기는 하지만 한 번 요청을 해서 응답을 받으면 연결이 끊어집니다.
  • 채팅과 같은 애플리케이션을 만들 때 http 나 https 를 이용하는 것은 어려운 작업이다.

    • 데이터를 여러번 주고 받고자 하는 경우 연결하고 끊고 다시 연결하고 끊고 하는 작업을 반복해야 한다.
    • 데이터를 주고 받는 시간보다 연결과 해제에 많은 자원을 소모한다.
    • 그래서 등장방법 중의 하나는 애플리케이션을 설치하는 방법이었습니다.
  • 순수 웹 환경에서 실시간 양방향 통신을 위한 Spec이 HTML5에서 추가가 되었는데 이 Spec이 WebSocket 입니다.

    • WebSocket은 한번 연결하면 close 할 때 까지 계속 연결되어 있다.
  • 최근에는 Android나 iOS에서도 웹 소켓에 접속하는 API가 추가

1.클라이언트(웹브라우저)에서의 사용 방법(자바스크립트)

1) WebSocket 객체 생성

new WebSocket("ws://domain/demo")

2) 데이터 전송

WebSocket객체.send("데이터")

3) 데이터 전송 받기 - 콜백 사용

WebSocket객체.addEventListener("message", function(e)){
        //매개변수 e가 전송된 데이터입니다.
}

4) 기타 이벤트

open, close 이벤트

5) 연결 해제

WebSocket객체.close()

2.Spring을 이용한 WebSocket 서버 설정

  • WebSocketHandler 인터페이스를 구현한 클래스를 생성하고 메소드 재정의

  • 클래스위에 @EnableWebSocket 이나 설정 파일에 websocket:handlers 태그를 이용해서 웹 소켓 서버 설정

3.WebSocketHandler 인터페이스를 구현한 클래스

  • TextWebSocketHandler, AbstractWebSocketHandler

4.의존성 라이브러리

spring-websocket

5.주의할 점

  • spring의 websocket은 서블릿 3.0부터 사용 가능

6.실습

1) pom.xml 파일에 spring-websocket 의존성 설정

        <!-- 웹 소켓 사용을 위한 의존성 라이브러 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-websocket</artifactId>
            <version>${org.springframework-version}</version>
        </dependency>

2) WebSocket 서버 클래스를 생성

  • TextWebSocketHandler 클래스를 상속
  • 기본패키지 안에 생성
  • com.gmail.hiwony7933.util.ChatHandler

image-20200224123427013

//Bean 생성을 자동으로 해주기 위한 어노테이션 
@Component
// 웹 소켓 채팅서버 클래스 
public class ChatHandler extends TextWebSocketHandler {
    // 접속한 유저 목록을 가질 list를 생성
    private static List<WebSocketSession> users = new ArrayList<WebSocketSession>();

    // 클라이언트가 접속 했을 때 호출될 메소드
    // 매개변수로 대입된 데이터가 접속한 클라이언트
    @Override
    public void afterConnectionEstablished(WebSocketSession session) {
        // List에 추가
        users.add(session);

    }

    // 클라이언트가 접속을 해제했을 때 호출될 메소드
    // 매개변수로 대입된 데이터가 접속을 해제한 클라이언트
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
        // List에서 제거
        users.remove(session);
    }

    @Override
    public void handleTextMessage(WebSocketSession session, TextMessage message) {
        // 전송된 메시지를 출력
        System.out.println(message.getPayload() + "가 전송됨 ");
        // 전송받은 메시지를 모든 유저에게 다시 전송
        for (WebSocketSession ses : users) {
            try {
                ses.sendMessage(new TextMessage(message.getPayload()));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

1) ChatHandler 클래스를 URL과 매핑하는 코드를 servlet-context.xml 파일에 작성

  • sevlet-context.xml 파일에 websorcket 네임스페이스를 추가
    <!--  WebSocket 클래스와 URL 매핑  -->
    <websocket:handlers>
        <!-- handler에서 bean의 id를 작성하고 path는 클라이언트가 접속할 URL  -->
        <websocket:mapping handler="chatHandler" path="/chat-ws"/>
    </websocket:handlers>

4) hom.jsp 파일에 채팅 페이지로 이동할 링크를 추가

<a href="chat">WebSocket을 이용한 채팅구현 </a><br/>

5) HomeController 클래스의 위의 요청을 처리하는 메소드를 작성

    //chat 이라는 요청이 오면 chat이라는 문자열을 가지고 ViewReslover 설정을 확인해서 
    // 뷰 페이지를 결정 - WEB-INF/views/?.jsp 
    @RequestMapping(value="/chat", method=RequestMethod.GET)
    public String chat(HttpServletRequest request, Model model ) {

        // 뷰 이름을 리턴 
        return "chat";
    }

6) views 디렉토리에 chat.jsp 파일을 만들고 작성

pom.xml 의 servlet 버전이 2.5 -> 3.1 버전으로 교체 했다.

        <!-- Servlet -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
        </dependency>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>채팅</title>
<style>
    #chatArea{
        width:200px;
        height:100px;
        overflow-y:auto;
        border:1px solid black;
    }
</style>
</head>
<body>
    이름<input type="text" id="nickname" />
    <input type="button" id="enterbtn" value="입장" />
    <input type="button" id="exitbtn" value="퇴장" />

    <h1>채팅창</h1>
    <div id="chatArea">
        <div id="chatmessagearea"></div>
    </div>
    <br />

    <input type="text" id="message" />
    <input type="button" id="sendbtn" value="전송" />

    <script>
        //웹 소켓 변수
        var wsocket

        //문자열을 출력하는 함수 
        var appendMessage = function(msg){
            document.getElementById("chatmessagearea").innerHTML = 
                msg + "<br/>" + document.getElementById("chatmessagearea").innerHTML    

        }

        // 이벤트 처리 함수 
        var onOpen = function(){
            appendMessage("연결되었습니다");
        }
        var onClose = function(){
            appendMessage("연결 해제되었습니다.");
            wsocket.close();    
        }
        var onMessage = function(evnt) {
            var data = evnt.data;
            appendMessage(data);
        }
        var send = function(){
            // 입력한 내용을 WebSocket 서버에게 전달하고 message 란은 클리어  
            var nickname = document.getElementById("nickname").value;
            var msg = document.getElementById("message").value;
            wsocket.send(nickname + ":" + msg);
            document.getElementById("message").value='';
        }

        //웹 소켓 연결함수 
        var connect = function(){
            //http://localhost:8080/hiwony7933/chat-ws - 자기 컴퓨터에서만 접속
            wsocket = new WebSocket("ws://localhost:8080/hiwony7933/chat-ws")
            //이벤트 핸들러 연결 
            wsocket.addEventListener("open", onOpen);
            wsocket.addEventListener("message", onMessage);

        };

        //message 입력란에서 키보드 이벤트가 발생하면 
        document.getElementById("message").addEventListener("keypress", function(e){
            //enter를 누르면 send() 호출 
            event = e || window.event;
            var keycode=(event.keyCode?event.keyCode:event.which);
            if(keycode == 13) {
                send() 
            }
            event.stopPropagation();
        })

        //버튼들의 이벤트 처리 
        document.getElementById('sendbtn').addEventListener("click",function(e){
            send();
        })
        document.getElementById('enterbtn').addEventListener("click",function(e){
            connect();
        })
        document.getElementById('exitbtn').addEventListener("click",function(e){
            onClose();
        })

    </script>
</body>
</html>

로그인 기능 추가

1.데이터베이스에 접속해서 로그인 처리를 테이블을 생성하고 샘플 데이터를 입력

CREATE TABLE member(
    num number(10),
    userud varchar2(100) UNIQUE,
    userpw varchar2(100) NOT NULL,
    nickname varchar2(100),
    PRIMARY KEY (num)
);

INSERT INTO MEMBER values(1, 'root', '1234', '관리자');
INSERT INTO MEMBER values(2, 'ggangpae', '1234', '두목');
INSERT into MEMBER values(3, 'hiwony', '1234', '나');

COMMIT;

SELECT * FROM MEMBER;

2.테이블과 매핑할 DTO 클래스를 생성

  • com.gmail.hiwony7933.domain.Member
package com.gmail.hiwony7933.domain;

import lombok.Data;

@Data
public class Member {

    private int num;
    private String userid;
    private String userpw;
    private String nickname;
}

3.dao 패키지와 클래스와 데이터베이스 테이블을 매핑시키는 xml 파일을 생성

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.gmail.hiwony7933.domain">
    <class name="Member" table="Member">
        <id name="num" column="num">
        </id>
        <property name="userid" column="userid" />
        <property name="userpw" column="userpw" />
        <property name="nickname" column="nickname" />
    </class>
</hibernate-mapping>

4.Hibernate 설정 파일을 등록하는 코드를 추가 root-context.xml 파일에 추가

                <!-- 추가 -->
                <value>com/gmail/hiwony7933/dao/member.hbm.xml</value>

5.Hibernate 사용을 위한 Dao 클래스를 만들고 로그인 관련 메소드를 작성

  • dao/MemberDao
@Repository
public class MemberDao {
    // 하이버네이트 사용 관련 객체를 주입받기
    @Autowired
    private SessionFactory sessionFactory;

    // 로그인 관련 메소드
    // id를 매개변수로 받아서 일치하는 데이터가 있는지 찾아옵니다.
    public Member login(Member member) {
        // userid가 기본키가 아니라서 
        // SQL을 이용해서 직접 조회 
        List<Member> list = 
                (List<Member>) sessionFactory.getCurrentSession()
                .createSQLQuery("select * from member where userid=:userid")
                .addEntity(Member.class)
                .setString("userid", member.getUserid()).list();

        // 조회된 데이터가 없으면 null을 리턴하고 데이터가 없으면 데이터를 리턴 
        if(list.size() == 0) {
            return null;
        }else {
            return list.get(0);
        }
    }
}

6.home.jsp 파일에 로그인와 로그아웃 링크를 생성

  • 로그인에 성공하면 member 라는 속성에 회원정보를 저장할 것입니다.
  • 로그인 여부는 session에 member 라는 속성에 데이터가 있으면 로그인 된것이고 그렇지 않으면 로그인이 되지 않은 것으로 간주
<!--  if 나 forEach를 사용하기 위한 태그 라이브러리를 설정  -->
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

    <c:if test="${member == null}">
        <a href="login">로그인  </a><br/>    
    </c:if>

    <c:if test="${member != null }">
        ${member.nickname}님 <a href="logout">로그아웃 </a><br/>
    </c:if>

7.login 요청이 GET 방식으로 오면 login 페이지로 포워딩 하도록 메소드를 HomeController에 작성

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인</title>
</head>
<body>
    <form method="post" id="loginform">
        아이디<input type="text" id="userid" name="userid"
        required="required"/><br/>
        비밀번호<input type="text" id="userpw" name="userpw"
        required="required"/><br/>
        <input type="submit" value="전송"/>
    </form>
</body>
</html>

8.MemberService 인터페이스를 만들고 로그인 처리 메소드를 선언

public interface MemberService {
    //로그인 처리 메소드
    public Member login(HttpServletRequest request);
}

9.MemberService 인터페이스를 구현한 MemberServiceImpl 클래스를 만들고 로그인 처리 메소드를 구현

@Service
public class MemberServiceImpl implements MemberService {
    @Autowired
    private MemberDao memberDao;

    @Override
    @Transactional
    public Member login(HttpServletRequest request) {
        Member member = null;

        //파라미터 읽기
        String userid = request.getParameter("userid");
        String userpw = request.getParameter("userpw");

        //member에 인스턴스를 대입하고 userid를 설정
        member = new Member();
        member.setUserid(userid);

        //userid에 해당하는 데이터 찾아오기
        member = memberDao.login(member);
        //userid에 해당하는 데이터가 있다면
        if(member != null) {
            if(member.getUserpw().equals(userpw)) {
                //로그인 성공한 경우는 session에 로그인 정보를 저장
                member.setUserpw(null);
                request.getSession().setAttribute("member", member);
            }else {
                //로그인 실패
                member = null;
            }
        }
        return member;
    }
}

10.HomeController에서 login 요청이 POST 방식으로 오면 처리하는 메소드를 생성

1) 새로운 서비스를 주입받는 코드를 작성

@Autowired
private MemberService memberService;

2) 요청을 처리하는 메소드를 생성

@RequestMapping(value="/login", method=RequestMethod.POST)
    public String login(HttpServletRequest request, Model model,
            RedirectAttributes attrs) {
        //RedirectAttributes는  redirect 할 때 1번만 사용하는 데이터를
        //저장할 수 있는 Spring이 제공하는 클래스

        //서비스의 메소드 호출
        Member member = memberService.login(request);
        //로그인 처리도 redirect로 이동
        if(member == null) {
            //로그인 실패의 경우 msg를 저장하고 login으로 다시 이동
            attrs.addFlashAttribute(
                "msg", "없는 아이디이거나 잘못된 비밀번호입니다.");
            return "redirect:login";
        }else {
            //로그인 성공이면 메인 페이지로 이동
            return "redirect:./";
        }
    }

11.login.jsp 파일에 로그인 실패 메시지를 출력할 영역을 생성

<div>${msg}</div>

12.로그아웃 구현

  • login 된 사실을 을 Session에 데이터를 저장해서 그 데이터를 확인해서 하게 됩니다.
  • logout은 Session에 저장한 데이터를 삭제하면 됩니다.
    • 데이터 1개를 삭제할 때는 session.removeAttribute("key");
    • 세션 전체를 삭제하고자 할 때는 session.invalidate();
    • 일정 시간 동안 세션을 사용하지 않으면 세션을 자동삭제자 하는 경우에는 web.xml 파일에 session-config 설정을 추가하면 되는데 시간의 단위는 분입니다.

1) HomeController 클래스에 logout을 처리하는 메소드 추가

    @RequestMapping(value = "/logout", method = RequestMethod.GET)
    public String logout(HttpServletRequest request, HttpSession session) {
        //Session 객체 만들기
        //request.getSession() 해도 되고 Controller의 요청 처리 메소드에 매개변수로 추가해도 된다. 

        // 세션 초기화 
        session.invalidate();
        // 로그인과 로그아웃도 redirect를 합니다. 
        return "redirect:./";
        }

2) web.xml 파일에 5분동안 세션을 사용하지 않으면 세션을 자동 초기화 해주는 설정을 추가

  • 모바일 페이지는 기렉 설정하는 편이고, 일반 PC용 페이지는 짧게 설정한다.
    <!--  세션을 자동 초기화 해주는 설정  -->
    <session-config>
        <session-timeout>5</session-timeout>

    </session-config>

13.메인 화면의 데이터 출력하는 부분을 수정

1) 메인화면의 데이터 출력 영역의 크기를 제한

  • 출력하는 데이터를 div 태그로 감싸고 div 태그의 높이를 설정한 후 overflow 속성을 이용해서 스크롤 바가 생기도록 만들어주면 됩니다.

    • div로 감싸고

results matching ""

    No results matching ""