웹 소켓(Web Socket) 이란

웹소켓(WebSocket)은 하나의 TCP 접속에 전이중 통신 채널을 제공하는 컴퓨터 통신 프로토콜이다. 웹소켓 프로토콜은 2011년 IETF에 의해 RFC 6455로 표준화되었으며 웹 IDL의 웹소켓 API는 W3C에 의해 표준화되고 있다. (위키피디아)

더 쉽게 말하면 웹 소켓은 웹 서버와 웹 브라우저가 연결된 네트워크(TCP)를 통해 실시간으로 데이터를 주고 받는 기술이며 HTML5에 정의된 사양이다. 일반적으로 웹소켓은 일반 HTML 의 요청/응답과 비교하여 약 50배 정도의 성능 향상이 있는 것으로 보고되고 있다.

[출처] https://github.com/jwvg0425/ProjectArthas/wiki/Network-(TCP-IP-기본)

목적 및 작업 계획

동일한 지도 화면을 공유하여 작업상황을 모니터링 하고, 상황에 따른 지휘 통제를 위해 동일한 작업을 그룹 내에서 공유

일반적인 화상회의 솔루션이 현재 화면의 메모리덤프 형식으로 동작하여 래스터방식의 현재 화면 상태 만을 전송함. 이에 지도 위에서 일어나는 편집 (점, 선, 면) 작업의 저장 및 활용에 제약이 존재하여 GIS 전자지도가 제공하는 기능의 활용에 애로가 있음

  1. 이에 대한 해결 방안으로써 웹 소켓을 이용하여 작업자가 지도 위에서 생성한 객체 정보를 그룹 내 다른 사용자에게 전달
  2. Sender가 보낸 Message를 수신한 사용자 브라우저의 지도 위에 시각화
  3. 앞의 과정을 통해 그룹 내 모든 사용자는 동일한 GIS 실행 화면을 공유


실행 환경 설정

1. Server : Linux Container, 가상서버 위에 설치되는 DBMS
2. WAS(Web Application Server) : Apache Tomcat

 

구성 요약

1. Socket 서버 : 웹 브라우저와 메시지 교환

      • Socket의 기본형태는 위 소스와 같이 Open, Close, Error, Message로 구분된다. 
      • 51행 : @onMessage를 통해 GIS Sender로부터 받은 좌표정보를 Receiver 페이지로 전달
      • 61행 : Recevier 페이지로 전달
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package egovframework.map.service;
 
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
 
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
 
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Controller;
 
import egovframework.map.web.Coordinate;
 
@Controller
@ServerEndpoint("/websocket")
public class WSocketServer {
 
    static List<Session> sessionUsers = Collections.synchronizedList(new ArrayList<Session>());
    
 
    @OnOpen
    public void onOpen(Session userSession) {
        System.out.println("Open Connection!...");
        sessionUsers.add(userSession);
    }
 
    @OnClose
    public void onClose(Session userSession) {
        System.out.println("Close Connection!...");
        sessionUsers.remove(userSession);
    }
 
    @OnError
    public void onError(Throwable e) {
        e.printStackTrace();
    }
    
    @OnMessage
    public void onMessage(String message, Session userSession) throws IOException {
        System.out.println(message);
        
        Iterator<Session> itr = sessionUsers.iterator();
        
        while (itr.hasNext()) {
            try {
                Session session = itr.next();
                
                session.getBasicRemote().sendText(message);
                
            } catch (IOException e) {                        
                e.printStackTrace();
            }
        }
        
    }
 
}
 
cs

 

2. GIS Sender

GIS Sender는 화면공유를 할 수 있는 관리자 페이지로 지도상에서 선택한 위치정보를 포인트 형태로 공유할 수 있게 정보를 전송하는 페이지다. GIS Sender 페이지의 소스는 아래와 같다.

      • GIS Sender의 <scrip></script>부분은 GIS Sender 페이지 로딩 부분과 WebSocket 통신을 통해 정보를 공유하는 부분 이렇게 2개로 구분된다. 
      • 32행 ~ 106행 <scrip></script> : 지도 로딩 및 클릭 이벤트  
      • 124행 ~ 155행 <scrip></script> :  WebSocket 서버 통신 및 Message 등 설정
      • 126행 : WebSocket 서버 접속

