package jk_5.nailed.server.network; import com.google.common.util.concurrent.ThreadFactoryBuilder; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.EventLoopGroup; import io.netty.channel.epoll.Epoll; import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.channel.epoll.EpollServerSocketChannel; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import jk_5.nailed.server.NailedPlatform; import net.minecraft.network.NetworkManager; import net.minecraft.network.play.server.S40PacketDisconnect; import net.minecraft.util.ChatComponentText; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; public class NailedNetworkManager { private static final boolean disableEpoll = NailedPlatform.instance().getConfig().getBoolean("network.disable-epoll"); private static final boolean epollSupported = Epoll.isAvailable(); private static final List<Channel> endpoints = new ArrayList<Channel>(); static final List<NetworkManager> networkManagers = Collections.synchronizedList(new ArrayList<NetworkManager>()); private static final EventLoopGroup workers; private static final Logger logger = LogManager.getLogger(); static { if(epollSupported && !disableEpoll){ workers = new EpollEventLoopGroup(0, new ThreadFactoryBuilder().setNameFormat("IO Thread #%d").setDaemon(true).build()); }else{ workers = new NioEventLoopGroup(0, new ThreadFactoryBuilder().setNameFormat("IO Thread #%d").setDaemon(true).build()); } } public static void addEndpoint(final SocketAddress address){ ServerBootstrap bootstrap = new ServerBootstrap(); if(epollSupported && !disableEpoll){ bootstrap.channel(EpollServerSocketChannel.class); }else{ bootstrap.channel(NioServerSocketChannel.class); } bootstrap.localAddress(address); bootstrap.childHandler(NettyChannelInitializer.INSTANCE); bootstrap.group(workers); logger.info("Opening endpoint " + address.toString()); bootstrap.bind().syncUninterruptibly().addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future){ endpoints.add(future.channel()); logger.info("Endpoint " + address.toString() + " started"); } }); } public static void startEndpoints(){ logger.info("Starting server listeners"); if(epollSupported && disableEpoll){ logger.info("Epoll transport is supported, but disabled in the config. Falling back to NIO transport"); }else if(epollSupported){ logger.info("Epoll transport is supported and will be used"); }else{ logger.info("Epoll transport is not supported. Falling back to NIO transport"); } for(String endpoint : NailedPlatform.instance().getConfig().getStringList("network.endpoints")){ String[] parts = endpoint.split(":", 2); if(parts.length != 2){ logger.error("Invalid configuration: Server endpoint " + endpoint + " does not specify a port. Ignoring it"); }else{ addEndpoint(new InetSocketAddress(parts[0], Integer.parseInt(parts[1]))); } } } public static void stopEndpoints(){ final ChatComponentText msg = new ChatComponentText("Server shutting down"); for(final NetworkManager manager : networkManagers){ manager.sendPacket(new S40PacketDisconnect(msg), new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture channelFuture) throws Exception { manager.closeChannel(msg); } }); manager.disableAutoRead(); } for(Channel endpoint : endpoints){ endpoint.close().syncUninterruptibly(); } } public static void processQueuedPackets(){ synchronized (networkManagers){ Iterator<NetworkManager> it = networkManagers.iterator(); while(it.hasNext()){ final NetworkManager manager = it.next(); if(!manager.isChannelOpen()){ it.remove(); if(manager.getExitMessage() != null){ manager.getNetHandler().onDisconnect(manager.getExitMessage()); }else if(manager.getNetHandler() != null){ manager.getNetHandler().onDisconnect(new ChatComponentText("Disconnected")); } }else{ try{ manager.processReceivedPackets(); }catch(Exception e){ logger.warn("Error while handling packet for client " + manager.getRemoteAddress(), e); final ChatComponentText msg = new ChatComponentText("Internal server error"); manager.sendPacket(new S40PacketDisconnect(msg), new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture channelFuture) throws Exception { manager.closeChannel(msg); } }); manager.disableAutoRead(); } } } } } }