package net.jxta.impl.endpoint.netty; import java.net.BindException; import java.net.SocketAddress; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import net.jxta.endpoint.EndpointAddress; import net.jxta.endpoint.EndpointService; import net.jxta.endpoint.MessageReceiver; import net.jxta.endpoint.MessengerEvent; import net.jxta.endpoint.MessengerEventListener; import net.jxta.exception.PeerGroupException; import net.jxta.logging.Logger; import net.jxta.logging.Logging; import net.jxta.peer.PeerID; import net.jxta.peergroup.PeerGroup; import net.jxta.peergroup.PeerGroupID; import org.jboss.netty.bootstrap.ServerBootstrap; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelException; import org.jboss.netty.channel.ChannelHandler.Sharable; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChildChannelStateEvent; import org.jboss.netty.channel.ExceptionEvent; import org.jboss.netty.channel.ServerChannelFactory; import org.jboss.netty.channel.SimpleChannelUpstreamHandler; import org.jboss.netty.channel.group.ChannelGroup; import org.jboss.netty.channel.group.ChannelGroupFuture; import org.jboss.netty.channel.group.DefaultChannelGroup; import org.jboss.netty.util.HashedWheelTimer; /** * The server side of a netty based transport. Responsible for accepting remote connections and making * the endpoint service aware of these asynchronously. * * @author Iain McGinniss (iain.mcginniss@onedrum.com) */ public class NettyTransportServer implements NettyChannelRegistry, MessageReceiver, TransportServerComponent { private static final Logger LOG = Logging.getLogger(NettyTransportServer.class.getName()); private ServerBootstrap serverBootstrap; private Channel serverChannel; private EndpointService endpointService; private AtomicBoolean started = new AtomicBoolean(false); private PeerGroup group; private PeerGroupID homeGroupID; private PeerID localPeerID; private MessengerEventListener listener; private List<EndpointAddress> publicAddresses; private AddressTranslator addrTranslator; private ChannelGroup channels; private HashedWheelTimer timeoutTimer; private ChannelGroupFuture closeChannelsFuture; private List<EndpointAddress> boundAddresses; public NettyTransportServer(ServerChannelFactory factory, AddressTranslator addrTranslator, final PeerGroup group) { this.channels = new DefaultChannelGroup(); this.group = group; this.homeGroupID = group.getPeerGroupID(); this.localPeerID = group.getPeerID(); this.addrTranslator = addrTranslator; serverBootstrap = new ServerBootstrap(factory); serverBootstrap.setParentHandler(new ConnectionGroupAddHandler()); timeoutTimer = new HashedWheelTimer(); } public void init(List<? extends SocketAddress> potentialBindpoints, EndpointAddress publicAddress, boolean usePublicOnly) throws PeerGroupException { serverBootstrap.setPipelineFactory(new NettyTransportChannelPipelineFactory(group, localPeerID, timeoutTimer, this, addrTranslator, started, null, publicAddress)); SocketAddress chosenAddress = bindServerChannel(potentialBindpoints); boundAddresses = Collections.unmodifiableList(addrTranslator.translateToExternalAddresses(chosenAddress)); if(serverChannel == null) { Logging.logCheckedWarning(LOG, "Failed to bind to any of the addresses in the configured range"); throw new PeerGroupException("Failed to bind to any address in the configured range"); } if(usePublicOnly) { if(publicAddress == null) { Logging.logCheckedWarning(LOG, "Instructed to use public address only, but no public address specified! Using all bound addresses instead"); publicAddresses = new ArrayList<EndpointAddress>(boundAddresses); } else { publicAddresses = new ArrayList<EndpointAddress>(1); publicAddresses.add(publicAddress); } } else { int size = boundAddresses.size() + ((publicAddress != null) ? 1 : 0); publicAddresses = new ArrayList<EndpointAddress>(size); if(publicAddress != null) { publicAddresses.add(publicAddress); } publicAddresses.addAll(boundAddresses); } } private SocketAddress bindServerChannel(List<? extends SocketAddress> potentialBindpoints) { for(SocketAddress nextBP : potentialBindpoints) { try { serverChannel = serverBootstrap.bind(nextBP); channels.add(serverChannel); return nextBP; } catch(ChannelException e) { String failReason = (e.getCause() != null) ? e.getCause().getMessage() : e.getMessage(); Logging.logCheckedInfo(LOG, "Attempt to bind to ", nextBP, " failed (", failReason, "), trying another address"); } } return null; } public boolean start(EndpointService endpointService) throws IllegalStateException { if(started.get()) { throw new IllegalStateException("already started"); } this.endpointService = endpointService; listener = endpointService.addMessageTransport(this); if(listener == null) { Logging.logCheckedError(LOG, "Transport registration failed for netty transport server, protocol=", addrTranslator.getProtocolName()); return false; } started.set(true); return true; } public void beginStop() { if(!started.compareAndSet(true, false)) { Logging.logCheckedWarning(LOG, "Netty transport server for protocol ", addrTranslator.getProtocolName(), " already stopped or never started!"); return; } closeChannelsFuture = channels.close(); } public void stop() throws IllegalStateException { if(closeChannelsFuture != null) { closeChannelsFuture.awaitUninterruptibly(); } serverChannel = null; serverBootstrap.releaseExternalResources(); timeoutTimer.stop(); } public void newConnection(Channel channel, EndpointAddress directedAt, EndpointAddress logicalEndpointAddress) { // EndpointAddress localAddr = addrTranslator.toEndpointAddress(channel.getLocalAddress(), serverChannel.getLocalAddress()); // NettyMessenger messenger = new NettyMessenger(channel, homeGroupID, localPeerID, directedAt, logicalEndpointAddress, endpointService); AsynchronousNettyMessenger messenger = new AsynchronousNettyMessenger(channel, homeGroupID, localPeerID, directedAt, logicalEndpointAddress, endpointService); listener.messengerReady(new MessengerEvent(this, messenger, messenger.getDestinationAddress())); } public Iterator<EndpointAddress> getPublicAddresses() { return publicAddresses.iterator(); } public EndpointService getEndpointService() { return endpointService; } public String getProtocolName() { return addrTranslator.getProtocolName(); } @Sharable private final class ConnectionGroupAddHandler extends SimpleChannelUpstreamHandler { @Override public void childChannelOpen(ChannelHandlerContext ctx, ChildChannelStateEvent e) throws Exception { Logging.logCheckedDebug(LOG, String.format("Incoming connection for transport %s from %s to %s (handled by %s)", getProtocolName(), e.getChildChannel().getRemoteAddress(), e.getChildChannel().getLocalAddress(), ctx.getChannel().getLocalAddress() )); channels.add(e.getChildChannel()); super.childChannelOpen(ctx, e); } @Override public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { // BindExceptions are transformed into ChannelBindExceptions by the netty // ServerBootstrap, and handled in in bindServerChannel() if(!(e.getCause() instanceof BindException)) { LOG.warnParams("Unexpected exception on server channel for {} protocol:\n{}", new Object[] { getProtocolName(), e.getCause() }); } } } public boolean isStarted() { return started.get(); } /** * @return the physically bound addresses for this transport, as opposed to those which are * broadcasted to external peers. */ public List<EndpointAddress> getBoundAddresses() { return boundAddresses; } }