
org.gradle.foundation.ipc.basic.Server Maven / Gradle / Ivy
Show all versions of gradle-api Show documentation
/*
* 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.
*/
public class Server {
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 observerLord = new ObserverLord();
//
/**
* Implement this to define the behavior of the communication on the server side.
*/
public interface Protocol {
/**
* 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() {
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);
}
}