웹 소켓 기반 원격 실시간 로그 브라우저에서 장치 실행 로그 보기

12678 단어
본고는 웹소켓을 바탕으로 이루어진 원격 실시간 로그 시스템을 소개하고 브라우저를 통해 원격 모바일 장치의 실시간 실행 로그를 볼 수 있다.
시스템은 다음과 같은 세 가지 섹션으로 구성됩니다.
1. 서버: 모바일 기기와 브라우저와 웹소켓 연결을 구축하여 모바일 기기 웹소켓에서 읽은 실시간 로그를 해당 브라우저의 웹소켓에 전송
2. 브라우저 로그 보기 페이지: 서버와 웹소켓 연결을 맺고 웹소켓을 통해 지정된 장치의 실시간 실행 로그를 수신하여 표시합니다.
3. 모바일 장치: 서버와 웹소켓 연결을 만들고 실행 로그를 웹소켓 연결을 통해 서버에 업로드
서버 측 구현
Tomcat 7.0.27은 웹소켓을 지원하기 시작했습니다.본고의 서버 사이드 서브렛 프로그램은 Tomcat에 구축된 것이다.Tomcat 위에서 웹소켓을 지원하는 servlet에 대해서는 참고할 수 있습니다
Tomcat Websocket How-To
Tomcat 기반 웹소켓
서버 서브렛 소스:
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;


import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;


import org.apache.catalina.websocket.MessageInbound;
import org.apache.catalina.websocket.StreamInbound;
import org.apache.catalina.websocket.WebSocketServlet;
import org.apache.catalina.websocket.WsOutbound;


/**
 * Servlet implementation class WebLogcat
 */
@WebServlet("/WebLogcat")
public class WebLogcat extends WebSocketServlet {
	private static final long serialVersionUID = 1L;

	
	private final Set connections =
            new CopyOnWriteArraySet();
	
	private final Map devices =
			new ConcurrentHashMap();
	
	private final Map browsers =
			new ConcurrentHashMap();
	

	@Override
	protected StreamInbound createWebSocketInbound(String arg0,
			HttpServletRequest arg1) {
		String id = arg1.getParameter("id");
		String type = arg1.getParameter("type");
		
		if( id != null && type != null ) {
			if( type.equalsIgnoreCase("device") ) {
				return new DeviceMessageInbound( id );
			} else if( type.equalsIgnoreCase("browser") ) {
				return new BrowserMessageInbound( id );
			}
		} 
		
		// return NULL will lead to Exception
		return new LogMessageInbound();
	}
	
	private final class DeviceMessageInbound extends MessageInbound {
        private String _id;
        
        DeviceMessageInbound(String id) {
        	_id = id;
        }
        
		@Override
		protected void onClose(int status) {
			// remove me from device hash map
			devices.remove(_id);
			super.onClose(status);
		}

		@Override
		protected void onOpen(WsOutbound outbound) {
			// add me to device hash map
			devices.put(_id, this);
			super.onOpen(outbound);
		}

		@Override
		protected void onBinaryMessage(ByteBuffer arg0) throws IOException {
			
		}

		@Override
		protected void onTextMessage(CharBuffer arg0) throws IOException {
			// broadcast to all browser with the same id as me
			String message = new String(arg0.array());
			Set list = browsers.get( _id );
			if( list != null ) {
				for (BrowserMessageInbound connection : list) {
		            try {
		            	CharBuffer buffer = CharBuffer.wrap(message);
		                connection.getWsOutbound().writeTextMessage(buffer);
		            } catch (IOException ignore) {
		                // Ignore
		            }
		        }
			}
		}
		
	}
	
	private final class BrowserMessageInbound extends MessageInbound {

		private String _id;
		
		@Override
		protected void onClose(int status) {
			synchronized( browsers ) {
				 Set list = browsers.get( _id );
				 if( list != null ) {
					 list.remove(this);
					 if( list.isEmpty() ) {
						 browsers.remove(_id);
					 }
				 }
			}
			super.onClose(status);
		}

		@Override
		protected void onOpen(WsOutbound outbound) {
			synchronized( browsers ) {
				if( browsers.containsKey(_id) ) {
					browsers.get(_id).add(this);
				} else {
					Set list = new CopyOnWriteArraySet();
					list.add(this);
					browsers.put(_id, list);
				}
			}
			super.onOpen(outbound);
		}

		BrowserMessageInbound(String id) {
		    _id = id;	
		}
		
		@Override
		protected void onBinaryMessage(ByteBuffer arg0) throws IOException {
			
		}

