Java tutorial
/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package com.nus.mazegame.server; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; import com.nus.mazegame.domain.Command; import com.nus.mazegame.domain.GeneralResponseType; import com.nus.mazegame.domain.Message; import com.nus.mazegame.domain.ResponseStatus; import com.nus.mazegame.io.MDataInputStream; import com.nus.mazegame.io.MSimpleSerializable; import com.nus.mazegame.pojo.GeneralResponsePacket; import com.nus.mazegame.pojo.JoinResponsePacket; import com.nus.mazegame.pojo.MoveResponsePacket; import com.nus.mazegame.pojo.MsgHeader; import com.nus.mazegame.util.RetrySender; import com.nus.mazegame.util.SendPacketTask; import com.nus.mazegame.util.SenderInterface; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import java.net.InetSocketAddress; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; /** * * @author gejun */ public class GameServerHandler extends ChannelInboundHandlerAdapter implements SenderInterface { public static final GameServerHandler instance = new GameServerHandler(); private static final AtomicInteger userIdCounter = new AtomicInteger(0); public static final ConcurrentMap<String, ChannelHandlerContext> CNameChannelMap = new ConcurrentHashMap<String, ChannelHandlerContext>(); public static final ConcurrentMap<String, String> addressBook = new ConcurrentHashMap<>(); public static RetrySender rsender; int userId; String clientAddr; String CName; @Override public void channelActive(final ChannelHandlerContext ctx) { synchronized (instance) { if (rsender == null) { // when client connect to this slave, means master failed if (GameService.gameService.isIsSlave()) { Logger.getLogger(GameServerHandler.class.getName()).log(Level.INFO, "Master lost..."); new Thread() { @Override public void run() { try { GameService.gameService.end(GameService.gameService.getMasterId()); GameService.gameService.promoteSlave(); GameService.gameService.changeSlave(); } catch (Exception ex) { Logger.getLogger(GameService.class.getName()).log(Level.SEVERE, null, ex); } } }.start(); } rsender = new RetrySender(instance); } } String clientSocketAddr = ((InetSocketAddress) ctx.channel().remoteAddress()).toString(); Logger.getLogger(GameServerHandler.class.getName()).log(Level.INFO, "New user connected...{0}", clientSocketAddr); CNameChannelMap.putIfAbsent(clientSocketAddr, ctx); CName = clientSocketAddr; clientAddr = ((InetSocketAddress) ctx.channel().remoteAddress()).getAddress().getHostName(); addressBook.putIfAbsent(clientSocketAddr, clientAddr); ChannelFuture closeFuture = ctx.channel().closeFuture(); closeFuture.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (userId == GameService.gameService.getMasterId()) { Logger.getLogger(GameServerHandler.class.getName()).log(Level.SEVERE, "Master server failed but client still alive..."); } else if (userId == GameService.gameService.getSlaveId()) { Logger.getLogger(GameServerHandler.class.getName()).log(Level.INFO, "Slave node lost...userId:{0}", userId); // this case when slave dead new Thread() { @Override public void run() { try { GameService.gameService.end(userId); GameService.gameService.changeSlave(); } catch (Exception ex) { Logger.getLogger(GameService.class.getName()).log(Level.SEVERE, null, ex); } } }.start(); } else { Logger.getLogger(GameServerHandler.class.getName()).log(Level.INFO, "Client node lost...userId:{0}", userId); GameService.gameService.end(userId); MsgHeader header = new MsgHeader(); header.setUserId(userId); GeneralResponsePacket endPacket = new GeneralResponsePacket(); endPacket.setType(GeneralResponseType.END); endPacket.setStatus(ResponseStatus.SUCCESS); String msgId3 = RetrySender.genMsgKey(GameService.gameService.getMasterId()); header.setMsgId(msgId3); header.setAction(Command.UPDATE_END); SendPacketTask task2 = new SendPacketTask(header.getMsgId(), header, endPacket); task2.setCloseCtx(false); rsender.send(task2); } } }); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { try { MDataInputStream din = new MDataInputStream(new ByteArrayInputStream((byte[]) msg)); MsgHeader header = new MsgHeader(); header.fromStream(din); // invalid msg if (header.getMsgId().length() > 0 && header.getAction() < 50 && !rsender.isMsgValid(header.getMsgId())) { return; } Logger.getLogger(GameServerHandler.class.getName()).log(Level.INFO, "userId:{0} action:{1} msgId:{2}", new Object[] { header.getUserId(), header.getAction(), header.getMsgId() }); ByteArrayOutputStream bout; switch (header.getAction()) { case Command.JOIN: userId = userIdCounter.incrementAndGet(); JoinResponsePacket packet = GameService.gameService.join(CName, userId, clientAddr); bout = new ByteArrayOutputStream(); header.toStream(bout); packet.toStream(bout); sendMsg(ctx, bout.toByteArray(), false); break; case Command.MOVE: MoveResponsePacket movePacket = GameService.gameService.move(header.getUserId(), din); bout = new ByteArrayOutputStream(); header.toStream(bout); movePacket.toStream(bout); sendMsg(ctx, bout.toByteArray(), false); // retry send to slave String msgId2 = RetrySender.genMsgKey(GameService.gameService.getMasterId()); header.setMsgId(msgId2); header.setAction(Command.UPDATE_MOVE); SendPacketTask task = new SendPacketTask(header.getMsgId(), header, movePacket); task.setCloseCtx(false); rsender.send(task); break; case Command.END: GameService.gameService.end(header.getUserId()); GeneralResponsePacket endPacket = new GeneralResponsePacket(); endPacket.setType(GeneralResponseType.END); endPacket.setStatus(ResponseStatus.SUCCESS); bout = new ByteArrayOutputStream(); header.toStream(bout); endPacket.toStream(bout); sendMsg(ctx, bout.toByteArray(), false); // retry send to slave String msgId3 = RetrySender.genMsgKey(GameService.gameService.getMasterId()); header.setMsgId(msgId3); header.setAction(Command.UPDATE_END); SendPacketTask task2 = new SendPacketTask(header.getMsgId(), header, endPacket); task2.setCloseCtx(false); rsender.send(task2); break; case Command.ACK: String msgId = header.getMsgId(); if (msgId.length() > 0) { rsender.cancelMsg(msgId); } break; default: GeneralResponsePacket generalPacket = new GeneralResponsePacket(); generalPacket.setType(GeneralResponseType.ERROR); generalPacket.setStatus(ResponseStatus.FAIL); generalPacket.setMs(Message.WRONG_COMMAND); bout = new ByteArrayOutputStream(); header.toStream(bout); generalPacket.toStream(bout); sendMsg(ctx, bout.toByteArray(), false); } } catch (IOException ex) { Logger.getLogger(GameServerHandler.class.getName()).log(Level.SEVERE, null, ex); } } public void sendMsg(final ChannelHandlerContext ctx, byte[] msg, boolean closeCtx) { final ChannelFuture f = ctx.writeAndFlush(msg); if (closeCtx) { f.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) { assert f == future; ctx.close(); } }); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // Close the connection when an exception is raised. cause.printStackTrace(); ctx.close(); } // send to slave only @Override public void sendMsg(MsgHeader header, MSimpleSerializable packet, boolean isCloseCtx) throws IOException { Logger.getLogger(GameServerHandler.class.getName()).log(Level.FINE, "server send msg...{0} action {1}", new Object[] { header.getMsgId(), header.getAction() }); ChannelHandlerContext ctx = CNameChannelMap .get(GameService.gameService.getUsers().get(GameService.gameService.getSlaveId()).getClientName()); ByteArrayOutputStream bout = new ByteArrayOutputStream(); header.toStream(bout); if (packet != null) { packet.toStream(bout); } sendMsg(ctx, bout.toByteArray(), isCloseCtx); } }