Package com.esotericsoftware.kryonet

Source Code of com.esotericsoftware.kryonet.Server

package com.esotericsoftware.kryonet;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Set;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.util.IntMap;
import com.esotericsoftware.kryonet.FrameworkMessage.DiscoverHost;
import com.esotericsoftware.kryonet.FrameworkMessage.RegisterTCP;
import com.esotericsoftware.kryonet.FrameworkMessage.RegisterUDP;

import static com.esotericsoftware.minlog.Log.*;

/** Manages TCP and optionally UDP connections from many {@link Client Clients}.
* @author Nathan Sweet <misc@n4te.com> */
public class Server implements EndPoint {
  private final Serialization serialization;
  private final int writeBufferSize, objectBufferSize;
  private final Selector selector;
  private int emptySelects;
  private ServerSocketChannel serverChannel;
  private UdpConnection udp;
  private Connection[] connections = {};
  private IntMap<Connection> pendingConnections = new IntMap();
  Listener[] listeners = {};
  private Object listenerLock = new Object();
  private int nextConnectionID = 1;
  private volatile boolean shutdown;
  private Object updateLock = new Object();
  private Thread updateThread;
  private ServerDiscoveryHandler discoveryHandler;

  private Listener dispatchListener = new Listener() {
    public void connected (Connection connection) {
      Listener[] listeners = Server.this.listeners;
      for (int i = 0, n = listeners.length; i < n; i++)
        listeners[i].connected(connection);
    }

    public void disconnected (Connection connection) {
      removeConnection(connection);
      Listener[] listeners = Server.this.listeners;
      for (int i = 0, n = listeners.length; i < n; i++)
        listeners[i].disconnected(connection);
    }

    public void received (Connection connection, Object object) {
      Listener[] listeners = Server.this.listeners;
      for (int i = 0, n = listeners.length; i < n; i++)
        listeners[i].received(connection, object);
    }

    public void idle (Connection connection) {
      Listener[] listeners = Server.this.listeners;
      for (int i = 0, n = listeners.length; i < n; i++)
        listeners[i].idle(connection);
    }
  };

  /** Creates a Server with a write buffer size of 16384 and an object buffer size of 2048. */
  public Server () {
    this(16384, 2048);
  }

  /** @param writeBufferSize One buffer of this size is allocated for each connected client. Objects are serialized to the write
   *           buffer where the bytes are queued until they can be written to the TCP socket.
   *           <p>
   *           Normally the socket is writable and the bytes are written immediately. If the socket cannot be written to and
   *           enough serialized objects are queued to overflow the buffer, then the connection will be closed.
   *           <p>
   *           The write buffer should be sized at least as large as the largest object that will be sent, plus some head room to
   *           allow for some serialized objects to be queued in case the buffer is temporarily not writable. The amount of head
   *           room needed is dependent upon the size of objects being sent and how often they are sent.
   * @param objectBufferSize One (using only TCP) or three (using both TCP and UDP) buffers of this size are allocated. These
   *           buffers are used to hold the bytes for a single object graph until it can be sent over the network or
   *           deserialized.
   *           <p>
   *           The object buffers should be sized at least as large as the largest object that will be sent or received. */
  public Server (int writeBufferSize, int objectBufferSize) {
    this(writeBufferSize, objectBufferSize, new KryoSerialization());
  }

  public Server (int writeBufferSize, int objectBufferSize, Serialization serialization) {
    this.writeBufferSize = writeBufferSize;
    this.objectBufferSize = objectBufferSize;

    this.serialization = serialization;

    this.discoveryHandler = ServerDiscoveryHandler.DEFAULT;

    try {
      selector = Selector.open();
    } catch (IOException ex) {
      throw new RuntimeException("Error opening selector.", ex);
    }
  }

  public void setDiscoveryHandler (ServerDiscoveryHandler newDiscoveryHandler) {
    discoveryHandler = newDiscoveryHandler;
  }

