├── README ├── pom.xml └── src └── main └── java └── com └── tictactoe ├── game ├── Board.java ├── Game.java └── Player.java └── server ├── TicTacToeServer.java ├── TicTacToeServerHandler.java ├── WebSocketServerPipelineFactory.java └── message ├── GameOverMessageBean.java ├── HandshakeMessageBean.java ├── IncomingMessageBean.java ├── MessageBean.java ├── OutgoingMessageBean.java └── TurnMessageBean.java /README: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rocketpages/Netty-TicTacToe-Server/99dbb7fe544cc50ccbae23e5da125effcd245e15/README -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com 6 | TicTacToeServer 7 | 1.0-SNAPSHOT 8 | jar 9 | 10 | TicTacToeServer 11 | http://maven.apache.org 12 | 13 | 14 | UTF-8 15 | 16 | 17 | 18 | 19 | repository.jboss.org 20 | https://repository.jboss.org/nexus/content/repositories/releases/ 21 | 22 | false 23 | 24 | 25 | 26 | 27 | 28 | 29 | junit 30 | junit 31 | 3.8.1 32 | test 33 | 34 | 35 | org.jboss.netty 36 | netty 37 | 3.2.6.Final 38 | compile 39 | 40 | 41 | com.google.code.gson 42 | gson 43 | 1.7.1 44 | compile 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/main/java/com/tictactoe/game/Board.java: -------------------------------------------------------------------------------- 1 | package com.tictactoe.game; 2 | 3 | import com.tictactoe.game.Game.PlayerLetter; 4 | 5 | /** 6 | * A board for a game of Tic Tac Toe. Represents the current state of the game and which cells have been selected by whom. 7 | * 8 | * @author Kevin Webber 9 | */ 10 | public class Board { 11 | 12 | /* The number of winning combinations are small, so we'll keep it simple and do "brute force" matching. 13 | * For a game with a larger grid (such as Go), we would need to develop an algorithm, potentially based on 14 | * "Magic square". http://en.wikipedia.org/wiki/Magic_square 15 | */ 16 | public static final int[][] WINNING = { {1,2,3}, {4,5,6}, {7,8,9}, {1,4,7}, {2,5,8}, {3,6,9}, {1,5,9}, {3,5,7} }; 17 | 18 | /* 19 | * Represents a flattened game board for Tic Tac Toe. Below is the index value for each game cell. 20 | * 21 | * 1 | 2 | 3 22 | * 4 | 5 | 6 23 | * 7 | 8 | 9 24 | */ 25 | PlayerLetter[] cells = new PlayerLetter[9]; 26 | 27 | /** 28 | * Mark a cell with the player's selection. 29 | * 30 | * @param gridId 31 | * @param player 32 | */ 33 | protected void markCell(int gridId, PlayerLetter player) { 34 | cells[gridId-1] = player; 35 | } 36 | 37 | /** 38 | * Compare the current state of the game board with the possible winning combinations to determine a win condition. 39 | * This should be checked at the end of each turn. 40 | * 41 | * @param player 42 | * @return 43 | */ 44 | public boolean isWinner(PlayerLetter player) { 45 | for (int i = 0; i < WINNING.length; i++) { 46 | int[] possibleWinningCombo = WINNING[i]; 47 | if (cells[possibleWinningCombo[0]-1] == player && cells[possibleWinningCombo[1]-1] == player && cells[possibleWinningCombo[2]-1] == player) { 48 | return true; 49 | } 50 | } 51 | 52 | return false; 53 | } 54 | 55 | /** 56 | * Determines if the game is tied. The game is considered tied if there is no winner and all cells have been selected. 57 | */ 58 | public boolean isTied() { 59 | boolean boardFull = true; 60 | boolean tied = false; 61 | 62 | for (int i = 0; i < 9; i++) { 63 | PlayerLetter letter = cells[i]; 64 | if (letter == null) { 65 | boardFull = false; 66 | } 67 | } 68 | 69 | if (boardFull && (!isWinner(PlayerLetter.X) || !isWinner(PlayerLetter.O))) { 70 | tied = true; 71 | } 72 | 73 | return tied; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/tictactoe/game/Game.java: -------------------------------------------------------------------------------- 1 | package com.tictactoe.game; 2 | 3 | import java.util.Collection; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | /** 8 | * Represents a game of Tic Tac Toe. Contains players and a game board. 9 | * 10 | * @author Kevin Webber 11 | */ 12 | public class Game { 13 | 14 | private static int GAME_COUNT = 0; 15 | 16 | public enum Status { 17 | WAITING, IN_PROGRESS, WON, TIED 18 | } 19 | 20 | public enum PlayerLetter { 21 | X, O 22 | } 23 | 24 | // The game ID. The server increments this count with each new game initiated. 25 | private final int id; 26 | 27 | // Status of the current game (WAITING, IN_PROGRESS, FINISHED) 28 | private Status status; 29 | 30 | private final Board board; 31 | private Map players; 32 | private PlayerLetter winner; 33 | 34 | public Game() { 35 | this.id = GAME_COUNT++; 36 | this.board = new Board(); 37 | status = Status.WAITING; 38 | players = new HashMap(); 39 | } 40 | 41 | /** 42 | * Adds a player to this game. Changes status of game from WAITING to IN_PROGRESS if the game fills up. 43 | * 44 | * @param p 45 | * @return 46 | * @throws RuntimeException if there are already 2 or more players assigned to this game. 47 | */ 48 | public PlayerLetter addPlayer(Player p) { 49 | if (players.size() >= 2) { 50 | throw new RuntimeException("Too many players. Cannot add more than 1 player to a game."); 51 | } 52 | 53 | PlayerLetter l = (players.containsKey(PlayerLetter.X)) ? PlayerLetter.O : PlayerLetter.X; 54 | p.setLetter(l); 55 | players.put(l, p); 56 | 57 | if (players.size() == 2) { 58 | status = Status.IN_PROGRESS; 59 | } 60 | 61 | return l; 62 | } 63 | 64 | /** 65 | * Marks the selected cell of the user and updates the game's status. 66 | * 67 | * @param gridId 68 | * @param playerLetter 69 | */ 70 | public void markCell(int gridId, PlayerLetter playerLetter) { 71 | board.markCell(gridId, playerLetter); 72 | setStatus(playerLetter); 73 | } 74 | 75 | /** 76 | * Updates the status of the game. Invoked after each player's turn. 77 | * 78 | * @param playerLetter 79 | */ 80 | private void setStatus(PlayerLetter playerLetter) { 81 | // Checks first to see if the board has a winner. 82 | if (board.isWinner(playerLetter)) { 83 | status = Status.WON; 84 | 85 | if (playerLetter == PlayerLetter.X) { 86 | winner = PlayerLetter.X; 87 | } else { 88 | winner = PlayerLetter.O; 89 | } 90 | // Next check to see if the game has been tied. 91 | } else if (board.isTied()) { 92 | status = Status.TIED; 93 | } 94 | } 95 | 96 | public int getId() { 97 | return id; 98 | } 99 | 100 | public Collection getPlayers() { 101 | return players.values(); 102 | } 103 | 104 | public Player getPlayer(PlayerLetter playerLetter) { 105 | return players.get(playerLetter); 106 | } 107 | 108 | /** 109 | * Returns the opponent given a player letter. 110 | */ 111 | public Player getOpponent(String currentPlayer) { 112 | PlayerLetter currentPlayerLetter = PlayerLetter.valueOf(currentPlayer); 113 | PlayerLetter opponentPlayerLetter = currentPlayerLetter.equals(PlayerLetter.X) ? PlayerLetter.O : PlayerLetter.X; 114 | return players.get(opponentPlayerLetter); 115 | } 116 | 117 | public Board getBoard() { 118 | return board; 119 | } 120 | 121 | public Status getStatus() { 122 | return status; 123 | } 124 | 125 | public PlayerLetter getWinner() { 126 | return winner; 127 | } 128 | 129 | /** 130 | * Convenience method to determine if a specific player is the winner. 131 | */ 132 | public boolean isPlayerWinner(PlayerLetter playerLetter) { 133 | if (status == Status.WON && winner == playerLetter) { 134 | return true; 135 | } 136 | 137 | return false; 138 | } 139 | 140 | /** 141 | * Convenience method to determine if the game has been tied. 142 | */ 143 | public boolean isTied() { 144 | if (status == Status.TIED) { 145 | return true; 146 | } 147 | 148 | return false; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/main/java/com/tictactoe/game/Player.java: -------------------------------------------------------------------------------- 1 | package com.tictactoe.game; 2 | 3 | import org.jboss.netty.channel.Channel; 4 | 5 | import com.tictactoe.game.Game.PlayerLetter; 6 | 7 | /** 8 | * Represents a player for a game of Tic Tac Toe. 9 | * 10 | * @author Kevin Webber 11 | */ 12 | public class Player { 13 | 14 | // The player's websocket channel. Used for communications. 15 | private Channel channel; 16 | 17 | // The player's currently assigned letter. 18 | private PlayerLetter letter; 19 | 20 | public Player(Channel c) { 21 | channel = c; 22 | } 23 | 24 | public Channel getChannel() { 25 | return channel; 26 | } 27 | 28 | public void setLetter(PlayerLetter l) { 29 | letter = l; 30 | } 31 | 32 | public PlayerLetter getLetter() { 33 | return letter; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/tictactoe/server/TicTacToeServer.java: -------------------------------------------------------------------------------- 1 | package com.tictactoe.server; 2 | 3 | import java.net.InetSocketAddress; 4 | import java.util.concurrent.Executors; 5 | 6 | import org.jboss.netty.bootstrap.ServerBootstrap; 7 | import org.jboss.netty.channel.ChannelFactory; 8 | import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; 9 | 10 | public class TicTacToeServer { 11 | 12 | public static void main(String[] args) throws Exception { 13 | ChannelFactory factory = 14 | new NioServerSocketChannelFactory( 15 | Executors.newCachedThreadPool(), 16 | Executors.newCachedThreadPool()); 17 | 18 | ServerBootstrap bootstrap = new ServerBootstrap(factory); 19 | 20 | bootstrap.setPipelineFactory(new WebSocketServerPipelineFactory()); 21 | 22 | bootstrap.setOption("child.tcpNoDelay", true); 23 | bootstrap.setOption("child.keepAlive", true); 24 | 25 | bootstrap.bind(new InetSocketAddress(9000)); 26 | 27 | System.out.println("TicTacToe Server: Listening on port 9000"); 28 | } 29 | } -------------------------------------------------------------------------------- /src/main/java/com/tictactoe/server/TicTacToeServerHandler.java: -------------------------------------------------------------------------------- 1 | package com.tictactoe.server; 2 | 3 | import static com.tictactoe.server.message.GameOverMessageBean.Result.TIED; 4 | import static com.tictactoe.server.message.GameOverMessageBean.Result.YOU_WIN; 5 | import static com.tictactoe.server.message.TurnMessageBean.Turn.WAITING; 6 | import static com.tictactoe.server.message.TurnMessageBean.Turn.YOUR_TURN; 7 | import static org.jboss.netty.handler.codec.http.HttpHeaders.isKeepAlive; 8 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONNECTION; 9 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.ORIGIN; 10 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_KEY1; 11 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_KEY2; 12 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_LOCATION; 13 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_ORIGIN; 14 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_PROTOCOL; 15 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.WEBSOCKET_LOCATION; 16 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.WEBSOCKET_ORIGIN; 17 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.WEBSOCKET_PROTOCOL; 18 | import static org.jboss.netty.handler.codec.http.HttpHeaders.Values.WEBSOCKET; 19 | import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1; 20 | 21 | import java.security.MessageDigest; 22 | import java.util.HashMap; 23 | import java.util.Map; 24 | 25 | import org.jboss.netty.buffer.ChannelBuffer; 26 | import org.jboss.netty.buffer.ChannelBuffers; 27 | import org.jboss.netty.channel.ChannelFuture; 28 | import org.jboss.netty.channel.ChannelFutureListener; 29 | import org.jboss.netty.channel.ChannelHandlerContext; 30 | import org.jboss.netty.channel.ChannelPipeline; 31 | import org.jboss.netty.channel.ExceptionEvent; 32 | import org.jboss.netty.channel.MessageEvent; 33 | import org.jboss.netty.channel.SimpleChannelUpstreamHandler; 34 | import org.jboss.netty.handler.codec.http.DefaultHttpResponse; 35 | import org.jboss.netty.handler.codec.http.HttpHeaders; 36 | import org.jboss.netty.handler.codec.http.HttpHeaders.Names; 37 | import org.jboss.netty.handler.codec.http.HttpHeaders.Values; 38 | import org.jboss.netty.handler.codec.http.HttpMethod; 39 | import org.jboss.netty.handler.codec.http.HttpRequest; 40 | import org.jboss.netty.handler.codec.http.HttpResponse; 41 | import org.jboss.netty.handler.codec.http.HttpResponseStatus; 42 | import org.jboss.netty.handler.codec.http.HttpVersion; 43 | import org.jboss.netty.handler.codec.http.websocket.DefaultWebSocketFrame; 44 | import org.jboss.netty.handler.codec.http.websocket.WebSocketFrame; 45 | import org.jboss.netty.handler.codec.http.websocket.WebSocketFrameDecoder; 46 | import org.jboss.netty.handler.codec.http.websocket.WebSocketFrameEncoder; 47 | import org.jboss.netty.util.CharsetUtil; 48 | 49 | import com.google.gson.Gson; 50 | import com.tictactoe.game.Board; 51 | import com.tictactoe.game.Game; 52 | import com.tictactoe.game.Game.PlayerLetter; 53 | import com.tictactoe.game.Player; 54 | import com.tictactoe.server.message.GameOverMessageBean; 55 | import com.tictactoe.server.message.HandshakeMessageBean; 56 | import com.tictactoe.server.message.IncomingMessageBean; 57 | import com.tictactoe.server.message.OutgoingMessageBean; 58 | import com.tictactoe.server.message.TurnMessageBean; 59 | 60 | /** 61 | * Handles a server-side channel for a multiplayer game of Tic Tac Toe. 62 | * 63 | * @author Kevin Webber 64 | * 65 | */ 66 | public class TicTacToeServerHandler extends SimpleChannelUpstreamHandler { 67 | 68 | static Map games = new HashMap(); 69 | 70 | private static final String WEBSOCKET_PATH = "/websocket"; 71 | 72 | /* (non-Javadoc) 73 | * @see org.jboss.netty.channel.SimpleChannelUpstreamHandler#messageReceived(org.jboss.netty.channel.ChannelHandlerContext, org.jboss.netty.channel.MessageEvent) 74 | * 75 | * An incoming message (event). Invoked when either a: 76 | * 77 | * - A player navigates to the page. The initial page load triggers an HttpRequest. We perform the WebSocket handshake 78 | * and assign them to a particular game. 79 | * 80 | * - OR A player clicks on a tic tac toe square. The message contains who clicked 81 | * on which square (1 thru 9) and which game they're playing. 82 | */ 83 | @Override 84 | public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) 85 | throws Exception { 86 | Object msg = e.getMessage(); 87 | if (msg instanceof HttpRequest) { 88 | handleHttpRequest(ctx, (HttpRequest) msg); 89 | } else if (msg instanceof WebSocketFrame) { 90 | handleWebSocketFrame(ctx, (WebSocketFrame) msg); 91 | } 92 | } 93 | 94 | /** 95 | * Handles all HttpRequests. Must be a GET. Performs the WebSocket handshake 96 | * and assigns a player to a game. 97 | * 98 | * @param ctx 99 | * @param req 100 | * @throws Exception 101 | */ 102 | private void handleHttpRequest(ChannelHandlerContext ctx, HttpRequest req) 103 | throws Exception { 104 | 105 | // Allow only GET methods. 106 | if (req.getMethod() != HttpMethod.GET) { 107 | sendHttpResponse(ctx, req, new DefaultHttpResponse( 108 | HttpVersion.HTTP_1_1, HttpResponseStatus.FORBIDDEN)); 109 | return; 110 | } 111 | 112 | // Serve the WebSocket handshake request. 113 | if (req.getUri().equals(WEBSOCKET_PATH) 114 | && Values.UPGRADE.equalsIgnoreCase(req.getHeader(CONNECTION)) 115 | && WEBSOCKET.equalsIgnoreCase(req.getHeader(Names.UPGRADE))) { 116 | 117 | // Create the WebSocket handshake response. 118 | HttpResponse res = new DefaultHttpResponse( 119 | HTTP_1_1, 120 | new HttpResponseStatus(101, "Web Socket Protocol Handshake")); 121 | res.addHeader(Names.UPGRADE, WEBSOCKET); 122 | res.addHeader(CONNECTION, Values.UPGRADE); 123 | 124 | // Fill in the headers and contents depending on handshake method. 125 | // New handshake specification has a challenge. 126 | if (req.containsHeader(SEC_WEBSOCKET_KEY1) 127 | && req.containsHeader(SEC_WEBSOCKET_KEY2)) { 128 | 129 | // New handshake method with challenge 130 | res.addHeader(SEC_WEBSOCKET_ORIGIN, req.getHeader(ORIGIN)); 131 | res.addHeader(SEC_WEBSOCKET_LOCATION, getWebSocketLocation(req)); 132 | String protocol = req.getHeader(SEC_WEBSOCKET_PROTOCOL); 133 | if (protocol != null) { 134 | res.addHeader(SEC_WEBSOCKET_PROTOCOL, protocol); 135 | } 136 | 137 | // Calculate the answer of the challenge. 138 | String key1 = req.getHeader(SEC_WEBSOCKET_KEY1); 139 | String key2 = req.getHeader(SEC_WEBSOCKET_KEY2); 140 | int a = (int) (Long.parseLong(key1.replaceAll("[^0-9]", "")) / key1 141 | .replaceAll("[^ ]", "").length()); 142 | int b = (int) (Long.parseLong(key2.replaceAll("[^0-9]", "")) / key2 143 | .replaceAll("[^ ]", "").length()); 144 | long c = req.getContent().readLong(); 145 | ChannelBuffer input = ChannelBuffers.buffer(16); 146 | input.writeInt(a); 147 | input.writeInt(b); 148 | input.writeLong(c); 149 | ChannelBuffer output = ChannelBuffers 150 | .wrappedBuffer(MessageDigest.getInstance("MD5").digest( 151 | input.array())); 152 | res.setContent(output); 153 | } else { 154 | // Old handshake method with no challenge: 155 | res.addHeader(WEBSOCKET_ORIGIN, req.getHeader(ORIGIN)); 156 | res.addHeader(WEBSOCKET_LOCATION, getWebSocketLocation(req)); 157 | String protocol = req.getHeader(WEBSOCKET_PROTOCOL); 158 | if (protocol != null) { 159 | res.addHeader(WEBSOCKET_PROTOCOL, protocol); 160 | } 161 | } 162 | 163 | // Upgrade the connection and send the handshake response. 164 | ChannelPipeline p = ctx.getChannel().getPipeline(); 165 | p.remove("aggregator"); 166 | p.replace("decoder", "wsdecoder", new WebSocketFrameDecoder()); 167 | 168 | // Write handshake response to the channel 169 | ctx.getChannel().write(res); 170 | 171 | // Upgrade encoder to WebSocketFrameEncoder 172 | p.replace("encoder", "wsencoder", new WebSocketFrameEncoder()); 173 | 174 | // Initialize the game. Assign players to a game and assign them a letter (X or O) 175 | initGame(ctx); 176 | 177 | return; 178 | } 179 | 180 | // Send an error page otherwise. 181 | sendHttpResponse(ctx, req, new DefaultHttpResponse( 182 | HttpVersion.HTTP_1_1, HttpResponseStatus.FORBIDDEN)); 183 | } 184 | 185 | /** 186 | * Initializes a game. Finds an open game for a player (if another player is already waiting) or creates a new game. 187 | * 188 | * @param ctx 189 | */ 190 | private void initGame(ChannelHandlerContext ctx) { 191 | // Try to find a game waiting for a player. If one doesn't exist, create a new one. 192 | Game game = findGame(); 193 | 194 | // Create a new instance of player and assign their channel for WebSocket communications. 195 | Player player = new Player(ctx.getChannel()); 196 | 197 | // Add the player to the game. 198 | Game.PlayerLetter letter = game.addPlayer(player); 199 | 200 | // Add the game to the collection of games. 201 | games.put(game.getId(), game); 202 | 203 | // Send confirmation message to player with game ID and their assigned letter (X or O) 204 | ctx.getChannel().write(new DefaultWebSocketFrame(new HandshakeMessageBean(game.getId(), letter.toString()).toJson())); 205 | 206 | // If the game has begun we need to inform the players. Send them a "turn" message (either "waiting" or "your_turn") 207 | if (game.getStatus() == Game.Status.IN_PROGRESS) { 208 | game.getPlayer(PlayerLetter.X).getChannel().write(new DefaultWebSocketFrame(new TurnMessageBean(YOUR_TURN).toJson())); 209 | game.getPlayer(PlayerLetter.O).getChannel().write(new DefaultWebSocketFrame(new TurnMessageBean(WAITING).toJson())); 210 | } 211 | } 212 | 213 | /** 214 | * Finds an open game for a player (if another player is waiting) or creates a new game. 215 | * 216 | * @return Game 217 | */ 218 | private Game findGame() { 219 | // Find an existing game and return it 220 | for (Game g : games.values()) { 221 | if (g.getStatus().equals(Game.Status.WAITING)) { 222 | return g; 223 | } 224 | } 225 | 226 | // Or return a new game 227 | return new Game(); 228 | } 229 | 230 | /** 231 | * Process turn data from players. Message contains which square they clicked on. Sends turn data to their 232 | * opponent. 233 | * 234 | * @param ctx 235 | * @param frame 236 | */ 237 | private void handleWebSocketFrame(ChannelHandlerContext ctx, 238 | WebSocketFrame frame) { 239 | 240 | Gson gson = new Gson(); 241 | IncomingMessageBean message = gson.fromJson(frame.getTextData(), IncomingMessageBean.class); 242 | 243 | Game game = games.get(message.getGameId()); 244 | Player opponent = game.getOpponent(message.getPlayer()); 245 | Player player = game.getPlayer(PlayerLetter.valueOf(message.getPlayer())); 246 | 247 | // Mark the cell the player selected. 248 | game.markCell(message.getGridIdAsInt(), player.getLetter()); 249 | 250 | // Get the status for the current game. 251 | boolean winner = game.isPlayerWinner(player.getLetter()); 252 | boolean tied = game.isTied(); 253 | 254 | // Respond to the opponent in order to update their screen. 255 | String responseToOpponent = new OutgoingMessageBean(player.getLetter().toString(), message.getGridId(), winner, tied).toJson(); 256 | opponent.getChannel().write(new DefaultWebSocketFrame(responseToOpponent)); 257 | 258 | // Respond to the player to let them know they won. 259 | if (winner) { 260 | player.getChannel().write(new DefaultWebSocketFrame(new GameOverMessageBean(YOU_WIN).toJson())); 261 | } else if (tied) { 262 | player.getChannel().write(new DefaultWebSocketFrame(new GameOverMessageBean(TIED).toJson())); 263 | } 264 | } 265 | 266 | private void sendHttpResponse(ChannelHandlerContext ctx, HttpRequest req, 267 | HttpResponse res) { 268 | // Generate an error page if response status code is not OK (200). 269 | if (res.getStatus().getCode() != 200) { 270 | res.setContent(ChannelBuffers.copiedBuffer(res.getStatus() 271 | .toString(), CharsetUtil.UTF_8)); 272 | } 273 | 274 | // Send the response and close the connection if necessary. 275 | ChannelFuture f = ctx.getChannel().write(res); 276 | if (!isKeepAlive(req) || res.getStatus().getCode() != 200) { 277 | f.addListener(ChannelFutureListener.CLOSE); 278 | } 279 | } 280 | 281 | @Override 282 | public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) 283 | throws Exception { 284 | e.getCause().printStackTrace(); 285 | e.getChannel().close(); 286 | } 287 | 288 | private String getWebSocketLocation(HttpRequest req) { 289 | return "ws://" + req.getHeader(HttpHeaders.Names.HOST) + WEBSOCKET_PATH; 290 | } 291 | } -------------------------------------------------------------------------------- /src/main/java/com/tictactoe/server/WebSocketServerPipelineFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Red Hat, Inc. 3 | * 4 | * Red Hat licenses this file to you under the Apache License, version 2.0 5 | * (the "License"); you may not use this file except in compliance with the 6 | * License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package com.tictactoe.server; 17 | 18 | import static org.jboss.netty.channel.Channels.pipeline; 19 | 20 | import org.jboss.netty.channel.ChannelPipeline; 21 | import org.jboss.netty.channel.ChannelPipelineFactory; 22 | import org.jboss.netty.handler.codec.http.HttpChunkAggregator; 23 | import org.jboss.netty.handler.codec.http.HttpRequestDecoder; 24 | import org.jboss.netty.handler.codec.http.HttpResponseEncoder; 25 | import org.jboss.netty.handler.codec.string.StringDecoder; 26 | import org.jboss.netty.handler.codec.string.StringEncoder; 27 | 28 | /** 29 | * @author The Netty Project 30 | * @author Trustin Lee 31 | * 32 | * @version $Rev: 2080 $, $Date: 2010-01-26 18:04:19 +0900 (Tue, 26 Jan 2010) $ 33 | */ 34 | public class WebSocketServerPipelineFactory implements ChannelPipelineFactory { 35 | public ChannelPipeline getPipeline() throws Exception { 36 | // Create a default pipeline implementation. 37 | ChannelPipeline pipeline = pipeline(); 38 | pipeline.addLast("decoder", new HttpRequestDecoder()); 39 | pipeline.addLast("aggregator", new HttpChunkAggregator(65536)); 40 | pipeline.addLast("encoder", new HttpResponseEncoder()); 41 | pipeline.addLast("handler", new TicTacToeServerHandler()); 42 | return pipeline; 43 | } 44 | } -------------------------------------------------------------------------------- /src/main/java/com/tictactoe/server/message/GameOverMessageBean.java: -------------------------------------------------------------------------------- 1 | package com.tictactoe.server.message; 2 | 3 | public class GameOverMessageBean extends MessageBean { 4 | public enum Result { 5 | YOU_WIN, TIED 6 | } 7 | 8 | private final String type = "game_over"; 9 | private Result result; 10 | 11 | public GameOverMessageBean(Result r) { 12 | result = r; 13 | } 14 | 15 | public String getType() { 16 | return type; 17 | } 18 | 19 | public Result getResult() { 20 | return result; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/tictactoe/server/message/HandshakeMessageBean.java: -------------------------------------------------------------------------------- 1 | package com.tictactoe.server.message; 2 | 3 | import com.google.gson.Gson; 4 | 5 | public class HandshakeMessageBean extends MessageBean { 6 | 7 | private final String type = "handshake"; 8 | private int gameId; 9 | private String player; 10 | 11 | public HandshakeMessageBean(int gameId, String player) { 12 | this.gameId = gameId; 13 | this.player = player; 14 | } 15 | 16 | public String getType() { 17 | return type; 18 | } 19 | 20 | public int getGameId() { 21 | return gameId; 22 | } 23 | 24 | public void setGameId(int gameId) { 25 | this.gameId = gameId; 26 | } 27 | 28 | public String getPlayer() { 29 | return player; 30 | } 31 | 32 | public void setPlayer(String player) { 33 | this.player = player; 34 | } 35 | 36 | public String toJson() { 37 | Gson gson = new Gson(); 38 | return gson.toJson(this); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/tictactoe/server/message/IncomingMessageBean.java: -------------------------------------------------------------------------------- 1 | package com.tictactoe.server.message; 2 | 3 | public class IncomingMessageBean { 4 | private int gameId; 5 | private String player; 6 | private String gridId; 7 | 8 | public int getGameId() { 9 | return gameId; 10 | } 11 | public void setGameId(int gameId) { 12 | this.gameId = gameId; 13 | } 14 | public String getPlayer() { 15 | return player; 16 | } 17 | public void setPlayer(String player) { 18 | this.player = player; 19 | } 20 | public String getGridId() { 21 | return gridId; 22 | } 23 | public void setGridId(String gridId) { 24 | this.gridId = gridId; 25 | } 26 | public int getGridIdAsInt() { 27 | return Integer.valueOf(gridId.substring(gridId.length() - 1)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/tictactoe/server/message/MessageBean.java: -------------------------------------------------------------------------------- 1 | package com.tictactoe.server.message; 2 | 3 | import com.google.gson.Gson; 4 | 5 | public class MessageBean { 6 | 7 | public String toJson() { 8 | Gson gson = new Gson(); 9 | return gson.toJson(this); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/tictactoe/server/message/OutgoingMessageBean.java: -------------------------------------------------------------------------------- 1 | package com.tictactoe.server.message; 2 | 3 | public class OutgoingMessageBean extends MessageBean { 4 | private final String type = "response"; 5 | private String opponent; 6 | private String gridId; 7 | private boolean winner; 8 | private boolean tied; 9 | 10 | public OutgoingMessageBean(String opponent, String grid, boolean winner, boolean tied) { 11 | this.opponent = opponent; 12 | this.gridId = grid; 13 | this.winner = winner; 14 | this.tied = tied; 15 | } 16 | 17 | public String getType() { 18 | return type; 19 | } 20 | 21 | public String getOpponent() { 22 | return opponent; 23 | } 24 | 25 | public String getGrid() { 26 | return gridId; 27 | } 28 | 29 | public boolean getWinner() { 30 | return winner; 31 | } 32 | 33 | public boolean getTied() { 34 | return tied; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/tictactoe/server/message/TurnMessageBean.java: -------------------------------------------------------------------------------- 1 | package com.tictactoe.server.message; 2 | 3 | public class TurnMessageBean extends MessageBean { 4 | 5 | public enum Turn { 6 | WAITING, YOUR_TURN 7 | } 8 | 9 | private final String type = "turn"; 10 | private Turn turn; 11 | 12 | public TurnMessageBean(Turn t) { 13 | turn = t; 14 | } 15 | 16 | public String getType() { 17 | return type; 18 | } 19 | 20 | public Turn getTurn() { 21 | return turn; 22 | } 23 | 24 | } 25 | --------------------------------------------------------------------------------