首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >MMO游戏服务器

MMO游戏服务器
EN

Code Review用户
提问于 2015-05-06 13:01:41
回答 2查看 5.9K关注 0票数 13

我一直在用Java构建一个MMO,用于使用libGDX构建客户端的游戏。我已经为浏览器、桌面、iOS和安卓构建了客户端。为了适应多个平台,使用websockets,所有来回发送的消息都以字符串的形式发送。

计划的refreshAllClients()方法的目的是尝试在100 is的延迟时间内更新所有客户端。我听说这是一个很好的MMO方法,但我不完全确定这一点。

我已经删除了一些代码(主要是密码处理以及游戏和玩家数据的保存和加载),使其更加简洁。

我想知道代码的可读性和总体组织情况。这是迄今为止我用Java编写的最大的项目,我相信我仍然没有遵循所有的最佳实践,也没有利用所有的语言特性。如果您看到任何架构的缺陷,这些也将是很好的了解。

如果你想看其他的方法,请告诉我。BZLogger类的主要功能是登录到文件,同时允许可选地登录到控制台。websocketServer类有类似于onMessageonOpen之类的侦听器,但是所有东西都被转发到Server。我正在使用java_websocket库。

代码语言:javascript
复制
public class Server {

    private final ServerSockets websocketServer;

    private final int maxIpConnectionsThreshold = 10;

    private ArrayList<Client> clients = new ArrayList<Client>();
    private HashMap<String, Integer> recentIpAddresses = new HashMap<String, Integer>();

    private MainGame game;

    public boolean isLoadingOrSaving = false;

    private BZLogger messagesRecievedLogger = new BZLogger("messagesReceived", "messagesReceived.log", false);
    private BZLogger messagesSentLogger = new BZLogger("messagesSent", "messagesSent.log", false);
    private BZLogger systemMessagesLogger = new BZLogger("systemMessages", "systemMessages.log", true);

    /**
     * When the backupCount reaches X, the player and world data will be copied to a different folder
     */
    private int backupCount = 19; //start at 19 so that it will backup early

