io.dyn.net.tcp.TcpServer.java Source code

Java tutorial

Introduction

Here is the source code for io.dyn.net.tcp.TcpServer.java

Source

/*
 * Copyright (c) 2011-2012 by the original author or authors.
 *
 * 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.
 */

package io.dyn.net.tcp;

import static io.dyn.el.SpelExpression.*;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.channels.ClosedChannelException;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.net.ssl.SSLEngine;

import io.dyn.core.Dyn;
import io.dyn.core.Evented;
import io.dyn.core.EventedBase;
import io.dyn.core.Events;
import io.dyn.core.Lifecycle;
import io.dyn.core.Tasks;
import io.dyn.core.handler.CompletionHandler;
import io.dyn.core.handler.Handler;
import io.dyn.core.log.Logger;
import io.dyn.core.sys.Sys;
import io.dyn.net.nio.Buffer;
import io.dyn.net.nio.NioEvents;
import io.dyn.net.protocol.Protocol;
import io.dyn.net.ssl.SSL;
import io.dyn.net.ssl.SslConfig;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ChannelBuffer;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPipelineFactory;
import io.netty.channel.ChannelStateEvent;
import io.netty.channel.Channels;
import io.netty.channel.ExceptionEvent;
import io.netty.channel.MessageEvent;
import io.netty.channel.SimpleChannelUpstreamHandler;
import io.netty.channel.socket.nio.NioServerSocketChannelFactory;
import io.netty.handler.ssl.SslHandler;
import org.springframework.core.task.TaskExecutor;
import org.springframework.util.Assert;

/**
 * @author Jon Brisbin <jon@jbrisbin.com>
 */
public abstract class TcpServer<T extends TcpServer<? super T>> extends EventedBase<T> implements Lifecycle<T> {

    protected final Logger log = Logger.logger(getClass());

    protected String host = "localhost";
    protected int port = 3000;
    protected boolean reuseAddress = true;
    protected boolean keepAlive = true;
    protected int socketTimeout = 2000;
    protected int backlog = 1024;
    protected boolean ssl = false;
    protected SslConfig sslConfig;
    protected AtomicBoolean started = new AtomicBoolean(false);

    private ServerBootstrap bootstrap = new ServerBootstrap(
            new NioServerSocketChannelFactory(Executors.newCachedThreadPool(), // accept pool
                    Executors.newCachedThreadPool(Tasks.newThreadFactory(getClass().getSimpleName().toLowerCase())),
                    Sys.PROCESSORS * 2));

    protected Channel channel;
    protected Class<? extends Protocol> protocolHandler;

    {
        on(T(Throwable.class), new Handler<Throwable>() {
            @Override
            public void handle(Throwable t, Object... args) {
                if (!(t instanceof ClosedChannelException)) {
                    String s = t.getMessage();
                    if (null != s) {
                        switch (s) {
                        case "Broken pipe":
                        case "Connection reset by peer":
                            break;
                        default:
                            log.error(t);
                        }
                    }
                }
            }
        });
    }

    public String host() {
        return host;
    }

    @SuppressWarnings({ "unchecked" })
    public T host(String host) {
        this.host = host;
        return (T) this;
    }

    public int port() {
        return port;
    }

    @SuppressWarnings({ "unchecked" })
    public T port(int port) {
        this.port = port;
        return (T) this;
    }

    public boolean reuseAddress() {
        return reuseAddress;
    }

    @SuppressWarnings({ "unchecked" })
    public T reuseAddress(boolean reuseAddress) {
        this.reuseAddress = reuseAddress;
        return (T) this;
    }

    public boolean keepAlive() {
        return keepAlive;
    }

    @SuppressWarnings({ "unchecked" })
    public T keepAlive(boolean keepAlive) {
        this.keepAlive = keepAlive;
        return (T) this;
    }

    public int socketTimeout() {
        return socketTimeout;
    }

    @SuppressWarnings({ "unchecked" })
    public T socketTimeout(int socketTimeout) {
        this.socketTimeout = socketTimeout;
        return (T) this;
    }

    public int backlog() {
        return backlog;
    }

    @SuppressWarnings({ "unchecked" })
    public T backlog(int backlog) {
        Assert.state(backlog > 0, "backlog must be > 0");
        this.backlog = backlog;
        return (T) this;
    }

    public boolean ssl() {
        return ssl;
    }

    @SuppressWarnings({ "unchecked" })
    public T ssl(boolean ssl) {
        this.ssl = ssl;
        return (T) this;
    }

