/* * * Copyright (C) 2012-2014 R T Huitema. All Rights Reserved. * Web: www.42.co.nz * Email: robert@42.co.nz * Author: R T Huitema * * This file is part of the signalk-server-java project * * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Adapted from https://github.com/rhq-project/rhq-metrics */ package nz.co.fortytwo.signalk.server; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.DatagramPacket; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioDatagramChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.DelimiterBasedFrameDecoder; import io.netty.handler.codec.Delimiters; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import io.netty.util.CharsetUtil; import io.netty.util.concurrent.Future; import java.net.InetSocketAddress; import nz.co.fortytwo.signalk.util.ConfigConstants; import nz.co.fortytwo.signalk.util.Util; import org.apache.camel.Exchange; import org.apache.camel.Processor; import org.apache.camel.component.websocket.WebsocketConstants; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class NettyServer implements Processor{ private final EventLoopGroup group; private final EventLoopGroup workerGroup; private static Logger logger = LogManager.getLogger(NettyServer.class); private static final StringDecoder DECODER = new StringDecoder(); private static final StringEncoder ENCODER = new StringEncoder(); private CamelNettyHandler forwardingHandler = null; private CamelUdpNettyHandler udpHandler = null; private Channel udpChannel = null; private int tcpPort = Util.getConfigPropertyInt(ConfigConstants.TCP_PORT); private int udpPort = Util.getConfigPropertyInt(ConfigConstants.UDP_PORT); private String outputType; /** * @param configDir * @throws Exception */ public NettyServer(String configDir,String outputType) throws Exception { group = new NioEventLoopGroup(); workerGroup = new NioEventLoopGroup(); this.outputType=outputType; Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { @Override public void run() { shutdownServer(); } })); } public void run() throws Exception{ forwardingHandler = new CamelNettyHandler(outputType); // The generic TCP socket server ServerBootstrap skBootstrap = new ServerBootstrap(); skBootstrap.group(group, workerGroup).channel(NioServerSocketChannel.class).localAddress(tcpPort) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline pipeline = socketChannel.pipeline(); pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter())); pipeline.addLast(DECODER); pipeline.addLast(ENCODER); pipeline.addLast(forwardingHandler); logger.info("Signal K "+outputType+" Connection over TCP from:" + socketChannel.remoteAddress()); } }); final ChannelFuture signalkTcpFuture = skBootstrap.bind().sync(); logger.info("Server listening on TCP " + signalkTcpFuture.channel().localAddress()); signalkTcpFuture.channel().closeFuture(); if(udpPort>0){ udpHandler = new CamelUdpNettyHandler(outputType); Bootstrap udpBootstrap = new Bootstrap(); udpBootstrap.group(group).channel(NioDatagramChannel.class) .option(ChannelOption.SO_BROADCAST, true) .handler(udpHandler); udpChannel = udpBootstrap.bind(tcpPort-1).sync().channel(); logger.info("Server listening on UDP " + udpChannel.localAddress()); } } public void shutdownServer() { logger.info("Stopping ptrans..."); Future<?> groupShutdownFuture = group.shutdownGracefully(); Future<?> workerGroupShutdownFuture = workerGroup.shutdownGracefully(); try { groupShutdownFuture.sync(); } catch (InterruptedException ignored) { } try { workerGroupShutdownFuture.sync(); } catch (InterruptedException e) { e.printStackTrace(); } logger.info("Stopped"); } @Override public void process(Exchange exchange) throws Exception { logger.debug("Received msg : "+exchange.getIn().getBody()); String msg = exchange.getIn().getBody().toString(); if(msg!=null){ //get the session String session = exchange.getIn().getHeader(WebsocketConstants.CONNECTION_KEY, String.class); if(WebsocketConstants.SEND_TO_ALL.equals(session)){ //udp if(udpPort>0 && udpChannel!=null&& udpChannel.isWritable()){ for(InetSocketAddress client:udpHandler.getSessionList().values()){ if(logger.isDebugEnabled())logger.debug("Sending udp: "+exchange.getIn().getBody()); //udpCtx.pipeline().writeAndFlush(msg+"\r\n"); udpChannel.writeAndFlush(new DatagramPacket( Unpooled.copiedBuffer(msg+"\r\n", CharsetUtil.UTF_8),client)); if(logger.isDebugEnabled())logger.debug("Sent udp to "+client); } } //tcp for(String key: forwardingHandler.getContextList().keySet()){ ChannelHandlerContext ctx = forwardingHandler.getChannel(key); if(ctx!=null&& ctx.channel().isWritable())ctx.pipeline().writeAndFlush(msg+"\r\n"); } }else{ //udp if(udpPort>0 && udpChannel!=null&& udpChannel.isWritable()){ final InetSocketAddress client = udpHandler.getSessionList().get(session); if(logger.isDebugEnabled())logger.debug("Sending udp: "+exchange.getIn().getBody()); //udpCtx.pipeline().writeAndFlush(msg+"\r\n"); udpChannel.writeAndFlush(new DatagramPacket( Unpooled.copiedBuffer(msg+"\r\n", CharsetUtil.UTF_8),client)); if(logger.isDebugEnabled())logger.debug("Sent udp for session: "+session); //TODO: how do we tell when a UDP client is gone } //tcp ChannelHandlerContext ctx = forwardingHandler.getChannel(session); if(ctx!=null && ctx.channel().isWritable())ctx.pipeline().writeAndFlush(msg+"\r\n"); } } } public int getTcpPort() { return tcpPort; } public void setTcpPort(int port) { this.tcpPort = port; } protected int getUdpPort() { return udpPort; } protected void setUdpPort(int udpPort) { this.udpPort = udpPort; } }