/*
Copyright (C) 2011 monte
This file is part of PSP NetParty.
PSP NetParty is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package pspnetparty.lib.engine;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.ConcurrentSkipListSet;
import pspnetparty.lib.ILogger;
import pspnetparty.lib.Utility;
import pspnetparty.lib.constants.AppConstants;
import pspnetparty.lib.constants.ProtocolConstants;
import pspnetparty.lib.socket.AsyncTcpClient;
import pspnetparty.lib.socket.IProtocol;
import pspnetparty.lib.socket.IProtocolDriver;
import pspnetparty.lib.socket.IProtocolMessageHandler;
import pspnetparty.lib.socket.IServer;
import pspnetparty.lib.socket.IServerListener;
import pspnetparty.lib.socket.ISocketConnection;
import pspnetparty.lib.socket.TextProtocolDriver;
public class PortalEngine {
private interface IServerProtocol extends IProtocol {
public String getTypeName();
}
private static final int RETRY_INTERVAL = 5 * 60 * 1000;
private ILogger logger;
private AsyncTcpClient tcpClient;
private RoomServerStatusProtocol roomServerProtocol = new RoomServerStatusProtocol();
private ConcurrentSkipListSet<RoomServerStatusProtocolDriver> roomServers;
private ConcurrentSkipListMap<String, ISocketConnection> roomServerConnections;
private ConcurrentSkipListMap<String, RetryInfo> roomServerRetryAddresses;
private SearchServerStatusProtocol searchServerProtocol = new SearchServerStatusProtocol();
private ConcurrentSkipListSet<SearchServerStatusProtocolDriver> searchServers;
private ConcurrentSkipListMap<String, ISocketConnection> searchServerConnections;
private ConcurrentSkipListMap<String, RetryInfo> searchServerRetryAddresses;
private LobbyServerStatusProtocol lobbyServerProtocol = new LobbyServerStatusProtocol();
private ConcurrentSkipListSet<LobbyServerStatusProtocolDriver> lobbyServers;
private ConcurrentSkipListMap<String, ISocketConnection> lobbyServerConnections;
private ConcurrentSkipListMap<String, RetryInfo> lobbyServerRetryAddresses;
private ConcurrentSkipListSet<RoomListProtocolDriver> roomListClients;
private final Object reconnectLock = new Object();
private boolean isStarted = false;
private static class RetryInfo {
private long timestamp;
private String hostname;
private int port;
private RetryInfo(InetSocketAddress address) {
hostname = address.getHostName();
port = address.getPort();
timestamp = System.currentTimeMillis() + RETRY_INTERVAL;
}
}
public PortalEngine(IServer server, ILogger logger) {
this.logger = logger;
roomServers = new ConcurrentSkipListSet<RoomServerStatusProtocolDriver>();
roomServerConnections = new ConcurrentSkipListMap<String, ISocketConnection>();
roomServerRetryAddresses = new ConcurrentSkipListMap<String, RetryInfo>();
searchServers = new ConcurrentSkipListSet<SearchServerStatusProtocolDriver>();
searchServerConnections = new ConcurrentSkipListMap<String, ISocketConnection>();
searchServerRetryAddresses = new ConcurrentSkipListMap<String, RetryInfo>();
lobbyServers = new ConcurrentSkipListSet<PortalEngine.LobbyServerStatusProtocolDriver>();
lobbyServerConnections = new ConcurrentSkipListMap<String, ISocketConnection>();
lobbyServerRetryAddresses = new ConcurrentSkipListMap<String, PortalEngine.RetryInfo>();
roomListClients = new ConcurrentSkipListSet<PortalEngine.RoomListProtocolDriver>();
tcpClient = new AsyncTcpClient(logger, 1000000, 2000);
server.addServerListener(new IServerListener() {
@Override
public void log(String message) {
PortalEngine.this.logger.log(message);
}
@Override
public void serverStartupFinished() {
isStarted = true;
Thread retryThread = new Thread(new Runnable() {
@Override
public void run() {
try {
while (isStarted) {
synchronized (reconnectLock) {
for (Entry<String, RetryInfo> e : roomServerRetryAddresses.entrySet()) {
RetryInfo info = e.getValue();
retry(info, roomServerProtocol, true);
}
for (Entry<String, RetryInfo> e : searchServerRetryAddresses.entrySet()) {
RetryInfo info = e.getValue();
retry(info, searchServerProtocol, true);
}
for (Entry<String, RetryInfo> e : lobbyServerRetryAddresses.entrySet()) {
RetryInfo info = e.getValue();
retry(info, lobbyServerProtocol, true);
}
}
Thread.sleep(5000);
}
} catch (InterruptedException e) {
}
}
}, PortalEngine.class.getName() + " Retry");
retryThread.setDaemon(true);
retryThread.start();
}
@Override
public void serverShutdownFinished() {
isStarted = false;
for (SearchServerStatusProtocolDriver driver : searchServers) {
driver.getConnection().disconnect();
}
searchServers.clear();
for (RoomServerStatusProtocolDriver driver : roomServers) {
driver.getConnection().disconnect();
}
roomServers.clear();
for (LobbyServerStatusProtocolDriver driver : lobbyServers) {
driver.getConnection().disconnect();
}
lobbyServers.clear();
}
});
server.addProtocol(new PortalProtocol());
server.addProtocol(new RoomListProtocol());
server.addProtocol(new RoomDataProtocol());
}
public String statusToString() {
StringBuilder sb = new StringBuilder();
sb.append("Room : ");
sb.append(roomServers);
sb.append(AppConstants.NEW_LINE);
sb.append("Search: ");
sb.append(searchServers);
sb.append(AppConstants.NEW_LINE);
sb.append("Lobby : ");
sb.append(lobbyServers);
sb.append(AppConstants.NEW_LINE);
sb.append("RoomList Clients : ");
sb.append(roomListClients.size());
sb.append(AppConstants.NEW_LINE);
return sb.toString();
}
public String allRoomsToString() {
StringBuilder sb = new StringBuilder();
for (RoomServerStatusProtocolDriver d : roomServers) {
for (Entry<String, PortalPlayRoom> e : d.playRooms.entrySet()) {
PlayRoom room = e.getValue();
sb.append(room.getRoomAddress());
sb.append('\t');
sb.append(room.getTitle());
sb.append('\t');
sb.append(room.getCurrentPlayers());
sb.append('/');
sb.append(room.getMaxPlayers());
sb.append(AppConstants.NEW_LINE);
}
}
return sb.toString();
}
private class PortalProtocol implements IProtocol {
@Override
public void log(String message) {
logger.log(message);
}
@Override
public String getProtocol() {
return ProtocolConstants.PROTOCOL_PORTAL;
}
@Override
public IProtocolDriver createDriver(ISocketConnection connection) {
PortalProtocolDriver driver = new PortalProtocolDriver(connection);
return driver;
}
}
private class PortalProtocolDriver extends TextProtocolDriver {
public PortalProtocolDriver(ISocketConnection connection) {
super(connection, portalHandlers);
}
@Override
public void log(String message) {
logger.log(message);
}
@Override
public void connectionDisconnected() {
}
@Override
public void errorProtocolNumber(String number) {
}
}
private HashMap<String, IProtocolMessageHandler> portalHandlers = new HashMap<String, IProtocolMessageHandler>();
{
portalHandlers.put(ProtocolConstants.Portal.COMMAND_FIND_SEARCH_SERVER, new IProtocolMessageHandler() {
@Override
public boolean process(IProtocolDriver driver, String argument) {
try {
// System.out.println("Query " + searchServers);
SearchServerStatusProtocolDriver status = searchServers.first();
driver.getConnection().send(Utility.encode(status.address));
} catch (NoSuchElementException e) {
}
return false;
}
});
portalHandlers.put(ProtocolConstants.Portal.COMMAND_LIST_SEARCH_SERVERS, new IProtocolMessageHandler() {
@Override
public boolean process(IProtocolDriver driver, String argument) {
StringBuilder sb = new StringBuilder();
for (SearchServerStatusProtocolDriver d : searchServers) {
if (d.currentUsers >= d.maxUsers)
break;
sb.append(d.address);
sb.append('\t');
sb.append(d.currentUsers);
sb.append('\t');
sb.append(d.maxUsers);
sb.append('\n');
}
driver.getConnection().send(Utility.encode(sb));
return false;
}
});
portalHandlers.put(ProtocolConstants.Portal.COMMAND_FIND_ROOM_SERVER, new IProtocolMessageHandler() {
@Override
public boolean process(IProtocolDriver driver, String argument) {
try {
// System.out.println("Query " + roomServers);
RoomServerStatusProtocolDriver status = roomServers.first();
driver.getConnection().send(Utility.encode(status.address));
} catch (NoSuchElementException e) {
}
return false;
}
});
portalHandlers.put(ProtocolConstants.Portal.COMMAND_LIST_ROOM_SERVERS, new IProtocolMessageHandler() {
@Override
public boolean process(IProtocolDriver driver, String argument) {
StringBuilder sb = new StringBuilder();
for (RoomServerStatusProtocolDriver d : roomServers) {
if (d.currentRooms >= d.maxRooms)
break;
sb.append(d.address);
sb.append('\t');
sb.append(d.currentRooms);
sb.append('\t');
sb.append(d.maxRooms);
sb.append('\n');
}
driver.getConnection().send(Utility.encode(sb));
return false;
}
});
portalHandlers.put(ProtocolConstants.Portal.COMMAND_FIND_LOBBY_SERVERS, new IProtocolMessageHandler() {
@Override
public boolean process(IProtocolDriver driver, String argument) {
try {
LobbyServerStatusProtocolDriver d = lobbyServers.first();
driver.getConnection().send(Utility.encode(d.address));
} catch (NoSuchElementException e) {
}
return false;
}
});
}
private void sendRoomListServerStatus() {
StringBuilder sb = new StringBuilder();
sb.append(ProtocolConstants.SERVER_STATUS);
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(roomListClients.size());
ByteBuffer buffer = Utility.encode(sb);
for (RoomListProtocolDriver d : roomListClients) {
buffer.position(0);
d.getConnection().send(buffer);
}
}
private class RoomListProtocol implements IProtocol {
@Override
public void log(String message) {
logger.log(message);
}
@Override
public String getProtocol() {
return ProtocolConstants.PROTOCOL_ROOM_LIST;
}
@Override
public IProtocolDriver createDriver(ISocketConnection connection) {
RoomListProtocolDriver driver = new RoomListProtocolDriver(connection);
roomListClients.add(driver);
sendRoomListServerStatus();
StringBuilder sb = new StringBuilder();
for (RoomServerStatusProtocolDriver d : roomServers) {
for (Entry<String, PortalPlayRoom> e : d.playRooms.entrySet()) {
PortalPlayRoom room = e.getValue();
room.appendRoomCreated(sb, d.address);
sb.append(TextProtocolDriver.MESSAGE_SEPARATOR);
}
}
if (sb.length() > 0) {
sb.deleteCharAt(sb.length() - 1);
connection.send(Utility.encode(sb));
}
return driver;
}
}
private class RoomDataProtocol extends RoomListProtocol {
@Override
public String getProtocol() {
return "PNP_ROOMDATA";
}
}
private class RoomListProtocolDriver extends TextProtocolDriver implements Comparable<RoomListProtocolDriver> {
private long created = System.currentTimeMillis();
public RoomListProtocolDriver(ISocketConnection conn) {
super(conn, roomListHandlers);
}
@Override
public void connectionDisconnected() {
roomListClients.remove(this);
sendRoomListServerStatus();
}
@Override
public void errorProtocolNumber(String number) {
}
@Override
public void log(String message) {
}
@Override
public int compareTo(RoomListProtocolDriver o) {
if (this.created < o.created)
return -1;
else if (this.created > o.created)
return 1;
return 0;
}
}
private HashMap<String, IProtocolMessageHandler> roomListHandlers = new HashMap<String, IProtocolMessageHandler>();
private void logConnectionError(InetSocketAddress socketAddress, IOException ex) {
logger.log("( " + socketAddress + " ) " + ex.toString());
}
private void retry(RetryInfo info, IServerProtocol protocol, boolean checkTimestamp) {
if (checkTimestamp && info.timestamp > System.currentTimeMillis())
return;
InetSocketAddress socketAddress = null;
try {
socketAddress = new InetSocketAddress(info.hostname, info.port);
tcpClient.connect(socketAddress, ProtocolConstants.TIMEOUT, protocol);
} catch (IOException ex) {
info.timestamp = System.currentTimeMillis() + RETRY_INTERVAL;
logConnectionError(socketAddress, ex);
}
}
private void connect(Set<String> addresses, Map<String, ISocketConnection> activeMap, Map<String, RetryInfo> retryMap,
IServerProtocol protocol) {
HashSet<String> currentServers = new HashSet<String>();
currentServers.addAll(activeMap.keySet());
currentServers.addAll(retryMap.keySet());
for (String address : currentServers) {
if (!addresses.contains(address)) {
retryMap.remove(address);
ISocketConnection conn = activeMap.remove(address);
if (conn != null) {
conn.disconnect();
}
}
}
for (String address : addresses) {
if (activeMap.containsKey(address)) {
} else if (retryMap.containsKey(address)) {
synchronized (reconnectLock) {
RetryInfo info = retryMap.get(address);
retry(info, protocol, false);
}
} else {
InetSocketAddress socketAddress = Utility.parseSocketAddress(address);
if (socketAddress != null)
try {
tcpClient.connect(socketAddress, ProtocolConstants.TIMEOUT, protocol);
} catch (IOException ex) {
RetryInfo info = new RetryInfo(socketAddress);
retryMap.put(address, info);
logConnectionError(socketAddress, ex);
}
}
}
}
public void connectRoomServers(Set<String> addresses) {
connect(addresses, roomServerConnections, roomServerRetryAddresses, roomServerProtocol);
}
public String[] listActiveRoomServers() {
return roomServerConnections.keySet().toArray(new String[roomServerConnections.size()]);
}
public String[] listDeadRoomServers() {
return roomServerRetryAddresses.keySet().toArray(new String[roomServerRetryAddresses.size()]);
}
public void connectSearchServers(Set<String> addresses) {
connect(addresses, searchServerConnections, searchServerRetryAddresses, searchServerProtocol);
}
public String[] listActiveSearchServers() {
return searchServerConnections.keySet().toArray(new String[searchServerConnections.size()]);
}
public String[] listDeadSearchServers() {
return searchServerRetryAddresses.keySet().toArray(new String[searchServerRetryAddresses.size()]);
}
public void connectLobbyServers(Set<String> addresses) {
connect(addresses, lobbyServerConnections, lobbyServerRetryAddresses, lobbyServerProtocol);
}
public String[] listActiveLobbyServers() {
return lobbyServerConnections.keySet().toArray(new String[lobbyServerConnections.size()]);
}
public String[] listDeadLobbyServers() {
return lobbyServerRetryAddresses.keySet().toArray(new String[lobbyServerRetryAddresses.size()]);
}
public void reconnectNow() {
synchronized (reconnectLock) {
for (Entry<String, RetryInfo> e : roomServerRetryAddresses.entrySet()) {
RetryInfo info = e.getValue();
retry(info, roomServerProtocol, false);
}
for (Entry<String, RetryInfo> e : searchServerRetryAddresses.entrySet()) {
RetryInfo info = e.getValue();
retry(info, searchServerProtocol, false);
}
for (Entry<String, RetryInfo> e : lobbyServerRetryAddresses.entrySet()) {
RetryInfo info = e.getValue();
retry(info, lobbyServerProtocol, false);
}
}
}
private class LobbyServerStatusProtocol implements IServerProtocol {
@Override
public void log(String message) {
}
@Override
public String getProtocol() {
return ProtocolConstants.PROTOCOL_LOBBY_STATUS;
}
@Override
public String getTypeName() {
return "ロビーサーバー";
}
@Override
public IProtocolDriver createDriver(ISocketConnection connection) {
LobbyServerStatusProtocolDriver driver = new LobbyServerStatusProtocolDriver(connection);
lobbyServerConnections.put(driver.address, connection);
lobbyServerRetryAddresses.remove(driver.address);
lobbyServers.add(driver);
logger.log(getTypeName() + "と接続しました: " + connection.getRemoteAddress());
return driver;
}
}
private class LobbyServerStatusProtocolDriver extends TextProtocolDriver implements Comparable<LobbyServerStatusProtocolDriver> {
private String address;
public LobbyServerStatusProtocolDriver(ISocketConnection connection) {
super(connection, lobbyServerHandlers);
address = Utility.socketAddressToStringByHostName(connection.getRemoteAddress());
}
@Override
public void connectionDisconnected() {
lobbyServers.remove(this);
InetSocketAddress socketAddress = getConnection().getRemoteAddress();
ISocketConnection conn = lobbyServerConnections.remove(address);
if (conn != null) {
RetryInfo info = new RetryInfo(socketAddress);
lobbyServerRetryAddresses.put(address, info);
logger.log(lobbyServerProtocol.getTypeName() + "との接続が切断されました: " + socketAddress);
} else {
logger.log(lobbyServerProtocol.getTypeName() + "との接続を切断しました: " + socketAddress);
}
}
@Override
public void errorProtocolNumber(String number) {
}
@Override
public void log(String message) {
logger.log(message);
}
@Override
public int compareTo(LobbyServerStatusProtocolDriver d) {
return address.compareTo(d.address);
}
@Override
public String toString() {
return "LobbyServer(" + address + ")";
}
}
private HashMap<String, IProtocolMessageHandler> lobbyServerHandlers = new HashMap<String, IProtocolMessageHandler>();
private class SearchServerStatusProtocol implements IServerProtocol {
@Override
public void log(String message) {
logger.log(message);
}
@Override
public String getProtocol() {
return ProtocolConstants.PROTOCOL_SEARCH_STATUS;
}
@Override
public String getTypeName() {
return "検索サーバー";
}
@Override
public IProtocolDriver createDriver(ISocketConnection connection) {
SearchServerStatusProtocolDriver driver = new SearchServerStatusProtocolDriver(connection);
searchServerConnections.put(driver.address, connection);
searchServerRetryAddresses.remove(driver.address);
logger.log(getTypeName() + "と接続しました: " + connection.getRemoteAddress());
return driver;
}
}
private class SearchServerStatusProtocolDriver extends TextProtocolDriver implements Comparable<SearchServerStatusProtocolDriver> {
private String address;
private int currentUsers;
private int maxUsers;
private boolean feedRoomData = false;
private SearchServerStatusProtocolDriver(ISocketConnection connection) {
super(connection, searchServerHandlers);
address = Utility.socketAddressToStringByHostName(connection.getRemoteAddress());
}
@Override
public void connectionDisconnected() {
searchServers.remove(this);
InetSocketAddress socketAddress = getConnection().getRemoteAddress();
ISocketConnection conn = searchServerConnections.remove(address);
if (conn != null) {
RetryInfo info = new RetryInfo(socketAddress);
searchServerRetryAddresses.put(address, info);
logger.log(searchServerProtocol.getTypeName() + "との接続が切断されました: " + socketAddress);
} else {
logger.log(searchServerProtocol.getTypeName() + "との接続を切断しました: " + socketAddress);
}
}
@Override
public void log(String message) {
logger.log(message);
}
@Override
public void errorProtocolNumber(String number) {
}
@Override
public int compareTo(SearchServerStatusProtocolDriver d) {
if (maxUsers == 0) {
if (d.maxUsers == 0) {
return address.compareTo(d.address);
}
return 1;
} else if (d.maxUsers == 0) {
return -1;
} else if (currentUsers == 0 && d.currentUsers == 0) {
int diff = maxUsers - d.maxUsers;
if (diff < 0)
return 1;
else if (diff > 0)
return -1;
else
return address.compareTo(d.address);
}
double rate1 = (double) currentUsers / (double) maxUsers;
double rate2 = (double) d.currentUsers / (double) d.maxUsers;
double diff = rate1 - rate2;
if (diff < 0d)
return -1;
if (diff > 0d)
return 1;
return address.compareTo(d.address);
}
@Override
public String toString() {
return "SearchServer(" + address + "," + currentUsers + "/" + maxUsers + (feedRoomData ? " *" : "") + ")";
}
}
private HashMap<String, IProtocolMessageHandler> searchServerHandlers = new HashMap<String, IProtocolMessageHandler>();
{
searchServerHandlers.put(ProtocolConstants.SERVER_STATUS, new IProtocolMessageHandler() {
@Override
public boolean process(IProtocolDriver driver, String argument) {
// SS currentUsers maxUsers
String[] tokens = argument.split(TextProtocolDriver.ARGUMENT_SEPARATOR);
if (tokens.length != 2)
return true;
try {
SearchServerStatusProtocolDriver status = (SearchServerStatusProtocolDriver) driver;
// System.out.println("Before: " + searchServers);
searchServers.remove(status);
int currentUsers = Integer.parseInt(tokens[0]);
int maxUsers = Integer.parseInt(tokens[1]);
status.currentUsers = currentUsers;
status.maxUsers = maxUsers;
searchServers.add(status);
// System.out.println("After : " + searchServers);
} catch (NumberFormatException e) {
}
return true;
}
});
searchServerHandlers.put(ProtocolConstants.SearchStatus.COMMAND_ASK_ROOM_DATA, new IProtocolMessageHandler() {
@Override
public boolean process(IProtocolDriver driver, String argument) {
SearchServerStatusProtocolDriver status = (SearchServerStatusProtocolDriver) driver;
status.feedRoomData = true;
StringBuilder sb = new StringBuilder();
for (RoomServerStatusProtocolDriver d : roomServers) {
for (Entry<String, PortalPlayRoom> e : d.playRooms.entrySet()) {
PortalPlayRoom room = e.getValue();
room.appendRoomCreated(sb, d.address);
sb.append(TextProtocolDriver.MESSAGE_SEPARATOR);
}
}
if (sb.length() > 0) {
sb.deleteCharAt(sb.length() - 1);
status.getConnection().send(Utility.encode(sb));
}
return true;
}
});
}
private void sendRoomDataNotify(CharSequence notify) {
ByteBuffer buffer = Utility.encode(notify);
for (SearchServerStatusProtocolDriver s : searchServers) {
if (s.feedRoomData) {
buffer.position(0);
s.getConnection().send(buffer);
}
}
for (RoomListProtocolDriver d : roomListClients) {
buffer.position(0);
d.getConnection().send(buffer);
}
}
private class RoomServerStatusProtocol implements IServerProtocol {
@Override
public void log(String message) {
logger.log(message);
}
@Override
public String getProtocol() {
return ProtocolConstants.PROTOCOL_ROOM_STATUS;
}
@Override
public String getTypeName() {
return "ルームサーバー";
}
@Override
public IProtocolDriver createDriver(ISocketConnection connection) {
RoomServerStatusProtocolDriver driver = new RoomServerStatusProtocolDriver(connection);
roomServerConnections.put(driver.address, connection);
roomServerRetryAddresses.remove(driver.address);
roomServers.add(driver);
logger.log(getTypeName() + "と接続しました: " + connection.getRemoteAddress());
return driver;
}
}
private class RoomServerStatusProtocolDriver extends TextProtocolDriver implements Comparable<RoomServerStatusProtocolDriver> {
private String address;
private int currentRooms;
private int maxRooms;
private int myRooms;
private ConcurrentHashMap<String, PortalPlayRoom> playRooms;
public RoomServerStatusProtocolDriver(ISocketConnection connection) {
super(connection, roomServerHandlers);
playRooms = new ConcurrentHashMap<String, PortalPlayRoom>();
address = Utility.socketAddressToStringByHostName(connection.getRemoteAddress());
}
@Override
public void connectionDisconnected() {
roomServers.remove(this);
String notify = ProtocolConstants.SearchStatus.NOTIFY_ROOM_SERVER_REMOVED + TextProtocolDriver.ARGUMENT_SEPARATOR + address;
sendRoomDataNotify(notify);
InetSocketAddress socketAddress = getConnection().getRemoteAddress();
ISocketConnection conn = roomServerConnections.remove(address);
if (conn != null) {
RetryInfo info = new RetryInfo(socketAddress);
roomServerRetryAddresses.put(address, info);
logger.log(roomServerProtocol.getTypeName() + "との接続が切断されました: " + socketAddress);
} else {
logger.log(roomServerProtocol.getTypeName() + "との接続を切断しました: " + socketAddress);
}
}
@Override
public void log(String message) {
logger.log(message);
}
private String supplementServerAddress(String roomAddress) {
if (roomAddress.startsWith(":")) {
return this.address + roomAddress;
} else {
return roomAddress;
}
}
@Override
public int compareTo(RoomServerStatusProtocolDriver d) {
if (maxRooms == 0) {
if (d.maxRooms == 0) {
return address.compareTo(d.address);
}
return 1;
} else if (d.maxRooms == 0) {
return -1;
} else if (currentRooms == 0 && d.currentRooms == 0) {
int diff = maxRooms - d.maxRooms;
if (diff < 0)
return 1;
else if (diff > 0)
return -1;
else
return address.compareTo(d.address);
}
double rate1 = (double) currentRooms / (double) maxRooms;
double rate2 = (double) d.currentRooms / (double) d.maxRooms;
double diff = rate1 - rate2;
if (diff < 0d)
return -1;
if (diff > 0d)
return 1;
return address.compareTo(d.address);
}
@Override
public void errorProtocolNumber(String number) {
}
@Override
public String toString() {
return "RoomServer(" + address + "," + currentRooms + "/" + maxRooms + "," + myRooms + ")";
}
}
private HashMap<String, IProtocolMessageHandler> roomServerHandlers = new HashMap<String, IProtocolMessageHandler>();
{
roomServerHandlers.put(ProtocolConstants.SERVER_STATUS, new IProtocolMessageHandler() {
@Override
public boolean process(IProtocolDriver driver, String argument) {
// NRRC maxRooms
// NRRC roomCount maxRooms myRoomCount
String[] tokens = argument.split(TextProtocolDriver.ARGUMENT_SEPARATOR, -1);
int maxRooms;
try {
if (tokens.length == 1) {
maxRooms = Integer.parseInt(tokens[0]);
} else if (tokens.length == 3) {
maxRooms = Integer.parseInt(tokens[1]);
} else {
return true;
}
RoomServerStatusProtocolDriver status = (RoomServerStatusProtocolDriver) driver;
// System.out.println("Before: " + roomServers);
roomServers.remove(status);
status.maxRooms = maxRooms;
roomServers.add(status);
// System.out.println("After : " + roomServers);
} catch (NumberFormatException e) {
}
return true;
}
});
roomServerHandlers.put(ProtocolConstants.RoomStatus.NOTIFY_ROOM_CREATED, new IProtocolMessageHandler() {
@Override
public boolean process(IProtocolDriver driver, String argument) {
// R hostName:port masterName title currentPlayers
// maxPlayers hasPassword createdTime description
final String[] tokens = argument.split(TextProtocolDriver.ARGUMENT_SEPARATOR, -1);
if (tokens.length != 8)
return true;
try {
RoomServerStatusProtocolDriver status = (RoomServerStatusProtocolDriver) driver;
String serverAddress = tokens[0];
boolean isMyRoom = !Utility.isEmpty(serverAddress);
if (!isMyRoom)
serverAddress = status.address;
String masterName = tokens[1];
String title = tokens[2];
int currentPlayers = Integer.parseInt(tokens[3]);
int maxPlayers = Integer.parseInt(tokens[4]);
boolean hasPassword = "Y".equals(tokens[5]);
long created = Long.parseLong(tokens[6]);
String description = tokens[7];
PortalPlayRoom room = new PortalPlayRoom(serverAddress, masterName, title, hasPassword, currentPlayers, maxPlayers,
created, isMyRoom);
room.setDescription(description);
status.playRooms.put(room.getRoomAddress(), room);
if (isMyRoom) {
status.myRooms++;
} else {
roomServers.remove(status);
status.currentRooms++;
roomServers.add(status);
}
StringBuilder sb = new StringBuilder();
room.appendRoomCreated(sb, status.address);
sendRoomDataNotify(sb);
} catch (NumberFormatException e) {
}
return true;
}
});
roomServerHandlers.put(ProtocolConstants.RoomStatus.NOTIFY_ROOM_UPDATED, new IProtocolMessageHandler() {
@Override
public boolean process(IProtocolDriver driver, String argument) {
// U hostname:port:master title maxPlayers hasPassword
// description
String[] tokens = argument.split(TextProtocolDriver.ARGUMENT_SEPARATOR, -1);
if (tokens.length != 5)
return true;
try {
RoomServerStatusProtocolDriver status = (RoomServerStatusProtocolDriver) driver;
String address = status.supplementServerAddress(tokens[0]);
String title = tokens[1];
int maxPlayers = Integer.parseInt(tokens[2]);
boolean hasPassword = "Y".equals(tokens[3]);
String description = tokens[4];
PlayRoom room = status.playRooms.get(address);
if (room == null)
return true;
room.setTitle(title);
room.setMaxPlayers(maxPlayers);
room.setHasPassword(hasPassword);
room.setDescription(description);
StringBuilder sb = new StringBuilder();
sb.append(ProtocolConstants.SearchStatus.NOTIFY_ROOM_UPDATED);
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(address);
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(title);
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(maxPlayers);
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(hasPassword ? "Y" : "N");
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(description);
sendRoomDataNotify(sb);
} catch (NumberFormatException e) {
}
return true;
}
});
roomServerHandlers.put(ProtocolConstants.RoomStatus.NOTIFY_ROOM_DELETED, new IProtocolMessageHandler() {
@Override
public boolean process(IProtocolDriver driver, String argument) {
RoomServerStatusProtocolDriver status = (RoomServerStatusProtocolDriver) driver;
// NRD hostname:port:master
String address = status.supplementServerAddress(argument);
PortalPlayRoom room = status.playRooms.remove(address);
if (room == null)
return true;
if (room.isMyRoom) {
status.myRooms--;
} else {
roomServers.remove(status);
status.currentRooms--;
roomServers.add(status);
}
StringBuilder sb = new StringBuilder();
sb.append(ProtocolConstants.SearchStatus.NOTIFY_ROOM_DELETED);
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(address);
sendRoomDataNotify(sb);
return true;
}
});
roomServerHandlers.put(ProtocolConstants.RoomStatus.NOTIFY_ROOM_PLAYER_COUNT_CHANGED, new IProtocolMessageHandler() {
@Override
public boolean process(IProtocolDriver driver, String argument) {
RoomServerStatusProtocolDriver status = (RoomServerStatusProtocolDriver) driver;
// NRPC hostname:port:master playerCount
String[] tokens = argument.split(TextProtocolDriver.ARGUMENT_SEPARATOR, -1);
if (tokens.length != 2)
return true;
String address = status.supplementServerAddress(tokens[0]);
PlayRoom room = status.playRooms.get(address);
if (room == null)
return true;
try {
int playerCount = Integer.parseInt(tokens[1]);
room.setCurrentPlayers(playerCount);
StringBuilder sb = new StringBuilder();
sb.append(ProtocolConstants.SearchStatus.NOTIFY_ROOM_PLAYER_COUNT_CHANGED);
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(address);
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(playerCount);
sendRoomDataNotify(sb);
} catch (NumberFormatException e) {
}
return true;
}
});
}
private static class PortalPlayRoom extends PlayRoom {
private boolean isMyRoom;
private PortalPlayRoom(String serverAddress, String masterName, String title, boolean hasPassword, int currentPlayers,
int maxPlayers, long created, boolean isMyRoom) {
super(null, serverAddress, masterName, title, hasPassword, currentPlayers, maxPlayers, created);
this.isMyRoom = isMyRoom;
}
private void appendRoomCreated(StringBuilder sb, String source) {
sb.append(ProtocolConstants.SearchStatus.NOTIFY_ROOM_CREATED);
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(source);
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
if (isMyRoom)
sb.append(getServerAddress());
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(getMasterName());
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(getTitle());
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(getCurrentPlayers());
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(getMaxPlayers());
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(hasPassword() ? "Y" : "N");
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(getCreatedTime());
sb.append(TextProtocolDriver.ARGUMENT_SEPARATOR);
sb.append(getDescription());
}
}
}