  public Serialization getSerialization () {
    return serialization;
  }

  public Kryo getKryo () {
    return ((KryoSerialization)serialization).getKryo();
  }

  /** Opens a TCP only server.
   * @throws IOException if the server could not be opened. */
  public void bind (int tcpPort) throws IOException {
    bind(new InetSocketAddress(tcpPort), null);
  }

  /** Opens a TCP and UDP server.
   * @throws IOException if the server could not be opened. */
  public void bind (int tcpPort, int udpPort) throws IOException {
    bind(new InetSocketAddress(tcpPort), new InetSocketAddress(udpPort));
  }

  /** @param udpPort May be null. */
  public void bind (InetSocketAddress tcpPort, InetSocketAddress udpPort) throws IOException {
    close();
    synchronized (updateLock) {
      selector.wakeup();
      try {
        serverChannel = selector.provider().openServerSocketChannel();
        serverChannel.socket().bind(tcpPort);
        serverChannel.configureBlocking(false);
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        if (DEBUG) debug("kryonet", "Accepting connections on port: " + tcpPort + "/TCP");

        if (udpPort != null) {
          udp = new UdpConnection(serialization, objectBufferSize);
          udp.bind(selector, udpPort);
          if (DEBUG) debug("kryonet", "Accepting connections on port: " + udpPort + "/UDP");
        }
      } catch (IOException ex) {
        close();
        throw ex;
      }
    }
    if (INFO) info("kryonet", "Server opened.");
  }

