java development
이번 프로젝트에 webSocket으로 통신하는 기능을 넣기 위해 검색을 해봤는데 많은 글들이 이런저런 용어들에 대해서 두서없이 사용하고 있어서, 관련 내용을 이해하는데 어려움이 있었다. 우여곡절 끝에 어느 정도 전체적인 흐름을 이해하게 되어 다른 개발자들은 고생을 덜 하기를 바라는 마음으로 정리를 해본다. 관련 기술에 대해서 어떻게 발전해왔는지 흐름을 이해하면 프로젝트 진행 시 사용할 기술들을 선택하는데 큰 도움이 될 수 있다.
webSocket은 웹페이지와 서버 간에 실시간 상호작용을 위해 만들어진 스펙이다.
http 규격 자체가 클라이언트에서 서버로의 단방향 통신을 위해 만들어진 방법이다.
webSocket 이전에는 실시간 통신을 위해서 이런 일반 http request에 약간의 트릭을 사용해서 실시간인 것처럼 작동하게 하는 아래와 같은 기술들이 있었다.
클라이언트가 평범한 http request를 서버로 계속 날려서 이벤트 내용을 전달받는 방식이다. 가장 쉬운 방법이지만 클라이언트가 계속적으로 request를 날리기 때문에 클라이언트가 많아지면 서버의 부담이 급증하게 된다. http request connection을 맺고 끊는 것 자체가 부담이 많은 방식이다. 그럼에도 불구하고 클라이언트에서 실시간 정도의 빠른 응답을 기대하기도 어렵다.
클라이언트에서 서버로 일단 http request를 날린다. 이상태로 계속 기다리다가 서버에서 해당 클라이언트로 전달할 이벤트가 있다면 그 순간 response 메시지를 전달하면서 연결이 종료된다. 클라이언트에서는 곧바로 다시 http request를 날려서 서버의 다음 이벤트를 기다리게 되는 방식이다. 일반 polling 방식보다는 서버의 부담이 줄겠지만 클라이언트로 보내는 이벤트들의 시간 간격이 좁다면 polling과 별 차이가 없게 되며, 다수의 클라이언트에게 동시에 이벤트가 발생될 경우에는 곧바로 다수의 클라이언트가 서버로 접속을 시도하면서 서버의 부담이 급증하게 된다.
long polling과 마찬가지로 클라이언트에서 서버로 일단 http request를 날린다. 서버에서 클라이언트로 이벤트를 전달할 때 해당 요청을 끊지 않고 필요한 메시지만 보내기를(flush) 반복하는 방식이다. long polling에 비해 서버에서 메시지를 보내고도 다시 http request 연결을 하지 않아도 되어 부담이 경감될 것으로 보인다.
이와 같은 기술을 사용해본 적은 없지만 long polling, streaming 방식의 경우 서버에서 클라이언트로 메시지를 보낼 수 는 있으나 클라이언트에서 서버로 메시지를 보내는 것은 문제가 있어 보인다.
http://spring.io/blog/2012/05/08/spring-mvc-3-2-preview-techniques-for-real-time-updates
http://ghendra1610.blogspot.kr/2012/05/how-does-asynchronous-web-work.html
이런 꼼수에서 벋어나 정식으로 클라이언트 서버 간 양방향 통신이 가능하게 하기 위해서 HTML5 표준의 일부로 webSocket이 만들어지게 되었다.
webSocket이 기존의 일반 TCP Socket과 다른 점은 최초 접속이 일반 http request를 통해 handshaking과정을 통해 이루어진다는 점이다. http request를 그대로 사용하기 때문에 기존의 80, 443 포트로 접속을 하므로 추가로 방화벽을 열지 않고도 양방향 통신이 가능하고, http 규격인 CORS적용이나 인증 등의 과정을 기존과 동일하게 가져갈 수 있는 것이 장점이다.
이렇게 websocket은 처음부터 웹페이지와 서버 간에 양방향 통신을 위해 만들어진 스펙이다.
여기까지는 아무 문제없고 복잡할 것도 없어 보이는데 아래와 같은 이슈로 인해 생각할 것들이 많아진다.
webSocket 미지원 웹 브라우저: 오래된 버전의 웹 브라우저는 webSocket을 지원하지 않는다.(특히 인터넷 익스플로러 구버전)
웹 브라우저 이외의 클라이언트 지원: 서버의 입장에서 클라이언트는 웹 브라우저뿐만이 아니다.
웹 개발에 있어서 인터넷 익스플로러는 여러 가지로 골칫거리다. 가장 큰 문제는 자동 업데이트가 안된다는 문제이다. 웹 스펙이 계속 발전하고 있기 때문에 웹 브라우저도 항상 최신 버전을 유지해 주지 않으면 새로 만든 스펙으로 개발된 페이지는 작동하지 않게 되는 것이다. 그래서 최근의 웹 브라우저들은 모두 묻지도 따지지도 않고 무조건 최신 버전으로 업그레이드가 자동으로 이루어진다. 이렇게 해서 컴맹이라 할지라도 웹브라우저 업그레이드에 신경 쓰지 않고 최신 스펙으로 작성된 웹 페이지를 여는데 아무 문제가 없다.
그런데 인터넷 익스플로러 과거 버전은 심지어 윈도 업데이트를 해야만 업그레이드가 가능했기 때문에 사람들이 사용하는 인터넷 익스플로러의 버전은 파편화가 심해지게 되었다. 각 버전마다 기능이 다르고, 게다가 인터넷 익스플로러는 그 당시의 표준 규격도 준수하지 않는 등 개발자들 사이에서 악명이 높다.
인터넷 익스플로러 구버전 사용자들은 WebSocket으로 작성된 웹 페이지를 볼 수가 없기 때문에 웹 서비스를 제공하는 사업자 입장에서는 치명적일 수 있다.
그래서 이를 해결하기 위해 나온 기술들이 몇 가지 있는데 원리는 간단하다. 웹페이지가 열리는 브라우저가 webSocket을 지원하면 일반 webSocket 방식으로 동작하고 지원하지 않는 브라우저라면 위에서 설명한 일반 http 스펙을 이용해서 실시간 통신을 흉내 낼 수 있는 방식으로 통신을 하게 해주는 것이다. 아래는 이런 방식으로 만들어진 솔루션이다.
node.js 기반으로 만들어진 기술로 자체 스펙으로 만들어진 socket.io 서버를 만들고 socket.io 클라이언트와 브라우저에 구애받지 않고 실시간 통신이 가능해진다. socket.io는 node.js 기반이기 때문에 모든 코드가 javascript로 작성되어 있다. 서버, 클라이언트 모두 javascript 기반으로 개발하는 것이 기본이다. 그러다 보니 자바 개발자들은 socket.io를 쓸 수 없다. 자바로 개발이 가능하게 해주는 방법이 몇 가지 있긴 한 것 같지만 역시 javascript 기반 솔루션은 javascript로 개발해야 문제 발생을 줄일 수 있을 것이다.
springframework에서 WebSocket을 지원한다. 스프링 매뉴얼에 webSocket 부분을 보면 위와 같은 브라우저 문제를 해결하기 위한 방법으로 SockJS를 솔루션으로 제시한다. 역시 자체 스펙으로 webScoket 미지원 브라우저를 관리한다. 서버 개발시 스프링 설정에서 일반 webSocket으로 통신할지 SockJS 호환으로 통신할지 결정할 수 있다. 클라이언트 쪽은 SockJS client를 통해 서버와 통신한다.
이번 프로젝트에서 구상한 것은 webSocket 서버를 하나 만들고 서로 다른 타입의 각종 클라이언트와 통신할 수 있도록 하는 것이 목표이다.
클라이언트는 웹 페이지(모바일, PC), 안드로이드, 아이폰 등이다. webSocket 자체가 웹 브라우저를 위한 기술이지만 일반 네이티브 클라이언트도 스펙만 준수한다면 통신하지 못할 것이 없다. github를 뒤져 보면 개발자 개인들이 만든 websocket 클라이언트 프로젝트들이 언어 종류별로 몇 가지 있다. (참고로 자바의 경우 알려진 대형 벤더에서 공식적으로 만들어진 것은 찾지 못했다.)
이걸로 서버와 통신하면 간단할 것 같지만 위에서 본 내용과 같이 실제로는 구버전 웹 브라우저 때문에 순수 webSocket API 보다 이걸 자체 스펙으로 다시 감싼 다른 솔루션(socket.io, sockjs...)의 방식을 이용해서 서버를 만들어야 한다는 것이 문제다. socket.io, sockjs 등 모드 웹브라우저를 위한 javascript 클라이언트가 기본이기 때문에 다른 언어 지원은 미약하고 sockjs java 클라이언트의 경우 spring 기반으로 만들어져 있어서 안드로이드에서 실행시키기는 부적합하거나.. 하는 등 애로 사항이 많이 있는 상태이다.
위와 같은 내용을 이해하고 최종 결정된 개발 스펙은 다음과 같다.
1. 서버는 spring을 이용해서 STOMP 규격으로 개발한다.
1.1 두개의 접속 경로를 열어서 하나는 순수 stomp 규격으로 오픈
1.2 다른 하나는 sockJS + stomp 규격으로 오픈
2.웹 페이지에서는 sockJS javascript 클라이언트를 이용해서 서버와 접속한다.
sockJS를 이용해서 websocket을 지원하지 않는 브라우저까지 커버를 한다.
3. android와 IOS에서는 개인 개발자들이 만들어놓은 stomp 규격의 클라이언트 라이브러리를 이용해서 서버와 접속한다.
서버에서는 stomp와 sockJS + stomp로 두개를 오픈하고 각각의 경로로 들어온 같은 message에 대해서 동일한 소스로 message 처리가 가능하다.
http://www.slideshare.net/SpringCentral/deep-dive-intospringwebsockets-sergialmarspringone2gx2014
http://www.slideshare.net/sergialmar/websockets-with-spring-4