    public Server() {
        this.systemMessagesLogger.log("Server started");

        this.websocketServer = new ServerSockets(this, 9999);
        this.websocketServer.start();

        this.game = new MainGame();
        this.game.setServer(this);
        this.systemMessagesLogger.log("Game started");

        try {
            this.loadWorld();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        this.scheduleIpRefresh();
        this.scheduleGameUpdate();
        this.scheduleClientRefresh();
        this.scheduleWorldSave();
    }
    private void scheduleIpRefresh() {
        Runnable updateIpList = new Runnable() {
            public void run() {
                Server.this.ipRefresh();
            }
        };
        int initialDelayToStart = 0;
        int timeBetweenUpdates = 3;
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        executor.scheduleAtFixedRate(updateIpList, initialDelayToStart, timeBetweenUpdates, TimeUnit.MINUTES);
    }
    private void scheduleGameUpdate() {
        Runnable updateGame = new Runnable() {
            public void run() {
                Server.this.game.updateWorld();
            }
        };
        int initialDelayToStart = 0;
        int timeBetweenUpdates = 15;
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        executor.scheduleAtFixedRate(updateGame, initialDelayToStart, timeBetweenUpdates, TimeUnit.MINUTES);
    }
    private void scheduleClientRefresh() {
        Runnable clientRefresh = new Runnable() {
            public void run() {
                Server.this.refreshAllClients();
            }
        };
        int initialDelayToStart = 1000;
        int timeBetweenUpdates = 100;
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        executor.scheduleAtFixedRate(clientRefresh, initialDelayToStart, timeBetweenUpdates, TimeUnit.MILLISECONDS);
    }
    private void scheduleWorldSave() {
        Runnable worldSave = new Runnable() {
            public void run() {
                try {
                    Server.this.saveWorld();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        };
        int initialDelayToStart = 20;
        int timeBetweenUpdates = 600;
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        executor.scheduleAtFixedRate(worldSave, initialDelayToStart, timeBetweenUpdates, TimeUnit.SECONDS);
    }

    /**
     * Used by the game to send messages to players
     */
    public ArrayList<Client> getClients() {
        return this.clients;
    }

    /**
     * For every connected client, gets the messages from their array
     * Combines all the messages together into one big message
     * Sends it to that client
     */
    private void refreshAllClients() {

        if (this.isLoadingOrSaving) {
            return;
        }

        for (Client client : this.clients) {
            StringBuilder bigMessageBuilder = new StringBuilder();
            for (Message message : client.getAllMessages()) {
                bigMessageBuilder.append(message.getMessageString()).append(Message.delimiter);
            }
            if (!bigMessageBuilder.toString().equals("")) {
                client.connection.send(bigMessageBuilder.toString());
                this.messagesSentLogger.log(("sending client " + client.getPlayer().getName() + ": " + bigMessageBuilder.toString()));
            }
        }
    }

    /**
     * This method will call the ip frequency check
     * The connection will be closed if it has tried to connect too frequently
     */
    public void clientConnected(WebSocket conn, ClientHandshake handshake) {
        this.systemMessagesLogger.log("Client opened connection " + conn.getRemoteSocketAddress());
        this.processIpAddress(conn.getRemoteSocketAddress());
        this.systemMessagesLogger.log("total ips connected = " + String.valueOf(this.recentIpAddresses.size()));
        if (!this.hasIpConnectedTooFrequently(conn.getRemoteSocketAddress())) {        
            this.clients.add(new Client(conn, handshake));
            this.sendWelcome(conn);
        } else {
            conn.close(0);
            this.clientDisconnected(conn);
        }
    }
    private void sendWelcome(WebSocket conn) {
        WelcomeMessage welcome = new WelcomeMessage();
        conn.send(welcome.getMessageString());
        this.messagesSentLogger.log("sent conn welcome " + conn.getRemoteSocketAddress()) ;
    }

    /**
     * Saves the player data if it is not null
     */
    public void clientDisconnected(WebSocket conn) {
        this.systemMessagesLogger.log("Client closed connection " + conn.getRemoteSocketAddress());
        Client clientToRemove = null;
        for (Client client : this.clients) {
            if (conn.equals(client.connection)) {
                clientToRemove = client;
            }
        }
        if (clientToRemove != null) {
            if (clientToRemove.getUserName() != null && clientToRemove.getPlayer() != null) {
                try {
                    this.savePlayer(clientToRemove);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            this.clients.remove(clientToRemove);
        }
    }

    /**
     * Place where all messages are parsed and handled or passed to the game
     * Only messages from currently connected clients will be handled
     * For most messages, the client must be attached so that messages will be properly handled
     */
    public void processMessage(WebSocket conn, String message) {
        for (Client client : this.clients) {
            if (conn.equals(client.connection)) {

                this.messagesRecievedLogger.log("conn " + conn.getRemoteSocketAddress() + " sent:" + message);

                String delimiter = Message.delimiter;
                String[] messageFrags = message.split(delimiter);

                if (messageFrags[0].equals(String.valueOf(MessageType.PLAYER_REGISTRATION_MESSAGE.id()))) {
                    PlayerRegistrationMessage registrationMessage = PlayerRegistrationMessage.decodeMessage(messageFrags[1]);
                    this.processPlayerRegistration(registrationMessage, client);
                } else if (messageFrags[0].equals(String.valueOf(MessageType.PLAYER_LOGIN_MESSAGE.id()))) {
                    PlayerLoginMessage loginMessage = PlayerLoginMessage.decodeMessage(messageFrags[1]);
                    this.processPlayerLogin(loginMessage, client);
                } else if (messageFrags[0].equals(String.valueOf(MessageType.PLAYER_GAME_READY_MESSAGE.id()))) {
                    this.sendStartingPlayerAndRegionData(client);
                } else if (messageFrags[0].equals(String.valueOf(MessageType.PLAYER_MOVED_MESSAGE.id()))) {
                    PlayerMovedMessage decodedMessage = PlayerMovedMessage.decodeMessage(messageFrags[1]);
                    decodedMessage.client = client;
                    this.game.acceptMessage(decodedMessage);
                } else if (messageFrags[0].equals(String.valueOf(MessageType.PLAYER_BUILT_MESSAGE.id()))) {
                    PlayerBuiltMessage decodedMessage = PlayerBuiltMessage.decodeMessage(messageFrags[1]);
                    decodedMessage.client = client;
                    this.game.acceptMessage(decodedMessage);
                } else if (messageFrags[0].equals(String.valueOf(MessageType.PLAYER_SOLD_MESSAGE.id()))) {
                    PlayerSoldMessage decodedMessage = PlayerSoldMessage.decodeMessage(messageFrags[1]);
                    decodedMessage.client = client;
                    this.game.acceptMessage(decodedMessage);
                } else if (messageFrags[0].equals(String.valueOf(MessageType.PLAYER_BOUGHT_MESSAGE.id()))) {
                    PlayerBoughtMessage decodedMessage = PlayerBoughtMessage.decodeMessage(messageFrags[1]);
                    decodedMessage.client = client;
                    this.game.acceptMessage(decodedMessage);
                } else if (messageFrags[0].equals(String.valueOf(MessageType.PLAYER_BOUGHT_ALL_MESSAGE.id()))) {
                    PlayerBoughtAllMessage decodedMessage = PlayerBoughtAllMessage.decodeMessage(messageFrags[1]);
                    decodedMessage.client = client;
                    this.game.acceptMessage(decodedMessage);
                } else if (messageFrags[0].equals(String.valueOf(MessageType.PLAYER_TOOK_MESSAGE.id()))) {
                    PlayerTookMessage decodedMessage = PlayerTookMessage.decodeMessage(messageFrags[1]);
                    decodedMessage.client = client;
                    this.game.acceptMessage(decodedMessage);
                } else if (messageFrags[0].equals(String.valueOf(MessageType.PLAYER_GAVE_ALL_MESSAGE.id()))) {
                    PlayerGaveAllMessage decodedMessage = PlayerGaveAllMessage.decodeMessage(messageFrags[1]);
                    decodedMessage.client = client;
                    this.game.acceptMessage(decodedMessage);
                } else if (messageFrags[0].equals(String.valueOf(MessageType.PLAYER_GAVE_MESSAGE.id()))) {
                    PlayerGaveMessage decodedMessage = PlayerGaveMessage.decodeMessage(messageFrags[1]);
                    decodedMessage.client = client;
                    this.game.acceptMessage(decodedMessage);
                } else if (messageFrags[0].equals(String.valueOf(MessageType.PLAYER_TOOK_REGION_MESSAGE.id()))) {
                    PlayerTookEntireRegionMessage decodedMessage = PlayerTookEntireRegionMessage.decodeMessage(messageFrags[1]);
                    decodedMessage.client = client;
                    this.game.acceptMessage(decodedMessage);
                } else if (messageFrags[0].equals(String.valueOf(MessageType.PLAYER_ADDED_ENERGY_MESSAGE.id()))) {
                    PlayerAddedEnergyMessage decodedMessage = PlayerAddedEnergyMessage.decodeMessage(messageFrags[1]);
                    decodedMessage.client = client;
                    this.game.acceptMessage(decodedMessage);
                } else if (messageFrags[0].equals(String.valueOf(MessageType.PLAYER_ADDED_MAX_ENERGY_MESSAGE.id()))) {
                    PlayerAddedMaxEnergyMessage decodedMessage = PlayerAddedMaxEnergyMessage.decodeMessage(messageFrags[1]);
                    decodedMessage.client = client;
                    this.game.acceptMessage(decodedMessage);
                } else if (messageFrags[0].equals(String.valueOf(MessageType.PLAYER_ADDED_ENERGY_REGION_MESSAGE.id()))) {
                    PlayerAddedEnergyRegionMessage decodedMessage = PlayerAddedEnergyRegionMessage.decodeMessage(messageFrags[1]);
                    decodedMessage.client = client;
                    this.game.acceptMessage(decodedMessage);
                } else if (messageFrags[0].equals(String.valueOf(MessageType.PLAYER_UPDATE_MESSAGE.id()))) {
                    PlayerUpdateMessage decodedMessage = PlayerUpdateMessage.decodeMessage(messageFrags[1]);
                    decodedMessage.client = client;
                    this.game.acceptMessage(decodedMessage);
                } else if (messageFrags[0].equals(String.valueOf(MessageType.PLAYER_BOUGHT_ABILITY_MESSAGE.id()))) {
                    PlayerBoughtAbilityMessage decodedMessage = PlayerBoughtAbilityMessage.decodeMessage(messageFrags[1]);
                    decodedMessage.client = client;
                    this.game.acceptMessage(decodedMessage);
                } else if (messageFrags[0].equals(String.valueOf(MessageType.PLAYER_ACTIVATED_ABILITY_MESSAGE.id()))) {
                    PlayerActivatedAbilityMessage decodedMessage = PlayerActivatedAbilityMessage.decodeMessage(messageFrags[1]);
                    decodedMessage.client = client;
                    this.game.acceptMessage(decodedMessage);
                }
            }
        }
    }

    /**
     * To allow for logins from multiple locations/devices
     */
    private void tryToRemoveExistingPlayer(String name) {
        Client clientToRemove = null;
        for (Client existingClient : this.clients) {
            if (existingClient.getPlayer() != null &&
                name.equals(existingClient.getPlayer().getName())) {
                this.systemMessagesLogger.log("found matching player " + existingClient.getPlayer().getName());
                clientToRemove = existingClient;
            }
        }
        if (clientToRemove != null) {
            clientToRemove.connection.close(0);
            this.clientDisconnected(clientToRemove.connection);
        }
    }

    /**
     * This is the initial payload sent to the client that contains the nearby regions, and player data
     */
    private void sendStartingPlayerAndRegionData(Client client) {
        if (client.getPlayer() == null) {
            String userName = client.getUserName();
            if (userName != null) {
                Player savedPlayer = this.loadPlayerForUserName(userName);
                if (savedPlayer == null) {
                    return;
                }
                client.setPlayer(savedPlayer);
            }
        }

        PlayerMessage playerMessage = new PlayerMessage(client.getPlayer().encodeToString());
        client.connection.send(playerMessage.getMessageString());

        for (Region region : this.game.getClosebyRegionsForClient(client)) {
            RegionMessage regionMessage = new RegionMessage(region.encodeToString(), region.worldPosition());
            client.connection.send(regionMessage.getMessageString());
        }

        this.systemMessagesLogger.log("adding client " + client.getPlayer().getName());
    }

    /**
     * If the current count for an ip address in the map is over the threshold,
     * Reduces the count back to the threshold
     * Otherwise simply reduces the currently saved count by 2
     * Over time this will allow ip addresses to try to connect again
     */
    private void ipRefresh() {
        for (String string : this.recentIpAddresses.keySet()) {
            int persistentlyConnectingThreshold = 12;
            int currentCount = this.recentIpAddresses.get(string);
            if (currentCount > persistentlyConnectingThreshold) {
                this.recentIpAddresses.put(string, 10);
            } else {
                this.recentIpAddresses.put(string, Math.max(this.recentIpAddresses.get(string) - 2, 0));
            }
        }
    }

    /**
     * Adds the ip address to the map of recently connected IPs
     * Also keeps track of the number of times it has connected (if already present in map)
     */
    private void processIpAddress(InetSocketAddress address) {
        boolean wasInMap = false;
        for (String string : this.recentIpAddresses.keySet()) {
            if (string.equals(address.getHostString())) {
                int currentCount = this.recentIpAddresses.get(string);
                this.recentIpAddresses.put(string, currentCount + 1);
                wasInMap = true;
            }
        }
        if (!wasInMap) {
            this.recentIpAddresses.put(address.getHostString(), 1);
        }

        this.systemMessagesLogger.log("num times connected = " + String.valueOf(this.recentIpAddresses.get(address.getHostString())));
    }

    private boolean hasIpConnectedTooFrequently(InetSocketAddress address) {
        int count = this.recentIpAddresses.get(address.getHostString());
        if (count > this.maxIpConnectionsThreshold) {
            return true;
        }
        return false;
    }
}

玩游戏这里

EN

回答 2

Code Review用户

回答已采纳

发布于 2015-05-06 13:36:23

一些我能看到的快速的事情:

公共ArrayList getClients() {返回this.clients;}

在返回实现时,您几乎应该总是调整接口,在本例中是List。隐藏实现是件好事。

try { this.loadWorld(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }

自动生成的代码几乎应该立即更改。在大多数情况下,打印到堆栈跟踪并不是您想要的管理方式。如果您真的不知道如何管理它,请用堆栈跟踪记录错误并完成它。与控制台输出相比,日志在某个地方被查看和保存的可能性更大。

(messageFrags0.equals(String.valueOf(MessageType.PLAYER_REGISTRATION_MESSAGE.id()))) . String[] messageFrags =message.split(分隔符);if .

这段代码并不是那么可读的。我首先可能会创建MessageType.PLAYER_REGISTRATION_MESSAGE.id()的字符串版本,因为它将删除许多不太有用的转换。对于if本身,您可以使用switch作为String --也许它是一种解决方案。

票数 12
EN

Code Review用户

发布于 2015-05-06 14:32:14

代码语言:javascript
复制
/**
 * When the backupCount reaches X, the player and world data will be copied to a different folder
 */
private int backupCount = 19; //start at 19 so that it will backup early

未使用的变量有一个注释,说明功能非常大,可能需要它自己的一组类。

这里发生了什么事?

想必,你遗漏了一些代码。即使如此,我还是要说,这不是处理这类事情的方法--“不同的文件夹”和“备份”听起来像是一种不安全的冗余。

19开始,仅仅是因为这会导致"it“提前备份,这听起来充其量是一次大规模的黑客攻击。

一些咆哮者:

  • 过度使用this来引用当前实例,这只是额外的干扰,使我很难区分其他对象上的函数调用和this上的函数调用。
  • processMessage将您的服务器和所有消息类连接在一起--考虑将id映射到类并让它处理您的消息。
  • 服务器一般都太大了--主要是因为它有时太深了。当然,它必须做一些事情,但它不需要独自完成所有的事情。这里的方法应该是高级别的,而不关心实现,而是将它委托给其他类。
  • 我没有看到任何与安全有关的东西。我担心有人会给你的服务器发送假消息。
票数 10
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/89964

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档