141행 : WebSocket으로 GeoJson형태의 위치정보 전송 및 Textarea에 좌표정보 표출

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ page session="false" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Tomcat WebCommander</title>
<link type="text/css" rel="stylesheet" href="<c:url value='/css/openlayers/ol.css'/>" />
<link type="text/css" rel="stylesheet" href="<c:url value='/css/bootstrap/bootstrap.min.css'/>" />
 
<script src="<c:url value='/js/openlayers/ol.js'/>"></script>
<script src="<c:url value='/js/jquery/jquery-1.12.4.min.js'/>"></script>
<script src="<c:url value='/js/bootstrap/bootstrap.min.js'/>"></script>
 
<style>
#map {
    height: 550px;
    width: 100%;
}
 
#echoText{
    height: 200px;
    width: 100%;
}
#message{
    width: 300px;
    margin: 10px;
}
</style>
 
</head>
<script type="text/javascript">
var map;
var hdms;
var geojsonStr;
var iconStyle = new ol.style.Style({
    image: new ol.style.Icon(({
        anchor: [0.580],
        anchorXUnits: 'fraction',
        anchorYUnits: 'pixels',
        src: '/images/common/logo.png',
    size: [150150],
    scale: 0.5
    }))
});
    
var writer = new ol.format.GeoJSON();
 
var vectorSource = new ol.source.Vector();
 
var vectorLayer = new ol.layer.Vector({
    source: vectorSource
});
 
$(document).ready(function(){
    
    var extent = [0.82683994988722991.296765637000096168.1705899498872449.63660938700009];
    
    map = new ol.Map({
        layers: [
              new ol.layer.Tile({
                source: new ol.source.OSM({
                    
                })
              }),
              vectorLayer
        ],
        target: 'map',
        interactions: ol.interaction.defaults({
            dragPan: false
        }),
        view: new ol.View({
            projection : 'EPSG:4326',     
            center: ol.extent.getCenter(extent),
              zoom: 3,
              maxZoom: 3,
              minZoom:3
        })
    });
    
    map.on('click'function(evt) {
 
        vectorSource.clear();
        
        var coordinate = evt.coordinate;
        hdms = ol.coordinate.toStringHDMS(coordinate);
        
        var coordLon = coordinate[0];
        var coordLat = coordinate[1];
        
        var iconFeature = new ol.Feature({
            geometry: new ol.geom.Point([coordLon,coordLat])
          });
        
        iconFeature.setStyle(iconStyle);
        
        vectorSource.addFeature(iconFeature);
    
        $('#message').attr('value',coordinate);        
        
        geojsonStr = writer.writeFeatures(vectorSource.getFeatures());
        
    });
 
});
</script>
<body>
    <nav class="navbar navbar-dark bg-primary">
        <div class="container-fluid">
            <div class="navbar-header">
                <h1>GIS Sender </h1>
            </div>
        </div>
    </nav>
    <h1>Web Socket을 활용한 GIS 화면 공유 </h1>
    <form>
        <input id="message" type="text">
        <input onclick="wsSendMessage();" value="위치 전송" type="button">
        <input onclick="wsCloseConnection();" value="Disconnect" type="button">
    </form>
    <div id="map"></div>
    <br>
    <textarea id="echoText" rows="500" cols="700"></textarea>
    <script type="text/javascript">
        //웹소켓 서버 접속
        var webSocket = new WebSocket("ws://mapview.paas.lx.or.kr/websocket");
        
        var echoText = document.getElementById("echoText");
        echoText.value = "";
        
        var message = document.getElementById("message");
        
        webSocket.onopen = function(message){ wsOpen(message);};
        webSocket.onclose = function(message){ wsClose(message);};
        webSocket.onerror = function(message){ wsError(message);};
        
        
        function wsOpen(message){
            echoText.value += "Connected ... \n";
        }
        function wsSendMessage(){
            webSocket.send(geojsonStr);
              echoText.value += "GIS sender dent the location information to the Recevier : " + hdms + "\n";
        }
        function wsCloseConnection(){
            webSocket.close();
        }
        function wsClose(message){
            echoText.value += "Disconnect ... \n";
        }
 
        function wserror(message){
            echoText.value += "Error ... \n";
        }
    </script>
    <footer class="page-footer font-small blue">
 
          <div class="footer-copyright text-center py-3">
            
            <a href="https://progworks.tistory.com">
                <img alt="logo" width="70px" height="70px" src="/images/common/logo.png">
            </a>
            PROGWORKS - https://progworks.tistory.com/
            
          </div>
    </footer>
