package org.threadly.litesockets; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketException; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.concurrent.Executor; import org.threadly.concurrent.future.FutureUtils; import org.threadly.concurrent.future.ListenableFuture; import org.threadly.litesockets.buffers.ReuseableMergedByteBuffers; import org.threadly.util.Clock; /** * A Client representation of a UDP connection. * * This is the UDPClient for litesockets. The UDPClient is a little special as it is * not actually a selectable client. The Server actually does all the "Reading" for the socket. * * Since all UDP connections must has a bound port all UDPClients in litesockets are tight to a UDPServer. * The UDPClient is basically a unique host that is sending messages to the open UDP socket. * * Another unique aspect to UDPClients is there writing. When any form of "write" is called it is immediately done * on the socket. * */ @SuppressWarnings("deprecation") public class UDPClient extends Client { protected static final ListenableFuture<Boolean> COMPLETED_FUTURE = FutureUtils.immediateResultFuture(true); private final UDPSocketOptions uso = new UDPSocketOptions(); protected final long startTime = Clock.lastKnownForwardProgressingMillis(); protected final InetSocketAddress remoteAddress; protected final UDPServer udpServer; protected UDPClient(final InetSocketAddress sa, final UDPServer server) { super(server.getSocketExecuter()); this.remoteAddress = sa; udpServer = server; } @Override public boolean equals(final Object o) { if(o instanceof UDPClient && hashCode() == o.hashCode()) { final UDPClient u = (UDPClient)o; if(u.remoteAddress.equals(this.remoteAddress) && u.udpServer.getSelectableChannel().equals(udpServer.getSelectableChannel())) { return true; } } return false; } @Override public int hashCode() { return remoteAddress.hashCode() * udpServer.getSelectableChannel().hashCode(); } @Override protected SocketChannel getChannel() { return null; } @Override protected Socket getSocket() { return null; } @Override public boolean isClosed() { return this.closed.get(); } @Override public void close() { if(this.setClose()) { callClosers(); } } @Override public WireProtocol getProtocol() { return WireProtocol.UDP; } @Override public boolean canWrite() { return true; } @Override public int getWriteBufferSize() { return 0; } @Override protected ByteBuffer getWriteBuffer() { return null; } @Override protected void reduceWrite(final int size) { //UDPClient does not have pending writes to reduce } @Override public boolean hasConnectionTimedOut() { return false; } @Override public ListenableFuture<Boolean> connect() { return COMPLETED_FUTURE; } @Override public int getTimeout() { return 0; } @Override protected void setConnectionStatus(final Throwable t) { //UDP has no "connection" } @Override public InetSocketAddress getRemoteSocketAddress() { return remoteAddress; } @Override public InetSocketAddress getLocalSocketAddress() { return (InetSocketAddress)udpServer.getSelectableChannel().socket().getLocalSocketAddress(); } @Override public String toString() { return "UDPClient:FROM:"+getLocalSocketAddress()+":TO:"+getRemoteSocketAddress(); } @Override public ListenableFuture<?> write(final ByteBuffer bb) { addWriteStats(bb.remaining()); if(!closed.get()) { return udpServer.write(bb, remoteAddress); } return COMPLETED_FUTURE; } @Override public void setConnectionTimeout(final int timeout) { //No connection to Timeout } @Override public boolean setSocketOption(final SocketOption so, final int value) { try{ if(so == SocketOption.UDP_FRAME_SIZE) { this.udpServer.setFrameSize(value); return true; } else if (so == SocketOption.USE_NATIVE_BUFFERS) { this.useNativeBuffers = value == 1; return true; } } catch(Exception e) { } return false; } @Override public ClientOptions clientOptions() { return uso; } @Override protected void addReadBuffer(final ByteBuffer bb) { addReadStats(bb.remaining()); synchronized(readerLock) { readBuffers.add(bb); } callReader(); } @Override public ReuseableMergedByteBuffers getRead() { ReuseableMergedByteBuffers mbb = new ReuseableMergedByteBuffers(); int start = 0; int finished = 0; synchronized(readerLock) { start = getReadBufferSize(); mbb.add(readBuffers.pop()); finished = start - getReadBufferSize(); } if(start >= maxBufferSize && finished < maxBufferSize) { se.setClientOperations(this); } return mbb; } @Override public Executor getClientsThreadExecutor() { return se.getExecutorFor(remoteAddress); } /** * * @author lwahlmeier * */ private class UDPSocketOptions extends BaseClientOptions { @Override public boolean setSocketSendBuffer(int size) { int prev = getSocketSendBuffer(); try { udpServer.getSelectableChannel().socket().getSendBufferSize(); if(prev != getSocketSendBuffer()) { udpServer.setFrameSize(prev); return false; } return true; } catch (SocketException e) { return false; } } @Override public int getSocketSendBuffer() { try { return udpServer.getSelectableChannel().socket().getSendBufferSize(); } catch (SocketException e) { return -1; } } @Override public boolean setSocketRecvBuffer(int size) { int prev = getSocketRecvBuffer(); try { udpServer.getSelectableChannel().socket().getReceiveBufferSize(); if(prev != getSocketRecvBuffer()) { udpServer.setFrameSize(prev); return false; } return true; } catch (SocketException e) { return false; } } @Override public int getSocketRecvBuffer() { try { return udpServer.getSelectableChannel().socket().getReceiveBufferSize(); } catch (SocketException e) { return -1; } } @Override public boolean setUdpFrameSize(int size) { udpServer.setFrameSize(size); return true; } @Override public int getUdpFrameSize() { return udpServer.getFrameSize(); } } }