Web Socket
1. Web Socket 순수 웹 환경에서 실시간 양방향 통신을 위한 Spec이 바로 ‘웹 소켓(Web Socket)‘ 웹 소켓은 웹 서버와 웹 브라우저가 지속적으로 연결된 TCP 라인을 통해 실시간으 로 데이터를 주고 받을 수 있도록 하는 HTML5의 새로운 사양으로 웹 소켓을 이용 하면 일반적인 TCP소켓과 같이 연결지향 양방향 전이중 통신이 가능 웹 소켓이 필요한 경우 실시간 양방향 데이터 통신이 필요한 경우 많은 수의 동시 접속자를 수용해야 하는 경우 브라우저에서 TCP 기반의 통신으로 확장해야 하는 경우 개발자에게 사용하기 쉬운 API가 필요할 경우 클라우드 환경이나 웹을 넘어 SOA 로 확장해야 하는 경우
1. Web Socket 웹 소켓 역시 일반적인 TCP 소켓 통신처럼 웹 소켓 역시 서버와 클라이언트간 데 이터 교환이 이루어지는 형태로 다른 HTML5 스펙과는 달리 클라이언트 코드만으 로 실행 가능한 코드를 만들 수 없습니다. 웹 소켓 클라이언트 웹 소켓이 제공하는 클라이언트 측 API는 매우 심플한데 기본적인 서버 연결, 데이터 송신, 데이터 수신만 정의하면 나머지는 일반적인 스크립트 로직입니 다. 웹 소켓이 동작하기 위해서 제일 처음 서버와 연결이 되어야 하는데 HTML5 가 제공하는 WebSocket 객체를 통해 서버 연결을 수행합니다. var wSocket = new WebSocket("ws://yourdomain/demo"); 서버와 연결이 되면 이제부터 데이터를 주고 받을 수 있게 되는데 WebSocket 객체의 send 함수로 데이터를 서버로 송신할 수 있습니다 wSocket.send("송신 메시지"); 서버에서 푸시(전송)하는 데이터를 받으려면 message 이벤트를 구현 wSocket.onmessage = function(e){ //매개변수 e를 통해 수신된 데이터를 조 회할 수 있다 open 이벤트: 연결이 설정되면 발생 close 이벤트: 연결이 끊어지면 발생
2. Spring Web Socket 웹 서버 소켓을 일반 Java Web Project의 형태로 구현하게 되면 DispatcherServlet 의 연동이나 스프링 빈 객체를 사용하는 것이 번거롭습니다. 스프링에서는 웹 소켓 서버를 구현하는데 필요한 인터페이스인 WebSocketHandler를 지원하고 있습니다. Spring4의 웹 소켓은 서블릿 3.0이상에서만 동작합니다. 웹 소켓 서버를 구현하기 위한 작업 WebSocketHandler 인터페이스를 구현한 클래스를 생성 DispatcherServlet.xml 파일에 <websocket:handlers> 또는 @EnableWebSocket 어노테이션을 이용해서 앞에서 구현한 클래스의 객체를 웹소켓 앤드포인트로 등록
2. Spring Web Socket WebSocketHandler 인터페이스 void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus): 소켓 연결이 종료 된 후 호출되는 메소드 void afterConnectionEstablished(WebSocketSession session): 연결이 되고 호출되는 메소드 void handleMessage(WebSocketSession session, WebSocketMessage<?> message) : 메시지를 받았을 때 호출되는 메소드 void handleTransportError(WebSocketSession session, Throwable exception): 전송 중 에러가 발생했을 때 호출되는 메소드 boolean supportsPartialMessages():: 대량의 데이터를 나누어 받을 것인지 여 부를 설정하는 메소드 인터페이스를 implements 한 클래스 TextWebSocketHandler AbstractWebSocketHandler
2. Spring Web Socket WebSocketMessage 인터페이스 T getPayLoad(): 실제 메시지 boolean isLast(): 마지막 인지의 여부 WebSocketSession String getId() URI getUri() InetSocketAddress getLocalAddress() InetSocketAddress getRemoteAddress() boolean isOpen() sendMessage(WebSocketMessage) void close()
2. Spring Web Socket Echo 만들기
2. Spring Web Socket Spring 프로젝트를 생성하고 pom.xml 파일에서 스프링 버전(4.0.0 이상) 수정 <org.springframework-version>4.0.4.RELEASE</org.springframework-version> pom.xml 파일에서 spring-websocket 의존성 추가 <dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>${org.springframework-version}</version> </dependency>
2. Spring Web Socket pom.xml 파일에서 servlet과 jsp 버전 변경 <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> </dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.2</version>
2. Spring Web Socket web.xml 파일의 dtd 변경 <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">
2. Spring Web Socket Spring Web Socket 서버를 위한 TextWebSocketHandler를 상속받은 클래스를 생성 package com.pk.websocket.service; 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; public class EchoHandler extends TextWebSocketHandler { @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { System.out.printf("%s 연결 됨\n", session.getId()); }
2. Spring Web Socket @Override protected void handleTextMessage( WebSocketSession session, TextMessage message) throws Exception { System.out.printf("%s로부터 [%s] 받음\n", session.getId(), message.getPayload()); session.sendMessage(new TextMessage("echo: " + message.getPayload())); } public void afterConnectionClosed( WebSocketSession session, CloseStatus status) throws Exception { System.out.printf("%s 연결 끊김\n", session.getId());
2. Spring Web Socket servlet-context.xml 파일에 디폴트 서블릿 설정과 WebSocket 객체를 주소와 바인딩하는 코드를 작성 – websocket 네임 스페이스 추가 <default-servlet-handler/> <beans:bean id="echoHandler" class="com.pk.websocket.service.EchoHandler" /> <websocket:handlers> <websocket:mapping handler="echoHandler" path="/echo-ws"/> </websocket:handlers>
2. Spring Web Socket home.jsp 파일 수정 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ page session="false"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Home</title> </head> <body> <a href="echo">에코</a> </body> </html
2. Spring Web Socket 문자열을 전송하는 페이지 생성(WEB-INF/views/echo.jsp) <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>에코</title> <!-- jquery link 설정 --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script> <script type="text/javascript"> $(document).ready(function() { $('#sendBtn').click(function() { sendMessage(); }); });
2. Spring Web Socket var wsocket; function sendMessage() { wsocket = new WebSocket("ws://localhost:8080/websocket/echo-ws"); wsocket.onmessage = onMessage; wsocket.onclose = onClose; wsocket.onopen = function() { wsocket.send( $("#message").val() ); }; }
2. Spring Web Socket function onMessage(evt) { var data = evt.data; alert("서버에서 데이터 받음: " + data); wsocket.close(); } function onClose(evt) { alert("연결 끊김"); </script> </head> <body> <input type="text" id="message"> <input type="button" id="sendBtn" value="전송"> </body> </html>
2. Spring Web Socket HomeController 클래스에 페이지 이동을 처리하는 메소드 작성 @RequestMapping("echo") public String echo() { return "echo"; }
2. Spring Web Socket
2. Spring Web Socket 채팅을 위한 웹 소켓 서버 클래스 생성(ChatWebSocketHandler) public class ChatWebSocketHandler extends TextWebSocketHandler { private Map<String, WebSocketSession> users = new HashMap<String, WebSocketSession>(); @Override public void afterConnectionEstablished( WebSocketSession session) throws Exception { log(session.getId() + " 연결 됨"); users.put(session.getId(), session); log("1:" + users.values()); }
2. Spring Web Socket @Override public void afterConnectionClosed( WebSocketSession session, CloseStatus status) throws Exception { log(session.getId() + " 연결 종료됨"); users.remove(session.getId()); }
2. Spring Web Socket @Override protected void handleTextMessage( WebSocketSession session, TextMessage message) throws Exception { log(session.getId() + "로부터 메시지 수신: " + message.getPayload()); TextMessage msg = new TextMessage( message.getPayload().substring(4)); for (WebSocketSession s : users.values()) { s.sendMessage(msg); log(s.getId() + "에 메시지 발송: " + message.getPayload()); }
2. Spring Web Socket @Override public void handleTransportError( WebSocketSession session, Throwable exception) throws Exception { log(session.getId() + " 익셉션 발생: " + exception.getMessage()); } private void log(String logmsg) { System.out.println(logmsg);
2. Spring Web Socket 서블릿 설정 파일(servlet-context.xml)에 추가 <websocket:handlers> <websocket:mapping handler="echoHandler" path="/echo-ws"/> <websocket:mapping handler="chatHandler" path="/chat- ws"/></websocket:handlers> <beans:bean id="echoHandler" class="com.pk.websocket.service.EchoHandler" /> <beans:bean id="chatHandler" class="com.pk.websocket.service.ChatWebSocketHandler" />
2. Spring Web Socket 화면에 출력될 jsp 파일 생성(WEB-INT/views/chat.jsp) <%@ page contentType="text/html; charset=UTF-8" trimDirectiveWhitespaces="true"%> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>채팅</title>
2. Spring Web Socket <!-- jquery link 설정 --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script> <script type="text/javascript"> var wsocket; function connect() { wsocket = new WebSocket( "ws://localhost:8080/websocket/chat-ws"); wsocket.onopen = onOpen; wsocket.onmessage = onMessage; wsocket.onclose = onClose; } function disconnect() { wsocket.close(); function onOpen(evt) { appendMessage("연결되었습니다.");
2. Spring Web Socket function onMessage(evt) { var data = evt.data; appendMessage(data); } function onClose(evt) { appendMessage("연결을 끊었습니다."); function send() { var nickname = $("#nickname").val(); var msg = $("#message").val(); wsocket.send("msg:"+nickname+":" + msg); $("#message").val("");
2. Spring Web Socket function appendMessage(msg) { $("#chatMessageArea").append(msg+"<br>"); var chatAreaHeight = $("#chatArea").height(); var maxScroll = $("#chatMessageArea").height() - chatAreaHeight; $("#chatArea").scrollTop(maxScroll); }
2. Spring Web Socket $(document).ready(function() { $('#message').keypress(function(event){ var keycode = (event.keyCode ? event.keyCode : event.which); if(keycode == '13'){ send(); } event.stopPropagation(); }); $('#sendBtn').click(function() { send(); }); $('#enterBtn').click(function() { connect(); }); $('#exitBtn').click(function() { disconnect(); }); </script>
2. Spring Web Socket <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="전송"> </body> </html>
2. Spring Web Socket home.jsp 파일에 chat 요청 링크를 생성 <a href="chat">채팅</a> HomeController에 요청이 왔을 때 페이지로 이동하도록 설정 @RequestMapping(value = "/chat", method = RequestMethod.GET) public String chat(Locale locale, Model model) { return "chat"; }