</body>
</html>
 
cs

3. Receiver

Receiver는 GIS Sender를 통해 보내진 위치정보를 사용자가 공유 받는 페이지이다.

      • Receiver 페이지는 GIS Sender 페이지와 유사하지만 별도의 기능이 없이 WebSocket을 통해 Sender페이지에서 받은 위처정보만을 지도상에 보여준다.
      • Receiver 페이지 또한 지도 로딩과 WebSocket 통신부분으로 2개의 <scrip></script>부분이 존재한다.
      • 28행 ~ 76행 <script></script> : 지도 로딩
      • 90행 ~ 137행 <script></script> : WebSocket 서버 통신, message 및 icon 생성
      • 92행 : WebSocket 접속
      • 110행 : WebSocket을 통해 위치정보를 전달받아 function addFeature() 실행 및 Textarea에 좌표정보 표출
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ page session="false" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Tomcat WebSocket</title>
<link type="text/css" rel="stylesheet" href="<c:url value='/css/openlayers/ol.css'/>" />
<link type="text/css" rel="stylesheet" href="<c:url value='/css/bootstrap/bootstrap.min.css'/>" />
 
<script src="<c:url value='/js/openlayers/ol.js'/>"></script>
<script src="<c:url value='/js/jquery/jquery-1.12.4.min.js'/>"></script>
<script src="<c:url value='/js/bootstrap/bootstrap.min.js'/>"></script>
 
<style>
#map {
    height: 550px;
    width: 100%;
}
 
#echoText{
    height: 200px;
    width: 100%;
}
</style>
 
</head>
<script type="text/javascript">
var map;
var hdms;
 
var iconStyle = new ol.style.Style({
    image: new ol.style.Icon(({
        anchor: [0.580],
        anchorXUnits: 'fraction',
        anchorYUnits: 'pixels',
        src: '/images/common/logo.png',
    size: [150150],
    scale: 0.5
    }))
});
var vectorSource = new ol.source.Vector();
 
var vectorLayer = new ol.layer.Vector({
    source: vectorSource
});
 
