Networking.Networking.java Source code

Java tutorial

Introduction

Here is the source code for Networking.Networking.java

Source

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package Networking;

import Model.Player;
import edu.cvut.vorobvla.bap.BapJSONKeys;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.util.logging.Level;
import java.util.logging.Logger;
import edu.cvut.vorobvla.bap.BapMessages;
import edu.cvut.vorobvla.bap.BapPorts;
import java.io.Closeable;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

/**
 * <p> Enables local network communication with moderator.
 * <p>The communication protocol is described in documentation
 * of {@code CommonLib} (see {@see edu.cvut.vorobvla.bap.BapMessages}).
 * <p> This class is implemented as a singleton.
 * @author Vladimir Vorobyev (vorobvla)
 * @created on Sep 7, 2014 at 12:50:59 PM
 */

public class Networking implements Closeable {
    /** Number of port that is used for listening to moderator's broadcast. */
    private int playerPortUDP;
    /** Input buffer used for storing data received via UDP. */
    private byte[] udpInData;
    /** Socket for listening to moderator's broadcast. */
    private DatagramSocket udpSocket;
    /** Socket for TCP-based communication with moderator. */
    private Socket tcpSocket;
    /** Handles input from {@see #tcpSocket}. */
    private BufferedReader inTCP;
    /** Handles output to {@see #tcpSocket}. */
    private PrintWriter outTCP;
    /** The  single instance of {@see Networking} object (typical for 
     * singleton implementation). */
    private static Networking instance;
    /** Describes network communication state. */
    private NetworkState state;
    /** Thread to perform listening for moderator's broadcast of 
     * {@see edu.cvut.vorobvla.bap.BapMessages#MSG_CALL_FOR_PLAYERS} message. */
    private Thread networkListener;
    /** Thread to perform listening for moderator's broadcast of 
     *  game information. (maybe redundant, will be fixed in next versions) */
    private Thread gameListener;
    /** Handles parsing of JSONObjects with game information. */
    private JSONParser parser;
    /** Timestamp parsed from the latest JSONObjects that has been received.
     * Needed for ignoring incoming information if it is not up-to-date.*/
    private long broadcastLatestTimestamp;
    /** If moderator enabled broadcasting. */
    private boolean broadcast;

