This repository has been archived on 2021-12-05. You can view files and clone it, but cannot push or open issues or pull requests.
envoy/client/src/main/java/envoy/client/net/Client.java

217 lines
6.0 KiB
Java

package envoy.client.net;
import java.io.*;
import java.net.Socket;
import java.util.concurrent.TimeoutException;
import java.util.logging.*;
import dev.kske.eventbus.core.*;
import dev.kske.eventbus.core.Event;
import envoy.data.*;
import envoy.event.*;
import envoy.util.*;
import envoy.client.data.ClientConfig;
import envoy.client.event.EnvoyCloseEvent;
/**
* Establishes a connection to the server, performs a handshake and delivers certain objects to the
* server.
*
* @author Kai S. K. Engelbart
* @author Maximilian Käfer
* @author Leon Hofmeister
* @since Envoy Client v0.1-alpha
*/
public final class Client implements Closeable {
// Connection handling
private Socket socket;
private Receiver receiver;
private boolean online;
// Asynchronously initialized during handshake
private volatile User sender;
private volatile boolean rejected;
// Configuration, logging and event management
private static final ClientConfig config = ClientConfig.getInstance();
private static final Logger logger = EnvoyLog.getLogger(Client.class);
private static final EventBus eventBus = EventBus.getInstance();
/**
* Constructs a client and registers it as an event listener.
*
* @since Envoy Client v0.2-beta
*/
public Client() {
eventBus.registerListener(this);
}
/**
* Enters the online mode by acquiring a user ID from the server. As a connection has to be
* established and a handshake has to be made, this method will block for up to 5 seconds. If
* the handshake does exceed this time limit, an exception is thrown.
*
* @param credentials the login credentials of the user
* @throws TimeoutException if the server could not be reached
* @throws IOException if the login credentials could not be written
* @throws InterruptedException if the current thread is interrupted while waiting for the
* handshake response
*/
public void performHandshake(LoginCredentials credentials)
throws TimeoutException, IOException, InterruptedException {
if (online)
throw new IllegalStateException("Handshake has already been performed successfully");
rejected = false;
// Establish TCP connection
logger.log(Level.FINER, String.format("Attempting connection to server %s:%d...",
config.getServer(), config.getPort()));
socket = new Socket(config.getServer(), config.getPort());
logger.log(Level.FINE, "Successfully established TCP connection to server");
// Create object receiver
receiver = new Receiver(socket.getInputStream());
// Register user creation processor, contact list processor, message cache and
// authentication token
receiver.registerProcessor(User.class, sender -> this.sender = sender);
// Start receiver
receiver.start();
// Write login credentials
SerializationUtils.writeBytesWithLength(credentials, socket.getOutputStream());
// Wait for a maximum of five seconds to acquire the sender object
final long start = System.currentTimeMillis();
while (sender == null) {
// Quit immediately after handshake rejection
// This method can then be called again
if (rejected) {
socket.close();
receiver.removeAllProcessors();
return;
}
if (System.currentTimeMillis() - start > 5000) {
rejected = true;
socket.close();
receiver.removeAllProcessors();
throw new TimeoutException("Did not log in after 5 seconds");
}
Thread.sleep(500);
}
// Remove handshake specific processors
receiver.removeAllProcessors();
online = true;
logger.log(Level.INFO, "Handshake completed.");
}
/**
* Sends an object to the server.
*
* @param obj the object to send
* @throws IllegalStateException if the client is not online
* @throws RuntimeException if the object serialization failed
* @since Envoy Client v0.2-beta
*/
public void send(Serializable obj) throws IllegalStateException, RuntimeException {
checkOnline();
logger.log(Level.FINE, "Sending " + obj);
try {
SerializationUtils.writeBytesWithLength(obj, socket.getOutputStream());
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
/**
* Sends a message to the server. The message's status will be incremented once it was delivered
* successfully.
*
* @param message the message to send
* @since Envoy Client v0.3-alpha
*/
public void sendMessage(Message message) {
send(message);
message.nextStatus();
}
/**
* Requests a new {@link IDGenerator} from the server.
*
* @since Envoy Client v0.3-alpha
*/
public void requestIDGenerator() {
logger.log(Level.INFO, "Requesting new id generator...");
send(new IDGeneratorRequest());
}
@Event(HandshakeRejection.class)
@Priority(1000)
private void onHandshakeRejection() {
rejected = true;
}
@Override
@Event(EnvoyCloseEvent.class)
@Priority(50)
public void close() {
if (online) {
logger.log(Level.INFO, "Closing connection...");
try {
// The sender must be reset as otherwise the handshake is immediately closed
sender = null;
online = false;
socket.close();
} catch (final IOException e) {
logger.log(Level.WARNING, "Failed to close socket: ", e);
}
}
}
/**
* Ensured that the client is online.
*
* @throws IllegalStateException if the client is not online
* @since Envoy Client v0.3-alpha
*/
private void checkOnline() throws IllegalStateException {
if (!online)
throw new IllegalStateException("Client is not online");
}
/**
* @return the {@link User} as which this client is logged in
* @since Envoy Client v0.1-alpha
*/
public User getSender() { return sender; }
/**
* Sets the client user which is used to send messages.
*
* @param clientUser the client user to set
* @since Envoy Client v0.2-alpha
*/
public void setSender(User clientUser) { sender = clientUser; }
/**
* @return the {@link Receiver} used by this {@link Client}
* @since v0.2-alpha
*/
public Receiver getReceiver() { return receiver; }
/**
* @return {@code true} if a connection to the server could be established
* @since Envoy Client v0.2-alpha
*/
public boolean isOnline() { return online; }
}