Java tutorial
/* * 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); }