		@Override
		protected void onTextMessage(CharBuffer arg0) throws IOException {
			
		}
		
	}
	
	private final class LogMessageInbound extends MessageInbound {

		@Override
		protected void onClose(int status) {
			connections.remove(this);
			super.onClose(status);
		}

		@Override
		protected void onOpen(WsOutbound outbound) {
			super.onOpen(outbound);
			connections.add(this);
		}

		@Override
		protected void onBinaryMessage(ByteBuffer arg0) throws IOException {
			
		}

		@Override
		protected void onTextMessage(CharBuffer arg0) throws IOException {
			
		}
		
	}
}

웹소켓을 지원하는 servlet을 실현하려면 웹소켓 servlet을 계승하고createWebSocketInbound 함수를 실현하여 웹소켓 연결을 대표하는 대상을 되돌려야 합니다.
본고는 프로그램에서 요청에 전달된 id와 type 파라미터를 수신합니다. iid는 장치의 id를 대표하거나 브라우저가 실시간 로그를 보려는 장치의 id를 나타냅니다.type은 연결 요청이 장치에서 왔는지 브라우저에서 왔는지 나타냅니다.서브렛은 type의 값에 따라 대응하는 형식의 웹소켓 연결을 만듭니다.연결 요청이 장치 장치에서 온 경우 DeviceMessageInbound를 만듭니다.장치 요청이 브라우저 브라우저에서 온 경우 BrowserMessageInbound를 만듭니다.
DeviceMessageInbound 및 BrowserMessageInbound 모두 MessageInbound에서 상속
DeviceMessageInbound의 경우
onOpen()에서 장치 맵 테이블에 자신을 추가합니다.
onClose()에서 장치 맵 테이블에서 제거합니다.
onTextMessage () 는 Servlet에서 장치로부터 텍스트 메시지를 받았습니다.Servlet은 장치 id에 따라 이 장치의 실시간 로그를 감청하고 있는 모든 브라우저를 찾아 텍스트 메시지를 브라우저에 전달합니다.
BrowserMessageInbound의 경우
onOpen () 에서 id를 감청하는 브라우저 목록에 자신을 추가합니다.같은 장치 id를 대상으로 여러 브라우저가 동시에 로그를 볼 수 있도록 합니다.따라서 id에 대응하는 브라우저 연결이 있는지 먼저 판단해야 합니다.존재하지 않으면 목록을 만들고 브라우저 맵에 삽입합니다.이미 존재하면 id에 따라 목록을 찾아 이 목록에 추가합니다.
onClose에서 id를 통해 목록을 찾으면 목록에서 제거합니다.제거한 후 목록이 비어 있으면 브라우저 맵에서 목록을 제거합니다.
브라우저 측 구현



    WebLogcat
    
    
        var Chat = {};

        Chat.socket = null;

        Chat.connect = (function(host) {
            if ('WebSocket' in window) {
                Chat.socket = new WebSocket(host);
            } else if ('MozWebSocket' in window) {
                Chat.socket = new MozWebSocket(host);
            } else {
                Console.log('Error: WebSocket is not supported by this browser.');
                return;
            }

            Chat.socket.onopen = function () {
                Console.log('Info: WebSocket connection opened.');
                document.getElementById('chat').onkeydown = function(event) {
                    if (event.keyCode == 13) {
                        Chat.sendMessage();
                    }
                };
            };

            Chat.socket.onclose = function () {
                document.getElementById('chat').onkeydown = null;
                Console.log('Info: WebSocket closed.');
            };

            Chat.socket.onmessage = function (message) {
                Console.log(message.data);
            };
        });

        Chat.initialize = function() {
            if (window.location.protocol == 'http:') {
                Chat.connect('ws://' + window.location.host + '/WebLogcat/WebLogcat?id=fv0557&type=browser');
            } else {
                Chat.connect('wss://' + window.location.host + '/WebLogcat/WebLogcat?id=fv0557&type=browser');
            }
        };

        Chat.sendMessage = (function() {
            var message = document.getElementById('chat').value;
            if (message != '') {
                Chat.socket.send(message);
                document.getElementById('chat').value = '';
            }
        });

        var Console = {};

        Console.log = (function(message) {
            var console = document.getElementById('console');
            var p = document.createElement('p');
            p.style.wordWrap = 'break-word';
            p.innerHTML = message;
            console.appendChild(p);
            while (console.childNodes.length > 25) {
                console.removeChild(console.firstChild);
            }
            console.scrollTop = console.scrollHeight;
        });

        Chat.initialize();

    