    public SslConfig sslConfig() {
        return sslConfig;
    }

    @SuppressWarnings({ "unchecked" })
    public T sslConfig(SslConfig sslConfig) {
        this.sslConfig = sslConfig;
        return (T) this;
    }

    public Class<? extends Protocol> protocolHandler() {
        return protocolHandler;
    }

    @SuppressWarnings({ "unchecked" })
    public T protocolHandler(Class<? extends Protocol> protocolHandler) {
        this.protocolHandler = protocolHandler;
        return (T) this;
    }

    @SuppressWarnings({ "unchecked" })
    @Override
    public T start() {
        Tasks.execute(new Runnable() {
            @Override
            public void run() {
                if (!started.get()) {
                    on(Lifecycle.STOP, new CompletionHandler() {
                        @Override
                        protected void complete() {
                            channel.close();
                            started.set(false);
                        }
                    });
                    bootstrap.setOption("backlog", backlog);
                    bootstrap.setOption("child.keepAlive", keepAlive);
                    bootstrap.setOption("child.reuseAddress", reuseAddress);
                    bootstrap.setOption("child.receiveBufferSize", Buffer.SMALL_BUFFER_SIZE);
                    bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
                        @Override
                        public ChannelPipeline getPipeline() throws Exception {
                            final ChannelPipeline pipeline = Channels.pipeline();
                            if (ssl) {
                                SSLEngine engine;
                                try {
                                    engine = SSL.sslContext(sslConfig).createSSLEngine();
                                    engine.setUseClientMode(false);
                                    pipeline.addLast("ssl", new SslHandler(engine));
                                } catch (Exception e) {
                                    event(Events.classToEventExpression(e.getClass()), e);
                                }
                            }
                            pipeline.addLast("channelHandler", new SimpleChannelUpstreamHandler() {
                                @Override
                                public void channelConnected(final ChannelHandlerContext ctx, ChannelStateEvent e)
                                        throws Exception {
                                    log.debug("channel connected: " + ctx.getChannel());
                                    Tasks.currentExecutor(new TaskExecutor() {
                                        @Override
                                        public void execute(Runnable task) {
                                            ctx.getPipeline().execute(task);
                                        }
                                    });

                                    Dyn<Channel> dyn = Dyn.wrap(ctx.getChannel());
                                    ctx.getChannel().setAttachment(dyn);
                                    event(NioEvents.CONNECTED, dyn);
                                }

                                @Override
                                public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e)
                                        throws Exception {
                                    log.debug("channel disconnected: " + ctx.getChannel());
                                    Tasks.currentExecutor(null);
                                    event(NioEvents.DISCONNECTED, ctx.getChannel().getAttachment());
                                }

                                @Override
                                public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
                                        throws Exception {
                                    event(Events.classToEventExpression(e.getCause().getClass()), e.getCause());
                                }
                            });
                            if (null != protocolHandler) {
                                pipeline.addLast("protocol", new SimpleChannelUpstreamHandler() {
                                    Protocol protocol = TcpServer.this.protocolHandler().newInstance();

                                    @Override
                                    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
                                            throws Exception {
                                        if (e.getMessage() instanceof ChannelBuffer) {
                                            ChannelBuffer cb = (ChannelBuffer) e.getMessage();
                                            int available = cb.readableBytes();
                                            byte[] bs = new byte[available];
                                            cb.readBytes(bs);
                                            protocol.decode(Buffer.wrap(bs),
                                                    (Evented) ctx.getChannel().getAttachment());
                                        }
                                    }
                                });
                            }
                            configurePipeline(pipeline);
                            return pipeline;
                        }
                    });

                    try {
                        channel = bootstrap.bind(new InetSocketAddress(InetAddress.getByName(host), port));
                        started.set(true);
                        //LOG.info("Listening on port %s...", port);
                        event(Lifecycle.START);
                    } catch (UnknownHostException e) {
                        event(Events.classToEventExpression(e.getClass()), e);
                    }
                }
            }
        }, executor);
        return (T) this;
    }

    @SuppressWarnings({ "unchecked" })
    @Override
    public T stop() {
        //LOG.info("Stopping...");
        if (null != channel) {
            channel.close().addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    started.set(false);
                    event(Lifecycle.STOP);
                }
            });
        }
        return (T) this;
    }

    protected abstract void configurePipeline(ChannelPipeline pipeline);

}