    /**
     * Typical singleton method to get the only instance of this object.
     * @return the only instance of {@see #Networking} object.
     */
    public static Networking getInstance() {
        if (instance == null) {
            try {
                instance = new Networking();
            } catch (SocketException ex) {
                Logger.getLogger(Networking.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        return instance;
    }

    /**
     * Constructs a new {@see #Networking} object. Sets {@see #state} to 
     * {@see Networking.NetworkState#NOT_CONNECTED}, initializes {@see #udpInData}
     * as array of 512 bytes. Sets {@see #playerPortUDP} to value defined in 
     * {@see edu.cvut.vorobvla.bap.BapPorts#PLAYER_PORT} and initializes
     * {@see udpSocket} with this value as constructor parameter (a port to
     * bind with this socket). If initialization fails 
     * ({@code SocketException} occurs) increments {@see #playerPortUDP} and
     * attempts to initialize {@see udpSocket} with parameter {@see #playerPortUDP}.
     * Proceeds like this until initialization succeeds or until {@see #playerPortUDP}
     * reaches {@see edu.cvut.vorobvla.bap.BapPorts#PLAYER_PORT}{@code + }
     * {@see edu.cvut.vorobvla.bap.BapPorts#PLAYER_PORT_RANGE}{@code - 1}.
     * Initializes {@see networkListener} as an anonymous {@code Thread} child
     * with overridden {@code run()} method. 
     * <p> The {@code run()} method
     * the greatest part of the network protocol (waits for
     * {@see edu.cvut.vorobvla.bap.BapMessages#MSG_CALL_FOR_PLAYERS}, processes it
     * and calls {@see establishConnectionWithModerator} and {@see recvOpts}).
     * <p>Constructor of {@see #Networking} proceeds with setting {@see #gameListener}
     * to {@code null}, {@see #broadcastLatestTimestamp} to {@code 0} and
     * initializing {@see #parser}.
     * 
     * @throws SocketException if all attempts to initialize {@see udpSocket}
     * were unsuccessful.
     */
    private Networking() throws SocketException {
        state = NetworkState.NOT_CONNECTED;
        this.playerPortUDP = BapPorts.PLAYER_PORT;
        //    identity = Player.getInstance().getName();
        udpInData = new byte[512];
        while (udpSocket == null) {//may be bug
            try {
                udpSocket = new DatagramSocket(playerPortUDP);
            } catch (SocketException ex) {
                if (playerPortUDP < BapPorts.PLAYER_PORT + BapPorts.PLAYER_PORT_RANGE) {
                    Logger.getLogger(Networking.class.getName()).log(Level.INFO,
                            "bad UDP port " + playerPortUDP + ". trying another...",
                            "bad UDP port " + playerPortUDP + ". trying another...");
                    playerPortUDP++;

                } else {
                    throw new SocketException("no suttable UDP port");
                    //Logger.getLogger(Networking.class.getName()).log(Level.SEVERE, null, "no suttable UDP port");
                    //break;
                }
            }
        }
        networkListener = new Thread() {

            @Override
            public void run() {
                try {
                    //    System.out.println("Start listening to UDP broadcast on"
                    //  + udpSocket.getLocalSocketAddress().toString());
                    DatagramPacket initMsgFromModerator = recvUDP();
                    String msg = new String(initMsgFromModerator.getData());
                    if (!msg.split(":")[0].matches(BapMessages.MSG_CALL_FOR_PLAYERS)) {
                        System.err.println("wrong message. got '" + msg + "' while '"
                                + BapMessages.MSG_CALL_FOR_PLAYERS + "' expexted");
                        return;
                    }
                    System.out.println("RECEVED: " + msg);
                    //get the moderator's IP from the pachage and the port that is listen by it from the message                 

                    establishConnectionWithModerator(initMsgFromModerator.getAddress(),
                            Integer.parseInt(msg.split(BapMessages.FIELD_DELIM)[1].replaceAll("\\D", "")));
                    recvOpts();
                } catch (IOException ex) {
                    Logger.getLogger(Networking.class.getName()).log(Level.SEVERE, null, ex);
                    System.err.println("exception while establishing connection");
                }
            }
        };

        gameListener = null;
        parser = new JSONParser();
        broadcastLatestTimestamp = 0;
        //    establishConnectionWithModerator();
    }

    /**
     * If state is {@see Networking.NetworkState#WAITING_FOR_CALL} 
     * starts {@see #networkListener} otherwise does nothing.
     */
    synchronized public void startNetworkingListener() {
        //recieve initializing message from the moderator.
        if (state != NetworkState.WAITING_FOR_CALL) {
            return;
        }
        if (networkListener.getState() != Thread.State.RUNNABLE) {
            networkListener.start();
        }
    }

    /**
     * Initializes {@see #tcpSocket} with specified parameters (address and
     * port listened by moderator's server) and 
     * establishes TCP connection with moderator's server.
     * Sends via TCP {@see edu.cvut.vorobvla.bap.BapMessages#MSG_INIT_CONNECTION}
     * message with {@see Model.Player.name} as identifier of this player and waits
     * for message {@see edu.cvut.vorobvla.bap.BapMessages#MSG_CONNECTION_EST}
     * from moderator.
     * @param moderatorAddr address of moderator's server (used as constructor 
     * parameter while initializing {@see #tcpSocket}).
     * @param moderatorTcpPort port of moderator's server (used as constructor 
     * parameter while initializing {@see #tcpSocket}).
     * 
     * @throws IOException if {@see edu.cvut.vorobvla.bap.BapMessages#MSG_CONNECTION_EST}
     * is not received when expected.
     */
    private void establishConnectionWithModerator(InetAddress moderatorAddr, int moderatorTcpPort)
            throws IOException {
        System.out.println("moderatorAddr " + moderatorAddr + "; moderatorTcpPort " + moderatorTcpPort);
        tcpSocket = new Socket(moderatorAddr, moderatorTcpPort);
        tcpSocket.setReuseAddress(false);
        inTCP = new BufferedReader(new InputStreamReader(tcpSocket.getInputStream()));
        outTCP = new PrintWriter(tcpSocket.getOutputStream(), true);
        sendMsg(BapMessages.MSG_INIT_CONNECTION + BapMessages.FIELD_DELIM + Player.getInstance().getName());
        String msg = recvMsg();
        System.out.println("RECEVED: " + msg);
        if (msg.matches(BapMessages.MSG_CONNECTION_EST)) {
            System.out.println("Connected to moderator");
            state = NetworkState.CONNECTED;
        } else {
            throw new IOException("Connection failed");
        }
    }

    /**
     * Receives {@see edu.cvut.vorobvla.bap.BapMessages#MSG_CONNECTION_EST}
     * message with information about broadcast options. Sets {@see #broadcast}
     * to {@code true} if {@see edu.cvut.vorobvla.bap.BapMessages#OPT_ON}
     * and to {@code false} otherwise.
     * @throws IOException if thrown by {@see #recvMsg} (is called to
     * receive message).
     * @throws IllegalStateException with {@code message} 
     * {@code "Illegal network state"} if called while {@see #state}
     * is not {@see Networking.NetworkState#CONNECTED}.
     */
    private void recvOpts() throws IOException, IllegalStateException {
        if (state != NetworkState.CONNECTED) {
            throw new IllegalStateException("Illegal network state");
        }
        String msg = recvMsg();
        System.out.println("GOT: " + msg);
        if (msg.split(BapMessages.FIELD_DELIM)[0].matches(BapMessages.MSG_OPT_BROADCAST)) {
            if (msg.split(BapMessages.FIELD_DELIM)[1].matches(BapMessages.OPT_ON)) {
                broadcast = true;
            } else {
                broadcast = false;
            }
        }
        state = NetworkState.IN_GAME;
    }

    /**
     * If broadcast is enabled.
     * @return value of {@see #broadcast}
     */
    public boolean isBroadcast() {
        return broadcast;
    }

    /*   
       public void startModeratorListener(){
    if(state != NetworkState.CONNECTED){
        throw new IllegalStateException("Illegal Network state");
    }
    if (moderatorListener == null){
        udpListener = new Thread() {
            @Override
            public void run() {
               String msg;
               try {
                    while(true){
                            msg = inTCP.readLine();
                                
                    /*        switch(msg){
                                case BapMessages.MSG_GAME_FIN :
                                        
                                    break;
                                default : throw new SecurityException("Unexpected message from moderator");
                            }*//*
                                } 
                                }
                                catch (IOException ex) {
                                Logger.getLogger(Networking.class.getName()).log(Level.SEVERE, null, ex);
                                } 
                                }
                                };
                                moderatorListener.start();
                                }
                                }*/

    /**
     * Receives data via UDP on {@see #udpSocket}.
     * @return {@code DatagramPacket} the packet received.
     */
    private DatagramPacket recvUDP() {
        try {
            // while(true){
            DatagramPacket receivePacket = new DatagramPacket(udpInData, udpInData.length);
            //     System.out.println("Listen UDP on " + udpSocket.getLocalSocketAddress().toString());
            udpSocket.receive(receivePacket);
            //     System.out.println("Listen UDP on " + udpSocket.getLocalAddress().toString());
            String msg = new String(receivePacket.getData());
            //    System.out.println("recvUDP: " + msg);
            //    System.out.println(" at " + udpSocket.getLocalSocketAddress().toString());
            return receivePacket;
        } catch (IOException ex) {
            Logger.getLogger(Networking.class.getName()).log(Level.SEVERE, null, ex);
            System.err.println("exception while broadcasting UDP");
        }
        return null;
    }

    /**
     * Receives data via UDP (calling {@see #recvUDP}) and 
     * parses it to {@code JSONObject}. Gets from this {@code JSONObject}
     * information about time of sending (with key 
     * {@see edu.cvut.vorobvla.bap.BapJSONKeys#KEY_BROADCAST_TIMESTAMP}).
     * If the data is up-to-date (time of sending is greater then
     * {@see #broadcastLatestTimestamp}) sets {@see #broadcastLatestTimestamp}
     * to the time of sending and returns the parsed JSONObject. Otherwise
     * returns {@code null}
     * @return {@code JSONObject} with game info normally, {@code null}
     * if the information is invalid ({@code ParseException occurs}) or not
     * up-to-date.
     */
    public JSONObject recvGameInfo() {
        System.out.println("recvGameInfo");
        String str = new String(recvUDP().getData());
        try {
            JSONObject jobj = (JSONObject) parser.parse(str);
            long tstmp = (long) jobj.get(BapJSONKeys.KEY_BROADCAST_TIMESTAMP);
            if (tstmp < broadcastLatestTimestamp) {
                System.out.println("Timestamp ignore");
                return null;
                //ignore info that is not up-to-date
            }
            broadcastLatestTimestamp = tstmp;
            System.out.println(jobj);
            return jobj;
        } catch (ParseException ex) {
            System.out.println("ParseException " + ex.toString());
            return null;
        }
    }

    /**
     * Sends message with specified content to moderator with TCP protocol.
     * @param msg desired message to send.
     */
    public void sendMsg(String msg) {
        outTCP.println(msg);
    }

    /**
     * Receives message from moderator with TCP protocol.
     * @return {@code String} with content of the received message.
     * @throws IOException if occurs wile receiving (while calling 
     * {@code inTCP.readLine()}) 
     */
    private String recvMsg() throws IOException {
        return inTCP.readLine();
    }

    /**
     * Closes {@see #tcpSocket}.
     * @throws IOException caused by {@code tcpSocket.close()}.
     */
    @Override
    public void close() throws IOException {
        tcpSocket.close();
    }

    /**
     * Interrupts {@see #networkListener}. (maybe useless).
     */
    public void interrtuptCallListener() {
        networkListener.interrupt();
    }

    /**
     * Returns {@see #state} of network communication with moderator.
     * @return {@see #state}.
     */
    public NetworkState getState() {
        return state;
    }

    /**
     * Sets {@see #state} specified value.
     * @param state the desired value.
     */
    public void setState(NetworkState state) {
        this.state = state;
    }

    /**
     * Informs moderator about terminating connection (
     * sends message {@see edu.cvut.vorobvla.bap.BapMessages#MSG_CONNECTION_TERM}).
     */
    public void sendConnTerm() {
        if (state == NetworkState.CONNECTED) {
            sendMsg(BapMessages.MSG_CONNECTION_TERM);
            state = NetworkState.NOT_CONNECTED;
        }
    }
}