com.nus.mazegame.server.GameServerHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.nus.mazegame.server.GameServerHandler.java

Source

/*
 * 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);
    }
}