/*
* Copyright 2013 The Solmix Project
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.gnu.org/licenses/
* or see the FSF site: http://www.fsf.org.
*/
package org.solmix.ipc.avro;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.ServerChannel;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.ChannelGroupFuture;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.util.concurrent.DefaultThreadFactory;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.EventExecutorGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import org.apache.avro.ipc.Responder;
import org.apache.avro.ipc.Server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.solmix.ipc.avro.MyNettyCodec.NettyDataPack;
import org.solmix.ipc.avro.MyNettyCodec.NettyFrameDecoder;
import org.solmix.ipc.avro.MyNettyCodec.NettyFrameEncoder;
/**
* A Netty-based RPC {@link Server} implementation,adapte netty 5.0
*
* @author solmix.f@gmail.com
* @version $Id$ 2014年2月16日
*/
public class MyNettyServer implements Server
{
private static final Logger LOG = LoggerFactory.getLogger(MyNettyServer.class.getName());
private static final int DEFAULT_BOSSES = 10;
private static final int DEFAULT_WORKERS = 300;
private final Responder responder;
private final Channel serverChannel;
private final ChannelGroup allChannels = new DefaultChannelGroup(
"avro-my-server", GlobalEventExecutor.INSTANCE);
private final EventLoopGroup accepter;
private final EventLoopGroup connector;
private final CountDownLatch closed = new CountDownLatch(1);
/**
*
* @param executionHandler if not null, will be inserted into the Netty
* pipeline. Use this when your responder does long, non-cpu bound
* processing (see Netty's ExecutionHandler javadoc).
* @param pipelineFactory Avro-related handlers will be added on top of what
* this factory creates
*/
public MyNettyServer(Responder responder, InetSocketAddress addr,
Class<? extends ServerChannel> channelClass,
final EventLoopGroup accepter, final EventLoopGroup connector,
final ChannelHandler... handlers)
{
this.responder = responder;
this.connector = connector;
this.accepter = accepter;
ServerBootstrap b = new ServerBootstrap();
b.channel(channelClass);
b.group(accepter, connector);
b.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("frameDecoder", new NettyFrameDecoder());
pipeline.addLast("frameEncoder", new NettyFrameEncoder());
pipeline.addLast("handler", new NettyServerAvroHandler());
if (handlers != null & handlers.length > 0)
for (ChannelHandler handler : handlers) {
pipeline.addLast(handler.getClass().getName(), handler);
}
}
});
serverChannel = b.bind(addr).channel();
allChannels.add(serverChannel);
}
public boolean isActive(){
return serverChannel.isActive();
}
public MyNettyServer(Responder responder, InetSocketAddress addr)
{
this(responder, addr, new ChannelHandler[] {});
}
public MyNettyServer(Responder responder, InetSocketAddress addr,
ChannelHandler... handlers)
{
this(responder, addr,
io.netty.channel.socket.nio.NioServerSocketChannel.class,
new NioEventLoopGroup(DEFAULT_BOSSES, new DefaultThreadFactory(
"Avro-nio-boss", true)), new NioEventLoopGroup(DEFAULT_WORKERS,
new DefaultThreadFactory("Avro-nio-worker", true)), handlers);
}
/**
* {@inheritDoc}
*
* @see org.apache.avro.ipc.Server#close()
*/
@Override
public void close() {
ChannelGroupFuture future = allChannels.close();
future.awaitUninterruptibly();
if (this.accepter != null)
accepter.shutdownGracefully();
if (this.connector != null)
connector.shutdownGracefully();
closed.countDown();
}
/**
* {@inheritDoc}
*
* @see org.apache.avro.ipc.Server#getPort()
*/
@Override
public int getPort() {
return ((InetSocketAddress) serverChannel.localAddress()).getPort();
}
/**
* {@inheritDoc}
*
* @see org.apache.avro.ipc.Server#join()
*/
@Override
public void join() throws InterruptedException {
closed.await();
}
/**
* {@inheritDoc}
*
* @see org.apache.avro.ipc.Server#start()
*/
@Override
public void start() {
// No-op.
}
/**
*
* @return The number of clients currently connected to this server.
*/
public int getNumActiveConnections() {
// allChannels also contains the server channel, so exclude that from
// the count.
return allChannels.size() - 1;
}
public class NettyServerAvroHandler extends
SimpleChannelInboundHandler<NettyDataPack>
{
private final MyNettyTransceiver connectionMetadata = new MyNettyTransceiver();
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
allChannels.add(ctx.channel());
super.channelActive(ctx);
}
/**
* {@inheritDoc}
*
* @see io.netty.channel.SimpleChannelInboundHandler#messageReceived
* (io.netty.channel.ChannelHandlerContext, java.lang.Object)
*/
@Override
protected void messageReceived(final ChannelHandlerContext ctx,
final NettyDataPack dataPack) throws Exception {
final List<ByteBuffer> req = dataPack.getDatas();
EventExecutor exe= ctx.executor();
/* if(exe.inEventLoop()){
List<ByteBuffer> res = responder.respond(req,
connectionMetadata);
// response will be null for oneway messages.
if (res != null) {
dataPack.setDatas(res);
ctx.write(dataPack);
}
}*/
EventExecutorGroup group= exe.parent();
group.execute(new Runnable(){
@Override
public void run() {
try {
List<ByteBuffer> res = responder.respond(req,
connectionMetadata);
// response will be null for oneway messages.
if (res != null) {
dataPack.setDatas(res);
ctx.write(dataPack);
}
} catch (IOException e) {
LOG.warn("unexpect error");
}
}
});
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
LOG.warn("Unexpected exception from downstream.", cause);
ctx.close();
allChannels.remove(ctx.channel());
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
LOG.info("Connection to {} disconnected.",
ctx.channel().remoteAddress());
super.channelInactive(ctx);
ctx.channel().close();
allChannels.remove(ctx.channel());
}
}
}