我一直在用Java构建一个MMO,用于使用libGDX构建客户端的游戏。我已经为浏览器、桌面、iOS和安卓构建了客户端。为了适应多个平台,使用websockets,所有来回发送的消息都以字符串的形式发送。
计划的refreshAllClients()方法的目的是尝试在100 is的延迟时间内更新所有客户端。我听说这是一个很好的MMO方法,但我不完全确定这一点。
我已经删除了一些代码(主要是密码处理以及游戏和玩家数据的保存和加载),使其更加简洁。
我想知道代码的可读性和总体组织情况。这是迄今为止我用Java编写的最大的项目,我相信我仍然没有遵循所有的最佳实践,也没有利用所有的语言特性。如果您看到任何架构的缺陷,这些也将是很好的了解。
如果你想看其他的方法,请告诉我。BZLogger类的主要功能是登录到文件,同时允许可选地登录到控制台。websocketServer类有类似于onMessage或onOpen之类的侦听器,但是所有东西都被转发到Server。我正在使用java_websocket库。
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;
}
}玩游戏这里
发布于 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 --也许它是一种解决方案。
发布于 2015-05-06 14:32:14
/**
* 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映射到类并让它处理您的消息。https://codereview.stackexchange.com/questions/89964
复制相似问题