package net.yura.lobby.netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;
import net.yura.lobby.model.Message;
import net.yura.lobby.server.LobbyServer;
import net.yura.lobby.server.LobbySession;
import net.yura.lobby.server.SocketServer;
import org.w3c.dom.Document;
/**
* @author Yura Mamyrin
*/
public class Server implements SocketServer,NettyMXBean {
public static final boolean DEBUG_SEND = false;
public static final Logger logger = Logger.getLogger(Server.class.getName());
private final int port;
private final LobbyServer server;
public Server(int port) {
this.port = port;
server = new LobbyServer(this);
bossGroup = new NioEventLoopGroup();
workerGroup = new NioEventLoopGroup();
if (DEBUG_SEND) {
logger.setLevel(Level.ALL);
}
}
@Override
public int getPort() {
return port;
}
// HACK for some reason netty sends message in wrong order when they come from different threads
// HACK so we send all message from the same thread
private final Executor singleThread = Executors.newSingleThreadExecutor();
@Override
public void send(final LobbySession key, final Message message) {
final Channel chc = getChannel(key);
if (chc!=null) {
if (DEBUG_SEND) {
logger.log(Level.FINE, "sending "+System.identityHashCode(message)+" "+message);
}
// HACK to fix ordering of messages
singleThread.execute(new Runnable() { @Override public void run() {
try {
chc.writeAndFlush(message);
}
catch (Exception ex) {
logger.log(Level.WARNING,"Failed to send "+key+" "+message,ex);
}
}});
}
else {
logger.warning("LobbySession: "+key+" NOT FOUND! can not send "+message);
}
}
@Override
public void kick(LobbySession key) {
Channel chc = getChannel(key);
if (chc!=null) {
chc.close();
}
else {
logger.warning("LobbySession: "+key+" NOT FOUND! can not kick");
}
}
@Override
public boolean isConnected(LobbySession key) {
Channel chc = getChannel(key);
if (chc!=null) {
return chc.isOpen();
}
else {
return false;
}
}
@Override
public List<LobbySession> getLobbySession(String username) {
List<LobbySession> result = new ArrayList();
for (Channel channel:allClientChannels) {
LobbySession session = channel.attr( SESSION_KEY ).get();
if (
(username == null) ||
(username.equals( session.getUsername() )) ||
("".equals(username) && session.getUsername()==null)
) {
result.add(session);
}
}
return result;
}
public static final AttributeKey<LobbySession> SESSION_KEY = new AttributeKey("LobbySession");
public final ChannelGroup allClientChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
private Channel getChannel(LobbySession key) {
// TODO this is not very good and would be much better if instead of the UUID
// we could store the cannel ID and then we could use allClientChannels.find(Integer)
for (Channel channel:allClientChannels) {
LobbySession session = channel.attr( SESSION_KEY ).get();
if (session == key) {
return channel;
}
}
return null;
}
@Override
public int getClientCount() {
return allClientChannels.size();
}
@Override
public void killAllClients() {
allClientChannels.close();
}
@Override
public void killSocketServer() {
serverChannel.close();
}
EventLoopGroup bossGroup,workerGroup;
Channel serverChannel;
@Override
public void start() throws InterruptedException {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("decoder",new ProtoDecoder(server));
ch.pipeline().addLast("encoder",new ProtoEncoder(server));
ch.pipeline().addLast("handler",new MessageHandler(server,allClientChannels));
}
});
// Bind and start to accept incoming connections.
ChannelFuture f = b.bind(port).sync();
serverChannel = f.channel();
logger.info("Netty server started on port "+port);
// Wait until the server socket is closed.
//serverChannel.closeFuture().sync();
}
@Override
public void shutdown() {
// Shut down all event loops to terminate all threads.
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
public static void main(String[] args) throws Exception {
LobbyServer.args = args;
LobbyServer.setupLogging();
Server myServer = new Server(1964);
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
mbs.registerMBean(myServer, new ObjectName("net.yura.lobby:type=NettyServer") );
// main thread now becomes the netty thread
myServer.start();
}
@Override
public String getVersion() {
try {
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document xmlDocument = builder.parse( ServerBootstrap.class.getResourceAsStream("/META-INF/maven/io.netty/netty-all/pom.xml") );
XPath xPath = XPathFactory.newInstance().newXPath();
XPathExpression xPathExpression = xPath.compile("/project/parent/version");
return xPathExpression.evaluate(xmlDocument);
}
catch (Exception ex) {
ex.printStackTrace();
return ex.toString();
}
}
}