这其实是一个HTML5的页面,同样是需要部署到服务器上的。只不过用户通过浏览器访问,在浏览器上运行而已。

这个其实修改自Tomcat自带的一个Websocket的例子,叫Chat。里面关于device id是hard code为fv0557。


设备端实现

本文中的设备指的是一个嵌入式设备,是运行linux的ARM系统。所以选用libwebsocket来实现一个websocket的客户端。


#include 
#include 


static int was_closed = 0;
static int deny_deflate;
static int deny_mux;


/* dumb_increment protocol */

static int
callback_weblogcat(struct libwebsocket_context *this,
			struct libwebsocket *wsi,
			enum libwebsocket_callback_reasons reason,
					       void *user, void *in, size_t len)
{
    unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + 4096 +
						  LWS_SEND_BUFFER_POST_PADDING];
	int l;
	

	switch (reason) {
	case LWS_CALLBACK_CLOSED:
		fprintf(stderr, "LWS_CALLBACK_CLOSED
"); was_closed = 1; break; case LWS_CALLBACK_CLIENT_ESTABLISHED: /* * LWS_CALLBACK_CLIENT_WRITEABLE will come next service */ fprintf(stderr, "LWS_CALLBACK_CLIENT_ESTABLISHED
"); libwebsocket_callback_on_writable(this, wsi); break; case LWS_CALLBACK_CLIENT_RECEIVE: ((char *)in)[len] = '\0'; fprintf(stderr, "rx %d '%s'
", (int)len, (char *)in); break; case LWS_CALLBACK_CLIENT_WRITEABLE: l = sprintf((char *)&buf[LWS_SEND_BUFFER_PRE_PADDING], "c #%06X %d %d %d;", (int)random() & 0xffffff, (int)random() % 500, (int)random() % 250, (int)random() % 24); libwebsocket_write(wsi, &buf[LWS_SEND_BUFFER_PRE_PADDING], l, LWS_WRITE_TEXT); /* get notified as soon as we can write again */ libwebsocket_callback_on_writable(this, wsi); sleep(3); break; /* because we are protocols[0] ... */ case LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED: if ((strcmp(in, "deflate-stream") == 0) && deny_deflate) { fprintf(stderr, "denied deflate-stream extension
"); return 1; } if ((strcmp(in, "x-google-mux") == 0) && deny_mux) { fprintf(stderr, "denied x-google-mux extension
"); return 1; } break; default: break; } return 0; } static struct libwebsocket_protocols protocols[] = { { NULL, callback_weblogcat, 0, }, { /* end of list */ NULL, NULL, 0 } }; int main( int argc, char* argv[]) { struct libwebsocket_context *context; struct libwebsocket *wsi_weblogcat; const char *address = "192.168.xxx.xxx"; int port = 8080; int use_ssl = 0; int n = 0; context = libwebsocket_create_context(CONTEXT_PORT_NO_LISTEN, NULL, protocols, NULL, NULL, NULL, NULL, -1, -1, 0, NULL); if (context == NULL) { fprintf(stderr, "Creating libwebsocket context failed
"); return 1; } /* create a client websocket using weblogcat protocol */ wsi_weblogcat = libwebsocket_client_connect(context, address, port, use_ssl, "/WebLogcat/WebLogcat?id=fv0557&type=device", address, address, protocols[0].name, -1); if (wsi_weblogcat == NULL) { fprintf(stderr, "libwebsocket weblogcat connect failed
"); return -1; } fprintf(stderr, "Websocket weblogcat connections opened
"); n = 0; while (n >= 0 && !was_closed) { n = libwebsocket_service(context, 1000); if (n < 0) continue; } fprintf(stderr, "Exiting
"); libwebsocket_context_destroy(context); return 0; }

이것도 libwebsocket에서 test-client를 수정합니다.c 파일.이것은 단지 시뮬레이션이 일정 시간마다 서버에 데이터를 보내는 것입니다.장치에서 웹 소켓을 실행하는 클라이언트를 테스트하는 데모는 데모일 뿐입니다.진정한 실시간 로그 시스템을 실현하려면, 이 프로세스는 시스템의 다른 프로세스에 로그를 보내는 인터페이스를 제공해야 한다.그리고 이 프로세스는 웹소켓을 통해 서버에 전송됩니다.또는 Android와 같이 시스템의 프로그램은 로그를 로그 장치에 기록하고 이 프로세스는 로그 장치에서 데이터를 읽고 웹소켓을 통해 서버에 보냅니다.

좋은 웹페이지 즐겨찾기