$(document).ready(function(){
    var extent = [0.82683994988722991.296765637000096168.1705899498872449.63660938700009];
    
    var proj = new ol.proj.Projection({
        code: 'EPSG:4326',
        units: 'm',
        extent: extent
    });
    
    map = new ol.Map({
        layers: [
              new ol.layer.Tile({
                source: new ol.source.OSM()
              }),
              vectorLayer
        ],
        target: 'map',
        interactions: ol.interaction.defaults({
            dragPan: false
        }),
        view: new ol.View({
            projection : 'EPSG:4326',     
            center: ol.extent.getCenter(extent),
            zoom: 3,
              maxZoom: 3,
              minZoom:3
        })
    });
});
</script>
<body>
    <nav class="navbar navbar-dark bg-primary">
        <div class="container-fluid">
            <div class="navbar-header">
                <h1>Receiver </h1>
            </div>
        </div>
    </nav>
    <h1>Web Socket을 활용한 GIS 화면 공유 </h1>    
    <div id="map"></div>
    <br>
    <textarea id="echoText" rows="500" cols="700"></textarea>
    <script type="text/javascript">
          //웹소켓 서버 접속        
        var webSocket = new WebSocket("ws://mapview.paas.lx.or.kr/websocket");
          
        var echoText = document.getElementById("echoText");
        echoText.value = "";
        
        var message = document.getElementById("message");
        
        webSocket.onopen = function(message){ wsOpen(message);};
        webSocket.onmessage = function(message){ wsGetMessage(message);};
        webSocket.onclose = function(message){ wsClose(message);};
        webSocket.onerror = function(message){ wsError(message);};
        
        function wsOpen(message){
            echoText.value += "Connected ... \n";
        }
        function wsCloseConnection(){
            webSocket.close();
        }
        function wsGetMessage(message){
            addFeature(message.data);
              echoText.value += "Location information received from to the GIS Sender : " + hdms + "\n";
        }
        function wsClose(message){
            echoText.value += "Disconnect ... \n";
        }
 
        function wserror(message){
            echoText.value += "Error ... \n";
        }
        
        function addFeature(ft){
            
            vectorSource.clear();
            
            var selectFt = ft;
            var writer = new ol.format.GeoJSON();
            var features = writer.readFeatures(selectFt);
            
              for (var i = 0; i < features.length; i++) {
                  features[i].setStyle(iconStyle)
                  coordInfo = features[i].getGeometry().getCoordinates()
                  hdms = ol.coordinate.toStringHDMS(coordInfo);
                  vectorSource.addFeature(features[i]);
            }
        }
    </script>
    <footer class="page-footer font-small blue">
 
          <div class="footer-copyright text-center py-3">
            
            <a href="https://progworks.tistory.com">
                <img alt="logo" width="70px" height="70px" src="/images/common/logo.png">
            </a>
            PROGWORKS - https://progworks.tistory.com/
            
          </div>
    </footer>
</body>
</html>
 
cs

 

실행 및 사용법

실행을 시키기 위해 GIS Sender 페이지와 Receiver 페이지에 접속한다. 테스트를 진행하기 위해 Receiver 페이지는 크롬, Microsoft Edge, IE 3곳에서 실행한다.
(Sender 한 명이 지도 상에 위치를 선택하면 선택한 위치가 사이트에 접속되어 있는 모든 사용자에게 동일하게 나타남)

    1. GIS Sender 및 Receiver 페이지(크롬, Microsoft Edge, IE)에 접속을 한다. 
    2. GIS Sender 페이지에서 지도상에 임의의 지점을 클릭한다.
    3. 좌측 상단 <input id="message" type="text">부분에 좌표정보가 출력되는지 확인한다. 
    4. 좌표 전송 버튼을 클릭한다. <textarea id="echoText" rows="500" cols="700"></textarea>부분에 아래와 같은 문구와 함께 좌표정보가 나타나면 정상작동된것이다.
      ex) "GIS sender sent the location information to the Recevier : 48° 03′ 21″ N 69° 06′ 29″ E"
    5. Receiver 페이지에서 좌표정보를 받아 지도상에 마커 및 <textarea id="echoText" rows="500" cols="700"></textarea>부분에 좌표정보가 출력되는지 확인한다.
      ex) "Location information received from to the GIS Sender : 48° 03′ 21″ N 69° 06′ 29″ E"

 

 

적용 및 응용 분야

  1. 실시간 상황 공유 기반 지휘 관제
  2. 폴리곤, 선 등 동일한 객체를 다중 편집자가 동시 작업
  3. 화면 덤프 방식으로 공유하는 영상회의 솔루션과 함께 사용되어 GIS 벡터 형식 자료 공유

 

결론

1. HTML5 웹 소켓을 통해 GIS 기반 위치 Push, 동일 객체 다중 편집 등이 가능함을 확인
2. 보통 채팅 등의 용도로 이용되는 웹 소켓이 GIS 부문 응용 앱의 기능을 더욱 풍부하게 할 수 있음을 확인

+ Recent posts