All Downloads are FREE. Search and download functionalities are using the official Maven repository.

lowentry.ue4.classes.sockets.SocketClient Maven / Gradle / Ivy

There is a newer version: 3.0.1
Show newest version
package lowentry.ue4.classes.sockets;


import lowentry.ue4.library.LowEntry;
import lowentry.ue4.libs.pyronet.jawnae.pyronet.PyroClient;
import lowentry.ue4.libs.pyronet.jawnae.pyronet.PyroException;
import lowentry.ue4.libs.pyronet.jawnae.pyronet.PyroSelector;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;


public class SocketClient
{
	protected final SocketServer server;
	protected final PyroClient   client;
	
	protected InetSocketAddress clientUdpAddress;
	
	protected volatile Object attachment;
	
	protected boolean isDisconnecting = false;
	
	protected boolean            isHandshakeCompleted            = false;
	protected boolean            isWebsocket                     = false;
	protected Collection bufferedMessagesDuringHandshake = null;
	
	protected volatile String addressText = null;
	
	protected final int hashCode;
	
	
	public SocketClient(final SocketServer server, final PyroClient client)
	{
		this.server = server;
		this.client = client;
		this.hashCode = super.hashCode();
	}
	
	
	/**
	 * Call this after receiving the remote UDP port.
	 */
	protected void setRemoteUdpPort(final int port)
	{
		if(port <= 0)
		{
			clientUdpAddress = null;
		}
		else
		{
			clientUdpAddress = new InetSocketAddress(client.getRemoteAddress().getAddress(), port);
		}
		
		if(SocketServer.IS_DEBUGGING)
		{
			SocketServer.DEBUGGING_PRINTSTREAM.println("[DEBUG] " + this + " has send UDP port " + port + " which resulted in UDP address: " + clientUdpAddress);
		}
	}
	
	
	/**
	 * Attach any object to a client, for example to store session information.
* Only one object can be attached at a time! */ public void setAttachment(final Object attachment) { this.attachment = attachment; } /** * Returns the previously attached object, or null if none is set.
* Call this function like this:
*
* YourClass object = client.getAttachment();
*
* This will automatically take the type that the compiler thinks it has to be.
* To control the type manually, call this function like this:
*
* YourClass object = client.<YourClass>getAttachment(); */ @SuppressWarnings("unchecked") public T getAttachment() { return (T) attachment; } /** * Returns true if the client has an attachment. */ public boolean hasAttachment() { return (attachment != null); } /** * Returns the local socket address (host and port), can return null. */ public InetSocketAddress getLocalAddress() { return client.getLocalAddress(); } /** * Returns the remote socket address (host and port), can return null. */ public InetSocketAddress getRemoteAddress() { return client.getRemoteAddress(); } /** * Returns the remote UDP socket address (host and port), can return null. */ public InetSocketAddress getRemoteAddressUdp() { return clientUdpAddress; } /** * Returns the remote UDP port, returns 0 if there is no UDP connection. */ public int getRemotePortUdp() { if(clientUdpAddress == null) { return 0; } return clientUdpAddress.getPort(); } /** * Returns the IP, can return null. */ public InetAddress getIp() { return client.getInetAddress(); } /** * Returns the IP as a String, can return null. */ public String getIpString() { InetAddress ip = getIp(); if(ip == null) { return null; } return ip.getHostAddress(); } /** * Will set flags and send pending data. */ protected void onHandshakeCompleted(final boolean isWebsocket) { if(this.isHandshakeCompleted) { return; } this.isHandshakeCompleted = true; this.isWebsocket = isWebsocket; if(bufferedMessagesDuringHandshake != null) { for(byte[] message : bufferedMessagesDuringHandshake) { sendMessage(message); } bufferedMessagesDuringHandshake = null; } } /** * Returns true if a handshaking message has been received and has been responded to. */ protected boolean isHandshakeCompleted() { return isHandshakeCompleted; } /** * Returns true if this client uses a websocket.
*
* NOTE: only works after handshaking has completed. */ protected boolean isWebsocket() { return isWebsocket; } /** * Will send the bytes immediately. */ public void sendUnreliableMessage(final byte[]... bytes) { sendUnreliableMessage(LowEntry.mergeBytes(bytes)); } /** * Will send the bytes immediately. */ public void sendUnreliableMessage(final byte[] bytes) { if(client.selector().isNetworkThread()) { if(bytes == null) { sendUnreliableMessageCode(ByteBuffer.allocate(0), true); } else { sendUnreliableMessageCode(ByteBuffer.wrap(bytes), true); } } else { if(bytes == null) { client.selector().scheduleTask(() -> sendUnreliableMessageCode(ByteBuffer.allocate(0), true)); } else { client.selector().scheduleTask(() -> sendUnreliableMessageCode(ByteBuffer.wrap(bytes), true)); } } } /** * Will send the bytes immediately, the ByteBuffer can be cleared and reused after calling this function. */ public void sendUnreliableMessage(final ByteBuffer bytes) { if(client.selector().isNetworkThread()) { if(bytes == null) { sendUnreliableMessageCode(ByteBuffer.allocate(0), true); } else { sendUnreliableMessageCode(bytes, false); } } else { if(bytes == null) { client.selector().scheduleTask(() -> sendUnreliableMessageCode(ByteBuffer.allocate(0), true)); } else { final ByteBuffer b = LowEntry.cloneByteBuffer(bytes, false); client.selector().scheduleTask(() -> sendUnreliableMessageCode(b, true)); } } } protected void sendUnreliableMessageCode(final ByteBuffer bytes, final boolean clonedBytes) { if(server.serverUdp == null) { return; } if(isWebsocket()) { if(!isConnected()) { return; } if(!isHandshakeCompleted()) { return; } byte opcode = -128 | 2; // FIN + BINARY DATA int size = (1 + SocketFunctions.uintByteCount(bytes.remaining()) + bytes.remaining()); ByteBuffer buffer = ByteBuffer.allocate(1 + SocketFunctions.websocketSizeByteCount(size) + 1 + SocketFunctions.uintByteCount(bytes.remaining())); buffer.put(opcode); SocketFunctions.putWebsocketSizeBytes(buffer, size); buffer.put(SocketMessageType.SIMULATED_UNRELIABLE_MESSAGE); SocketFunctions.putUint(buffer, bytes.remaining()); buffer.flip(); try { client.write(buffer); if(bytes.remaining() > 0) { if(clonedBytes) { client.write(bytes); } else { client.writeCopy(bytes); } } } catch(PyroException e) { if(SocketServer.IS_DEBUGGING) { SocketServer.DEBUGGING_PRINTSTREAM.println("[DEBUG] " + this + " can't be send an unreliable message:"); SocketServer.DEBUGGING_PRINTSTREAM.println(LowEntry.getStackTrace(e)); } } } else { if(!isConnectedUdp()) { return; } if(!isHandshakeCompleted()) { return; } if(clientUdpAddress == null) { return; } server.serverUdp.write(bytes, clientUdpAddress); } } /** * Will enqueue the bytes to send them. */ public void sendMessage(final byte[]... bytes) { sendMessage(LowEntry.mergeBytes(bytes)); } /** * Will enqueue the bytes to send them. */ public void sendMessage(final byte[] bytes) { final byte[] b = ((bytes == null) ? new byte[0] : bytes); if(client.selector().isNetworkThread()) { sendMessageCode(b); } else { client.selector().scheduleTask(() -> sendMessageCode(b)); } } protected void sendMessageCode(final byte[] bytes) { if(!isConnected()) { return; } if(!isHandshakeCompleted()) { if(bufferedMessagesDuringHandshake == null) { bufferedMessagesDuringHandshake = new ArrayList<>(); } bufferedMessagesDuringHandshake.add(bytes); return; } ByteBuffer buffer; if(isWebsocket()) { byte opcode = -128 | 2; // FIN + BINARY DATA int size = (1 + SocketFunctions.uintByteCount(bytes.length) + bytes.length); buffer = ByteBuffer.allocate(1 + SocketFunctions.websocketSizeByteCount(size) + 1 + SocketFunctions.uintByteCount(bytes.length)); buffer.put(opcode); SocketFunctions.putWebsocketSizeBytes(buffer, size); } else { buffer = ByteBuffer.allocate(1 + SocketFunctions.uintByteCount(bytes.length)); } buffer.put(SocketMessageType.MESSAGE); SocketFunctions.putUint(buffer, bytes.length); buffer.flip(); try { client.write(buffer); if(bytes.length > 0) { client.write(ByteBuffer.wrap(bytes)); } } catch(PyroException e) { if(SocketServer.IS_DEBUGGING) { SocketServer.DEBUGGING_PRINTSTREAM.println("[DEBUG] " + this + " can't be send a message:"); SocketServer.DEBUGGING_PRINTSTREAM.println(LowEntry.getStackTrace(e)); } } } /** * Will enqueue the bytes to send them. */ protected void sendFunctionCallResponse(final int functionCallId, final byte[] bytes) { final byte[] b = ((bytes == null) ? new byte[0] : bytes); if(client.selector().isNetworkThread()) { sendFunctionCallResponseCode(functionCallId, b); } else { client.selector().scheduleTask(() -> sendFunctionCallResponseCode(functionCallId, b)); } } protected void sendFunctionCallResponseCode(final int functionCallId, final byte[] bytes) { if(!isConnected()) { return; } if(!isHandshakeCompleted()) { // not possible return; } ByteBuffer buffer; if(isWebsocket()) { byte opcode = -128 | 2; // FIN + BINARY DATA int size = (1 + SocketFunctions.uintByteCount(functionCallId) + SocketFunctions.uintByteCount(bytes.length) + bytes.length); buffer = ByteBuffer.allocate(1 + SocketFunctions.websocketSizeByteCount(size) + 1 + SocketFunctions.uintByteCount(functionCallId) + SocketFunctions.uintByteCount(bytes.length)); buffer.put(opcode); SocketFunctions.putWebsocketSizeBytes(buffer, size); } else { buffer = ByteBuffer.allocate(1 + SocketFunctions.uintByteCount(functionCallId) + SocketFunctions.uintByteCount(bytes.length)); } buffer.put(SocketMessageType.FUNCTION_CALL_RESPONSE); SocketFunctions.putUint(buffer, functionCallId); SocketFunctions.putUint(buffer, bytes.length); buffer.flip(); try { client.write(buffer); if(bytes.length > 0) { client.write(ByteBuffer.wrap(bytes)); } } catch(PyroException e) { if(SocketServer.IS_DEBUGGING) { SocketServer.DEBUGGING_PRINTSTREAM.println("[DEBUG] " + this + " can't be send a function call response:"); SocketServer.DEBUGGING_PRINTSTREAM.println(LowEntry.getStackTrace(e)); } } } /** * Will enqueue the bytes to send them. */ protected void sendLatentFunctionCallResponse(final int functionCallId, final byte[] bytes) { final byte[] b = ((bytes == null) ? new byte[0] : bytes); if(client.selector().isNetworkThread()) { sendLatentFunctionCallResponseCode(functionCallId, b); } else { client.selector().scheduleTask(() -> sendLatentFunctionCallResponseCode(functionCallId, b)); } } protected void sendLatentFunctionCallResponseCode(final int functionCallId, final byte[] bytes) { if(!isConnected()) { return; } if(!isHandshakeCompleted()) { // not possible return; } ByteBuffer buffer; if(isWebsocket()) { byte opcode = -128 | 2; // FIN + BINARY DATA int size = (1 + SocketFunctions.uintByteCount(functionCallId) + SocketFunctions.uintByteCount(bytes.length) + bytes.length); buffer = ByteBuffer.allocate(1 + SocketFunctions.websocketSizeByteCount(size) + 1 + SocketFunctions.uintByteCount(functionCallId) + SocketFunctions.uintByteCount(bytes.length)); buffer.put(opcode); SocketFunctions.putWebsocketSizeBytes(buffer, size); } else { buffer = ByteBuffer.allocate(1 + SocketFunctions.uintByteCount(functionCallId) + SocketFunctions.uintByteCount(bytes.length)); } buffer.put(SocketMessageType.LATENT_FUNCTION_CALL_RESPONSE); SocketFunctions.putUint(buffer, functionCallId); SocketFunctions.putUint(buffer, bytes.length); buffer.flip(); try { client.write(buffer); if(bytes.length > 0) { client.write(ByteBuffer.wrap(bytes)); } } catch(PyroException e) { if(SocketServer.IS_DEBUGGING) { SocketServer.DEBUGGING_PRINTSTREAM.println("[DEBUG] " + this + " can't be send a latent function call response:"); SocketServer.DEBUGGING_PRINTSTREAM.println(LowEntry.getStackTrace(e)); } } } /** * Will enqueue the bytes to send them. */ protected void sendLatentFunctionCallCancel(final int functionCallId) { if(client.selector().isNetworkThread()) { sendLatentFunctionCallCancelCode(functionCallId); } else { client.selector().scheduleTask(() -> sendLatentFunctionCallCancelCode(functionCallId)); } } protected void sendLatentFunctionCallCancelCode(final int functionCallId) { if(!isConnected()) { return; } if(!isHandshakeCompleted()) { // not possible return; } ByteBuffer buffer; if(isWebsocket()) { byte opcode = -128 | 2; // FIN + BINARY DATA int size = (1 + SocketFunctions.uintByteCount(functionCallId)); buffer = ByteBuffer.allocate(1 + SocketFunctions.websocketSizeByteCount(size) + 1 + SocketFunctions.uintByteCount(functionCallId)); buffer.put(opcode); SocketFunctions.putWebsocketSizeBytes(buffer, size); } else { buffer = ByteBuffer.allocate(1 + SocketFunctions.uintByteCount(functionCallId)); } buffer.put(SocketMessageType.LATENT_FUNCTION_CALL_CANCELED); SocketFunctions.putUint(buffer, functionCallId); buffer.flip(); try { client.write(buffer); } catch(PyroException e) { if(SocketServer.IS_DEBUGGING) { SocketServer.DEBUGGING_PRINTSTREAM.println("[DEBUG] " + this + " can't be send a latent function call cancel:"); SocketServer.DEBUGGING_PRINTSTREAM.println(LowEntry.getStackTrace(e)); } } } /** * Will enqueue the bytes to send them. */ protected void sendConnectionValidationResponse(final int functionCallId) { if(client.selector().isNetworkThread()) { sendConnectionValidationResponseCode(functionCallId); } else { client.selector().scheduleTask(() -> sendConnectionValidationResponseCode(functionCallId)); } } protected void sendConnectionValidationResponseCode(final int functionCallId) { if(!isConnected()) { return; } if(!isHandshakeCompleted()) { // not possible return; } ByteBuffer buffer; if(isWebsocket()) { byte opcode = -128 | 2; // FIN + BINARY DATA int size = (1 + SocketFunctions.uintByteCount(functionCallId)); buffer = ByteBuffer.allocate(1 + SocketFunctions.websocketSizeByteCount(size) + 1 + SocketFunctions.uintByteCount(functionCallId)); buffer.put(opcode); SocketFunctions.putWebsocketSizeBytes(buffer, size); } else { buffer = ByteBuffer.allocate(1 + SocketFunctions.uintByteCount(functionCallId)); } buffer.put(SocketMessageType.CONNECTION_VALIDATION_RESPONSE); SocketFunctions.putUint(buffer, functionCallId); buffer.flip(); try { client.write(buffer); } catch(PyroException e) { if(SocketServer.IS_DEBUGGING) { SocketServer.DEBUGGING_PRINTSTREAM.println("[DEBUG] " + this + " can't be send a connection validation response:"); SocketServer.DEBUGGING_PRINTSTREAM.println(LowEntry.getStackTrace(e)); } } } /** * Gracefully shuts down the connection. The connection is closed after the last outbound bytes are sent. Enqueuing new bytes after shutdown will not do anything. */ public void disconnect() { if(client.selector().isNetworkThread()) { if(isDisconnecting) { return; } isDisconnecting = true; sendCloseMessage(); client.shutdown(); } else { client.selector().scheduleTask(() -> { if(isDisconnecting) { return; } isDisconnecting = true; sendCloseMessage(); client.shutdown(); }); } } /** * Immediately drop the connection, regardless of any pending outbound bytes. Actual behavior depends on the socket linger settings. */ public void disconnectImmediately() { if(client.selector().isNetworkThread()) { if(isDisconnecting) { return; } isDisconnecting = true; client.dropConnection(); } else { client.selector().scheduleTask(() -> { if(isDisconnecting) { return; } isDisconnecting = true; client.dropConnection(); }); } } /** * Sends a close message to the client.
*
* This is required to do so when closing the clients connection. */ protected void sendCloseMessage() { if(!isHandshakeCompleted()) { return; } if(isWebsocket()) { try { client.write(ByteBuffer.wrap(new byte[]{-128 | 8})); // FIN + CLOSE } catch(PyroException e) { if(SocketServer.IS_DEBUGGING) { SocketServer.DEBUGGING_PRINTSTREAM.println("[DEBUG] " + this + " can't be send FIN + CLOSE (websocket):"); SocketServer.DEBUGGING_PRINTSTREAM.println(LowEntry.getStackTrace(e)); } } } } /** * Returns true when the client is connected.
*
* WARNING: Only call this on the same thread this object was created in! Use {@link #execute(Runnable)} in case of doubt.
*/ public boolean isConnected() { client.selector().checkThread(); return (!isDisconnecting && !client.isDisconnected()); } /** * Returns true when the client is connected (ment for UDP methods). */ protected boolean isConnectedUdp() { return !isDisconnecting; } /** * Returns the PyroNet object of this wrapper. */ public PyroClient pyro() { return client; } /** * Returns the server this client is connected to. */ public SocketServer server() { return server; } /** * Returns the selector. */ public PyroSelector selector() { return client.selector(); } /** * This function will execute the given runnable on the thread this object was created in. */ public void execute(Runnable runnable) { if(client.selector().isNetworkThread()) { runnable.run(); } else { client.selector().scheduleTask(runnable); } } /** * Returns true if this is the thread this object was created in. */ public boolean isNetworkThread() { return client.selector().isNetworkThread(); } protected void saveAddressText() { if(client == null) { addressText = "closed"; return; } addressText = client.getAddressText(); } public String getAddressText() { final String addressText = this.addressText; if(addressText != null) { return addressText; } if(client == null) { return "closed"; } return client.getAddressText(); } @Override public String toString() { return getClass().getSimpleName() + "[" + getAddressText() + "]"; } @Override public int hashCode() { return hashCode; } @Override public boolean equals(Object o) { return (this == o); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy