Package org.gradle.foundation.ipc.basic

Source Code of org.gradle.foundation.ipc.basic.Server$ServerObserver

/*
* Copyright 2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gradle.foundation.ipc.basic;

import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.foundation.common.ObserverLord;

import java.io.IOException;
import java.io.Serializable;
import java.net.ServerSocket;
import java.net.Socket;

/**
* This is a server that talks to a client via sockets (Rudimentary form of Inter-Process Communication (IPC)). This
* does the work of locating a free socket and starting the connection. To use this, you really only have to define a
* Protocol that handles the actual messages. You'll want to make your client startup a ClientProcess object that
* implements a corresponding Protocol.
*
* @author mhunsicker
  */
public class Server<P extends Server.Protocol, O extends Server.ServerObserver>
{
   private final Logger logger = Logging.getLogger( Server.class );

   private ServerSocket serverSocket;
   private boolean isServerRunning;
   private boolean hasRequestedShutdown;

   private ObjectSocketWrapper clientSocket;
   protected P protocol;
   private Thread communicationThread;
   private int port;

   protected ObserverLord<O> observerLord = new ObserverLord<O>();

   //

    /**
     * Implement this to define the behavior of the communication on the server side.
     */
    public interface Protocol<S extends Server>
    {
        /**
         * Gives your protocol a chance to store this server so it can access its functions.
         */
        public void initialize( S server );

        /**
         * Notification that the connection was accepted by the client.
         */
        public void connectionAccepted();

        /**
         * @return true if we should keep the connection alive. False if we should stop communication.
         */
        public boolean continueConnection();

        /**
         * Notification that a message has been received.
         *
         * @param message the message that was received.
         */
        public void messageReceived( MessageObject message );

        /**
         * Notification that the client has stopped all communications.
         */
        public void clientCommunicationStopped();

        /**
         * Notification that a read failure occurred. This really only exists for debugging purposes when things
         * go wrong.
         */
        void readFailureOccurred();
    }

    //
    public interface ServerObserver
    {
        /**
         * Notification that the server has shutdown.
         */
        public void serverExited();
    }

   public Server( P protocol )
   {
      this.protocol = protocol;
      protocol.initialize( this );
   }

   public int getPort() { return port; }

   /**
     * Call this to start the server.
     *
     * @return true if we started, false if not.
   */
   public boolean start()
   {
      port = connect();
        if (port == -1) {
         return false;
        }

      communicationThread = new Thread( new Runnable()
      {
         public void run()
         {
            listenForConnections();
         }
      });

      communicationThread.start();

      communicationsStarted();

      return true;
   }

   /**
   * this exists solely so it can be overridden. Its an internal notification that communcations have started.
   * You may want to do some extra processing now.
   */

    protected void communicationsStarted() {

   }

   /**
     * This attempts to open a free port. We'll search for an open port until we find one.
     *
     * @return the port we opened or -1 if we couldn't open one.
   */
   private int connect()
   {
       try {
           serverSocket = new ServerSocket(0);
           return serverSocket.getLocalPort();
       } catch (IOException e) {
           logger.error( "Could not listen on port: " + port, e );
           return -1;
       }
   }

   /**
     * This sits in a loop and listens for connections. Once a connection has been made, we'll call another function to
     * process it.
   */
   private void listenForConnections()
   {
      int consecutiveFailures = 0;
      while( !hasRequestedShutdown )
      {
         Socket socket = null;
         try
         {
            serverSocket.setSoTimeout( 2000 )//attempt to connect for a few seconds, then try again (so we'll get any shutdown requests).
            socket = serverSocket.accept();

            clientSocket = new ObjectSocketWrapper( socket );
            protocol.connectionAccepted();
            consecutiveFailures = 0;   //reset our consecutive failures.
            serverSocket.setSoTimeout( 0 );

            processCommunications();

            clientSocket.close();
         }
         catch( IOException e )
         {
            consecutiveFailures++;
            if( consecutiveFailures >= 20 //if we fail too many times, we'll request to shutdown. It's obviously not working. This is an arbitrary number.
            {
               requestShutdown();
            }

            if( consecutiveFailures > 8 )    //the first few usually fail while we're waiting for the process to startup.
            {
               logger.error( "Accept failed (" + consecutiveFailures + ")." );
            }
         }
         catch( Throwable t ) {  //something really bad happened, shut down
            logger.error( "Listening for connections", t );
            requestShutdown();
         }
      }

      isServerRunning = false;

      stop();
      notifyServerExited();
   }

   /**
     * This is called once a connection is made. We'll listen for messages from the client, notifying the protocol of
     * them to do whatever it needs.
   */
   private void processCommunications()
   {
      boolean hasClientStopped = false;
      int failureCount = 0;
      while( !hasClientStopped && protocol.continueConnection() && !hasRequestedShutdown )
      {
         Object object = clientSocket.readObject();

         if( object == null )
         {
            if( !hasRequestedShutdown )   //if we're trying to shutdown, we can get errors here. Just ignore them and move on
            {
               failureCount++;
               protocol.readFailureOccurred();
               if( failureCount == 3 ) //after 3 failures, assume the client went away.
               {
                  hasClientStopped = true;
                  protocol.clientCommunicationStopped();
               }
            }
         }
         else
         {
            failureCount = 0; //reset our failures

                if (object instanceof String) {
               protocol.messageReceived( new MessageObject( "?", object.toString(), null ) );
                } else if (object instanceof MessageObject) {
                  protocol.messageReceived( (MessageObject) object );
                }
          }
        }
    }

   public void requestShutdown() { hasRequestedShutdown = true; }

   public boolean isServerRunning() { return isServerRunning; }

   /**
     * Call this to send a message. The protocal and the client must understand the message and message type.
     *
     * @param messageType the message type. Whatever the client and server want.
     * @param message the message being sent.
   */
   public void sendMessage( String messageType, String message )
   {
      clientSocket.sendObject( new MessageObject( messageType, message, null ) );
   }

   /**
     * Call this to send a message with some binary data. The protocal and the client must understand the message,
     * message type, and data.
     *
     * @param messageType the message type. Whatever the client and server want.
     * @param message the message being sent
     * @param data the data being sent. Must be serializable.
   */
   public void sendMessage( String messageType, String message, Serializable data )
   {
      clientSocket.sendObject( new MessageObject( messageType, message, data ) );
   }


   public void stop()
   {
      try
      {
         serverSocket.close();
      }
      catch( IOException e )
      {
         logger.error( "Closing socket", e );
      }
   }

   private void notifyServerExited()
   {
      observerLord.notifyObservers( new ObserverLord.ObserverNotification<O>()
      {
         public void notify( ServerObserver observer )
         {
            observer.serverExited();
         }
      } );
   }

   public void addServerObserver( O observer, boolean inEventQueue )
   {
      observerLord.addObserver( observer, inEventQueue );
   }

   public void removeServerObserver( O observer )
   {
      observerLord.removeObserver( observer );
   }
}
TOP

Related Classes of org.gradle.foundation.ipc.basic.Server$ServerObserver

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.