PageRenderTime 105ms CodeModel.GetById 46ms RepoModel.GetById 1ms app.codeStats 0ms

/InTheZone/src/main/java/inthezone/comptroller/Network.java

https://gitlab.com/inthezone/in-the-zone
Java | 327 lines | 273 code | 37 blank | 17 comment | 37 complexity | dfef2d087724b276c5809647613bf70d MD5 | raw file
  1. package inthezone.comptroller;
  2. import isogame.engine.CorruptDataException;
  3. import java.io.BufferedReader;
  4. import java.io.IOException;
  5. import java.io.InputStreamReader;
  6. import java.io.OutputStreamWriter;
  7. import java.io.Writer;
  8. import java.net.Socket;
  9. import java.util.List;
  10. import java.util.UUID;
  11. import java.util.concurrent.BlockingQueue;
  12. import java.util.concurrent.LinkedBlockingQueue;
  13. import java.util.concurrent.TimeUnit;
  14. import java.util.concurrent.atomic.AtomicBoolean;
  15. import inthezone.battle.commands.Command;
  16. import inthezone.battle.commands.StartBattleCommand;
  17. import inthezone.battle.commands.StartBattleCommandRequest;
  18. import inthezone.battle.data.GameDataFactory;
  19. import inthezone.battle.data.Player;
  20. import inthezone.protocol.Message;
  21. import inthezone.protocol.MessageKind;
  22. import inthezone.protocol.Protocol;
  23. import inthezone.protocol.ProtocolException;
  24. public class Network implements Runnable {
  25. private final static long timeout = 20*1000;
  26. private final TimeUnit timeoutUnit = TimeUnit.SECONDS;
  27. private final GameDataFactory gameData;
  28. private final LobbyListener lobbyListener;
  29. private final AtomicBoolean connect = new AtomicBoolean(false);
  30. private final AtomicBoolean loggedIn = new AtomicBoolean(false);
  31. private String host;
  32. private int port;
  33. private Socket socket = null;
  34. private BufferedReader fromServer = null;
  35. private Writer toServer = null;
  36. private String playerName;
  37. private UUID session = null;
  38. private static final long CONNECTION_DELAY = 5l * 1000l;
  39. private static final long MAX_CONNECTION_DELAY = 5l * 60l * 1000l;
  40. private long currentConnectionDelay = CONNECTION_DELAY;
  41. private final BlockingQueue<Message> sendQueue =
  42. new LinkedBlockingQueue<>();
  43. public final BlockingQueue<Command> readCommandQueue =
  44. new LinkedBlockingQueue<>();
  45. public Network(
  46. final GameDataFactory gameData,
  47. final LobbyListener lobbyListener
  48. ) {
  49. this.gameData = gameData;
  50. this.lobbyListener = lobbyListener;
  51. }
  52. private volatile boolean disconnectNow = false;
  53. public synchronized void shutdown() {
  54. connect.set(false);
  55. disconnectNow = true;
  56. logout();
  57. }
  58. @Override
  59. public void run() {
  60. while (!disconnectNow) {
  61. synchronized (connect) {
  62. while (!connect.get()) {
  63. try {
  64. connect.wait();
  65. } catch (final InterruptedException e) {
  66. /* ignore */
  67. }
  68. }
  69. try {
  70. doConnect();
  71. (new Thread(new NetworkReader(
  72. fromServer, lobbyListener, readCommandQueue, gameData,
  73. Thread.currentThread()))).start();
  74. loggedIn.set(true);
  75. currentConnectionDelay = CONNECTION_DELAY;
  76. } catch (final IOException e) {
  77. cleanUpConnection();
  78. if (!loggedIn.get()) {
  79. connect.set(false);
  80. lobbyListener.serverError(e);
  81. } else {
  82. try {
  83. Thread.sleep(currentConnectionDelay);
  84. } catch (final InterruptedException e2) {
  85. if (!connect.get()) lobbyListener.loggedOff();
  86. }
  87. currentConnectionDelay *= 2;
  88. currentConnectionDelay =
  89. Math.min(currentConnectionDelay, MAX_CONNECTION_DELAY);
  90. }
  91. continue;
  92. } catch (final ProtocolException e) {
  93. cleanUpConnection();
  94. connect.set(false);
  95. lobbyListener.serverError(e);
  96. continue;
  97. }
  98. }
  99. while (true) {
  100. try {
  101. final Message msg = sendQueue.poll(timeout, timeoutUnit);
  102. if (msg == null) throw new InterruptedException();
  103. toServer.write(msg.toString());
  104. toServer.flush();
  105. if (msg.kind == MessageKind.LOGOFF) {
  106. cleanUpConnection();
  107. lobbyListener.loggedOff();
  108. break;
  109. }
  110. } catch (final InterruptedException e) {
  111. if (socket.isClosed()) {
  112. cleanUpConnection();
  113. lobbyListener.connectionDropped();
  114. break;
  115. }
  116. } catch (final IOException e) {
  117. cleanUpConnection();
  118. lobbyListener.connectionDropped();
  119. break;
  120. }
  121. }
  122. }
  123. }
  124. private void cleanUpConnection() {
  125. sendQueue.clear();
  126. if (socket != null) try {
  127. socket.close();
  128. } catch (final IOException e) {
  129. /* Doesn't matter */
  130. }
  131. }
  132. private void doConnect() throws IOException, ProtocolException {
  133. // connect to the server. If this fails, terminate the thread.
  134. this.socket = new Socket(host, port);
  135. this.socket.setKeepAlive(true);
  136. this.socket.setTcpNoDelay(true);
  137. this.fromServer = new BufferedReader(
  138. new InputStreamReader(socket.getInputStream(), "UTF-8"));
  139. this.toServer = new OutputStreamWriter(socket.getOutputStream(), "UTF-8");
  140. toServer.write(Message.CV(
  141. Protocol.PROTOCOL_VERSION,
  142. gameData.getVersion()).toString());
  143. toServer.flush();
  144. String raw = fromServer.readLine();
  145. if (raw == null) throw new IOException("Unexpected disconnection");
  146. Message r = Message.fromString(raw);
  147. if (r.kind != MessageKind.S_VERSION) {
  148. throw new ProtocolException("Server error: protocol violation #1");
  149. } else {
  150. this.session = r.parseSessionKey();
  151. }
  152. raw = fromServer.readLine();
  153. if (raw == null) throw new IOException("Unexpected disconnection");
  154. r = Message.fromString(raw);
  155. if (r.kind == MessageKind.GAME_DATA) {
  156. try {
  157. gameData.update(r.payload);
  158. } catch (final CorruptDataException e) {
  159. throw new ProtocolException("Server error: bad game data", e);
  160. }
  161. } else if (r.kind != MessageKind.OK) {
  162. throw new ProtocolException("Server error: protocol violation #2");
  163. }
  164. // get a name on the server
  165. boolean named = false;
  166. while (!named) {
  167. toServer.write(Message.NAME(playerName).toString());
  168. toServer.flush();
  169. raw = fromServer.readLine();
  170. if (raw == null) throw new IOException("Unexpected disconnection");
  171. r = Message.fromString(raw);
  172. if (r.kind == MessageKind.OK) {
  173. named = true;
  174. } else {
  175. final String reason = r.parseMessage();
  176. throw new ProtocolException(reason);
  177. }
  178. }
  179. // server sends a list of players in the lobby
  180. raw = fromServer.readLine();
  181. if (raw == null) throw new IOException("Unexpected disconnection");
  182. r = Message.fromString(raw);
  183. if (r.kind != MessageKind.PLAYERS_JOIN) {
  184. throw new ProtocolException("Server error: protocol violation #4");
  185. } else {
  186. lobbyListener.connectedToServer(playerName, r.parseJoinedLobby());
  187. }
  188. }
  189. public synchronized void connectToServer(
  190. final String host, final int port, final String playerName
  191. ) {
  192. synchronized (connect) {
  193. if (connect.get()) return;
  194. connect.set(true);
  195. this.host = host;
  196. this.port = port;
  197. this.playerName = playerName;
  198. connect.notify();
  199. }
  200. }
  201. public void challengePlayer(
  202. final StartBattleCommandRequest cmd, final String player
  203. ) {
  204. try {
  205. sendQueue.put(Message.CHALLENGE_PLAYER(player, cmd.getJSON(), false));
  206. } catch (final InterruptedException e) {
  207. throw new RuntimeException("This cannot happen");
  208. }
  209. }
  210. public void enterQueue(final List<String> vetoMaps) {
  211. try {
  212. sendQueue.put(Message.ENTER_QUEUE(playerName, vetoMaps));
  213. } catch (final InterruptedException e) {
  214. throw new RuntimeException("This cannot happen");
  215. }
  216. }
  217. /**
  218. * @param player The player accepting the challenge
  219. * @param otherPlayer The player who's challenge we're accepting
  220. * */
  221. public void acceptChallenge(
  222. final StartBattleCommand cmd,
  223. final Player player,
  224. final String otherPlayer
  225. ) {
  226. try {
  227. sendQueue.put(Message.ACCEPT_CHALLENGE(
  228. otherPlayer, player, cmd.getJSON(), false));
  229. } catch (final InterruptedException e) {
  230. throw new RuntimeException("This cannot happen");
  231. }
  232. }
  233. /**
  234. * @param plaer the player accepting the game
  235. * @param otherPlayer the player who's game we're accepting
  236. * */
  237. public void acceptQueuedGame(
  238. final StartBattleCommandRequest cmdRq,
  239. final Player player,
  240. final String otherPlayer
  241. ) {
  242. try {
  243. sendQueue.put(Message.ACCEPT_CHALLENGE(
  244. otherPlayer, player, cmdRq.getJSON(), true));
  245. } catch (final InterruptedException e) {
  246. throw new RuntimeException("This cannot happen");
  247. }
  248. }
  249. /**
  250. * @param player the player to reject
  251. * @param thisPlayer this player
  252. * */
  253. public void refuseChallenge(final String player, final String thisPlayer) {
  254. try {
  255. sendQueue.put(Message.REJECT_CHALLENGE(player, thisPlayer, false));
  256. } catch (final InterruptedException e) {
  257. throw new RuntimeException("This cannot happen");
  258. }
  259. }
  260. public void cancelChallenge() {
  261. try {
  262. sendQueue.put(Message.CANCEL_CHALLENGE(playerName));
  263. } catch (final InterruptedException e) {
  264. throw new RuntimeException("This cannot happen");
  265. }
  266. }
  267. public void sendCommand(final Command cmd) {
  268. try {
  269. sendQueue.put(Message.COMMAND(cmd.getJSON()));
  270. } catch (final InterruptedException e) {
  271. throw new RuntimeException("This cannot happen");
  272. }
  273. }
  274. public void gameOver() {
  275. try {
  276. sendQueue.put(Message.GAME_OVER());
  277. } catch (final InterruptedException e) {
  278. throw new RuntimeException("This cannot happen");
  279. }
  280. }
  281. public void logout() {
  282. try {
  283. connect.set(false);
  284. loggedIn.set(false);
  285. sendQueue.put(Message.LOGOFF());
  286. } catch (final InterruptedException e) {
  287. throw new RuntimeException("This cannot happen");
  288. }
  289. }
  290. }