  /** Accepts any new connections and reads or writes any pending data for the current connections.
   * @param timeout Wait for up to the specified milliseconds for a connection to be ready to process. May be zero to return
   *           immediately if there are no connections to process. */
  public void update (int timeout) throws IOException {
    updateThread = Thread.currentThread();
    synchronized (updateLock) { // Blocks to avoid a select while the selector is used to bind the server connection.
    }
    long startTime = System.currentTimeMillis();
    int select = 0;
    if (timeout > 0) {
      select = selector.select(timeout);
    } else {
      select = selector.selectNow();
    }
    if (select == 0) {
      emptySelects++;
      if (emptySelects == 100) {
        emptySelects = 0;
        // NIO freaks and returns immediately with 0 sometimes, so try to keep from hogging the CPU.
        long elapsedTime = System.currentTimeMillis() - startTime;
        try {
          if (elapsedTime < 25) Thread.sleep(25 - elapsedTime);
        } catch (InterruptedException ex) {
        }
      }
    } else {
      emptySelects = 0;
      Set<SelectionKey> keys = selector.selectedKeys();
      synchronized (keys) {
        UdpConnection udp = this.udp;
        outer:
        for (Iterator<SelectionKey> iter = keys.iterator(); iter.hasNext();) {
          keepAlive();
          SelectionKey selectionKey = iter.next();
          iter.remove();
          Connection fromConnection = (Connection)selectionKey.attachment();
          try {
            int ops = selectionKey.readyOps();

            if (fromConnection != null) { // Must be a TCP read or write operation.
              if (udp != null && fromConnection.udpRemoteAddress == null) {
                fromConnection.close();
                continue;
              }
              if ((ops & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
                try {
                  while (true) {
                    Object object = fromConnection.tcp.readObject(fromConnection);
                    if (object == null) break;
                    if (DEBUG) {
                      String objectString = object == null ? "null" : object.getClass().getSimpleName();
                      if (!(object instanceof FrameworkMessage)) {
                        debug("kryonet", fromConnection + " received TCP: " + objectString);
                      } else if (TRACE) {
                        trace("kryonet", fromConnection + " received TCP: " + objectString);
                      }
                    }
                    fromConnection.notifyReceived(object);
                  }
                } catch (IOException ex) {
                  if (TRACE) {
                    trace("kryonet", "Unable to read TCP from: " + fromConnection, ex);
                  } else if (DEBUG) {
                    debug("kryonet", fromConnection + " update: " + ex.getMessage());
                  }
                  fromConnection.close();
                } catch (KryoNetException ex) {
                  if (ERROR) error("kryonet", "Error reading TCP from connection: " + fromConnection, ex);
                  fromConnection.close();
                }
              }
              if ((ops & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) {
                try {
                  fromConnection.tcp.writeOperation();
                } catch (IOException ex) {
                  if (TRACE) {
                    trace("kryonet", "Unable to write TCP to connection: " + fromConnection, ex);
                  } else if (DEBUG) {
                    debug("kryonet", fromConnection + " update: " + ex.getMessage());
                  }
                  fromConnection.close();
                }
              }
              continue;
            }

            if ((ops & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {
              ServerSocketChannel serverChannel = this.serverChannel;
              if (serverChannel == null) continue;
              try {
                SocketChannel socketChannel = serverChannel.accept();
                if (socketChannel != null) acceptOperation(socketChannel);
              } catch (IOException ex) {
                if (DEBUG) debug("kryonet", "Unable to accept new connection.", ex);
              }
              continue;
            }

            // Must be a UDP read operation.
            if (udp == null) {
              selectionKey.channel().close();
              continue;
            }
            InetSocketAddress fromAddress;
            try {
              fromAddress = udp.readFromAddress();
            } catch (IOException ex) {
              if (WARN) warn("kryonet", "Error reading UDP data.", ex);
              continue;
            }
            if (fromAddress == null) continue;

            Connection[] connections = this.connections;
            for (int i = 0, n = connections.length; i < n; i++) {
              Connection connection = connections[i];
              if (fromAddress.equals(connection.udpRemoteAddress)) {
                fromConnection = connection;
                break;
              }
            }

            Object object;
            try {
              object = udp.readObject(fromConnection);
            } catch (KryoNetException ex) {
              if (WARN) {
                if (fromConnection != null) {
                  if (ERROR) error("kryonet", "Error reading UDP from connection: " + fromConnection, ex);
                } else
                  warn("kryonet", "Error reading UDP from unregistered address: " + fromAddress, ex);
              }
              continue;
            }

            if (object instanceof FrameworkMessage) {
              if (object instanceof RegisterUDP) {
                // Store the fromAddress on the connection and reply over TCP with a RegisterUDP to indicate success.
                int fromConnectionID = ((RegisterUDP)object).connectionID;
                Connection connection = pendingConnections.remove(fromConnectionID);
                if (connection != null) {
                  if (connection.udpRemoteAddress != null) continue outer;
                  connection.udpRemoteAddress = fromAddress;
                  addConnection(connection);
                  connection.sendTCP(new RegisterUDP());
                  if (DEBUG)
                    debug("kryonet", "Port " + udp.datagramChannel.socket().getLocalPort() + "/UDP connected to: "
                      + fromAddress);
                  connection.notifyConnected();
                  continue;
                }
                if (DEBUG)
                  debug("kryonet", "Ignoring incoming RegisterUDP with invalid connection ID: " + fromConnectionID);
                continue;
              }
              if (object instanceof DiscoverHost) {
                try {
                  boolean responseSent = discoveryHandler.onDiscoverHost(udp, fromAddress, serialization);
                  if (DEBUG && responseSent) debug("kryonet", "Responded to host discovery from: " + fromAddress);
                } catch (IOException ex) {
                  if (WARN) warn("kryonet", "Error replying to host discovery from: " + fromAddress, ex);
                }
                continue;
              }
            }

            if (fromConnection != null) {
              if (DEBUG) {
                String objectString = object == null ? "null" : object.getClass().getSimpleName();
                if (object instanceof FrameworkMessage) {
                  if (TRACE) trace("kryonet", fromConnection + " received UDP: " + objectString);
                } else
                  debug("kryonet", fromConnection + " received UDP: " + objectString);
              }
              fromConnection.notifyReceived(object);
              continue;
            }
            if (DEBUG) debug("kryonet", "Ignoring UDP from unregistered address: " + fromAddress);
          } catch (CancelledKeyException ex) {
            if (fromConnection != null)
              fromConnection.close();
            else
              selectionKey.channel().close();
          }
        }
      }
    }
    long time = System.currentTimeMillis();
    Connection[] connections = this.connections;
    for (int i = 0, n = connections.length; i < n; i++) {
      Connection connection = connections[i];
      if (connection.tcp.isTimedOut(time)) {
        if (DEBUG) debug("kryonet", connection + " timed out.");
        connection.close();
      } else {
        if (connection.tcp.needsKeepAlive(time)) connection.sendTCP(FrameworkMessage.keepAlive);
      }
      if (connection.isIdle()) connection.notifyIdle();
    }
  }

  private void keepAlive () {
    long time = System.currentTimeMillis();
    Connection[] connections = this.connections;
    for (int i = 0, n = connections.length; i < n; i++) {
      Connection connection = connections[i];
      if (connection.tcp.needsKeepAlive(time)) connection.sendTCP(FrameworkMessage.keepAlive);
    }
  }

  public void run () {
    if (TRACE) trace("kryonet", "Server thread started.");
    shutdown = false;
    while (!shutdown) {
      try {
        update(250);
      } catch (IOException ex) {
        if (ERROR) error("kryonet", "Error updating server connections.", ex);
        close();
      }
    }
    if (TRACE) trace("kryonet", "Server thread stopped.");
  }

  public void start () {
    new Thread(this, "Server").start();
  }

  public void stop () {
    if (shutdown) return;
    close();
    if (TRACE) trace("kryonet", "Server thread stopping.");
    shutdown = true;
  }

  private void acceptOperation (SocketChannel socketChannel) {
    Connection connection = newConnection();
    connection.initialize(serialization, writeBufferSize, objectBufferSize);
    connection.endPoint = this;
    UdpConnection udp = this.udp;
    if (udp != null) connection.udp = udp;
    try {
      SelectionKey selectionKey = connection.tcp.accept(selector, socketChannel);
      selectionKey.attach(connection);

      int id = nextConnectionID++;
      if (nextConnectionID == -1) nextConnectionID = 1;
      connection.id = id;
      connection.setConnected(true);
      connection.addListener(dispatchListener);

      if (udp == null)
        addConnection(connection);
      else
        pendingConnections.put(id, connection);

      RegisterTCP registerConnection = new RegisterTCP();
      registerConnection.connectionID = id;
      connection.sendTCP(registerConnection);

      if (udp == null) connection.notifyConnected();
    } catch (IOException ex) {
      connection.close();
      if (DEBUG) debug("kryonet", "Unable to accept TCP connection.", ex);
    }
  }

  /** Allows the connections used by the server to be subclassed. This can be useful for storage per connection without an
   * additional lookup. */
  protected Connection newConnection () {
    return new Connection();
  }

  private void addConnection (Connection connection) {
    Connection[] newConnections = new Connection[connections.length + 1];
    newConnections[0] = connection;
    System.arraycopy(connections, 0, newConnections, 1, connections.length);
    connections = newConnections;
  }

  void removeConnection (Connection connection) {
    ArrayList<Connection> temp = new ArrayList(Arrays.asList(connections));
    temp.remove(connection);
    connections = temp.toArray(new Connection[temp.size()]);

    pendingConnections.remove(connection.id);
  }

  // BOZO - Provide mechanism for sending to multiple clients without serializing multiple times.

  public void sendToAllTCP (Object object) {
    Connection[] connections = this.connections;
    for (int i = 0, n = connections.length; i < n; i++) {
      Connection connection = connections[i];
      connection.sendTCP(object);
    }
  }

  public void sendToAllExceptTCP (int connectionID, Object object) {
    Connection[] connections = this.connections;
    for (int i = 0, n = connections.length; i < n; i++) {
      Connection connection = connections[i];
      if (connection.id != connectionID) connection.sendTCP(object);
    }
  }

  public void sendToTCP (int connectionID, Object object) {
    Connection[] connections = this.connections;
    for (int i = 0, n = connections.length; i < n; i++) {
      Connection connection = connections[i];
      if (connection.id == connectionID) {
        connection.sendTCP(object);
        break;
      }
    }
  }

  public void sendToAllUDP (Object object) {
    Connection[] connections = this.connections;
    for (int i = 0, n = connections.length; i < n; i++) {
      Connection connection = connections[i];
      connection.sendUDP(object);
    }
  }

  public void sendToAllExceptUDP (int connectionID, Object object) {
    Connection[] connections = this.connections;
    for (int i = 0, n = connections.length; i < n; i++) {
      Connection connection = connections[i];
      if (connection.id != connectionID) connection.sendUDP(object);
    }
  }

  public void sendToUDP (int connectionID, Object object) {
    Connection[] connections = this.connections;
    for (int i = 0, n = connections.length; i < n; i++) {
      Connection connection = connections[i];
      if (connection.id == connectionID) {
        connection.sendUDP(object);
        break;
      }
    }
  }

  public void addListener (Listener listener) {
    if (listener == null) throw new IllegalArgumentException("listener cannot be null.");
    synchronized (listenerLock) {
      Listener[] listeners = this.listeners;
      int n = listeners.length;
      for (int i = 0; i < n; i++)
        if (listener == listeners[i]) return;
      Listener[] newListeners = new Listener[n + 1];
      newListeners[0] = listener;
      System.arraycopy(listeners, 0, newListeners, 1, n);
      this.listeners = newListeners;
    }
    if (TRACE) trace("kryonet", "Server listener added: " + listener.getClass().getName());
  }

  public void removeListener (Listener listener) {
    if (listener == null) throw new IllegalArgumentException("listener cannot be null.");
    synchronized (listenerLock) {
      Listener[] listeners = this.listeners;
      int n = listeners.length;
      Listener[] newListeners = new Listener[n - 1];
      for (int i = 0, ii = 0; i < n; i++) {
        Listener copyListener = listeners[i];
        if (listener == copyListener) continue;
        if (ii == n - 1) return;
        newListeners[ii++] = copyListener;
      }
      this.listeners = newListeners;
    }
    if (TRACE) trace("kryonet", "Server listener removed: " + listener.getClass().getName());
  }

  /** Closes all open connections and the server port(s). */
  public void close () {
    Connection[] connections = this.connections;
    if (INFO && connections.length > 0) info("kryonet", "Closing server connections...");
    for (int i = 0, n = connections.length; i < n; i++)
      connections[i].close();
    connections = new Connection[0];

    ServerSocketChannel serverChannel = this.serverChannel;
    if (serverChannel != null) {
      try {
        serverChannel.close();
        if (INFO) info("kryonet", "Server closed.");
      } catch (IOException ex) {
        if (DEBUG) debug("kryonet", "Unable to close server.", ex);
      }
      this.serverChannel = null;
    }

    UdpConnection udp = this.udp;
    if (udp != null) {
      udp.close();
      this.udp = null;
    }

    // Select one last time to complete closing the socket.
    synchronized (updateLock) {
      selector.wakeup();
      try {
        selector.selectNow();
      } catch (IOException ignored) {
      }
    }
  }

  /** Releases the resources used by this server, which may no longer be used. */
  public void dispose () throws IOException {
    close();
    selector.close();
  }

  public Thread getUpdateThread () {
    return updateThread;
  }

  /** Returns the current connections. The array returned should not be modified. */
  public Connection[] getConnections () {
    return connections;
  }
}
TOP

Related Classes of com.esotericsoftware.kryonet.Server

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.