/* * Copyright 2004 - 2008 Christian Sprajc, Dennis Waldherr. All rights reserved. * * This file is part of PowerFolder. * * PowerFolder is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation. * * PowerFolder is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with PowerFolder. If not, see <http://www.gnu.org/licenses/>. * * $Id$ */ package de.dal33t.powerfolder.util.net; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Logger; import de.dal33t.powerfolder.util.os.OSUtil; /** * Basic wrapper for a UDT socket in java. Currently it's not possible to adjust * the buffer sizes used in the native implementation in java. For this reason, * expect alot of memory usage from creating such a socket. <br> * Actually, the amount used should be around 22MB per socket (if one looks at * the default buffer sizes). * * @author Dennis "Bytekeeper" Waldherr */ public class UDTSocket { private static final Logger LOG = Logger.getLogger(UDTSocket.class .getName()); private class UDTInputStream extends InputStream { @Override public int read() throws IOException { byte b[] = new byte[1]; try { return recv(b, 0, 1); } catch (IOException e) { connected = false; throw e; } } @Override public int read(byte[] b, int off, int len) throws IOException { if (off < 0 || len < 0 || len > b.length - off) { throw new IndexOutOfBoundsException(b.length + " from:" + off + " len:" + len); } try { return recv(b, off, len); } catch (IOException e) { connected = false; throw e; } } } private class UDTOutputStream extends OutputStream { @Override public void write(byte[] b, int off, int len) throws IOException { if (off < 0 || len < 0 || len > b.length - off) { throw new IndexOutOfBoundsException(b.length + " from:" + off + " len:" + len); } try { send(b, off, len); } catch (IOException e) { connected = false; throw e; } } @Override public void write(int b) throws IOException { byte buf[] = new byte[]{(byte) b}; try { send(buf, 0, 1); } catch (IOException e) { connected = false; throw e; } } } private static boolean supported = false; public static final AtomicInteger openSockets = new AtomicInteger(0); static { // Supported on windows only ATM. if (OSUtil.isWindowsSystem() && OSUtil.loadLibrary(UDTSocket.class, "udt") && OSUtil.loadLibrary(UDTSocket.class, "udt4j")) { initIDs(); supported = true; } } /** * @return true if UDTSockets are supported. */ public static boolean isSupported() { return supported; } private volatile boolean closed = false; private volatile boolean connected = false; private InputStream in; private OutputStream out; // Used in native code! private int sock = -1; private InetSocketAddress remoteAddress; /** * Creates an unbound, unconnected socket. */ public UDTSocket() { sock = socket(); if (openSockets.incrementAndGet() > 20) { LOG.warning("Many open UDT sockets (" + openSockets.get() + ')'); } } // Used in native code! private UDTSocket(int sock) { this.sock = sock; connected = true; } /** * Accepts a connection. This socket must be bound and in listen state * before calling this method. Otherwise an IOException is thrown * * @return the accepted connection socket * @throws IOException */ public UDTSocket accept() throws IOException { UDTSocket s = acceptImpl(); s.remoteAddress = s.getRemoteAddressImpl(); return s; } /** * Binds this socket to a local address. * * @param bindPoint * @throws IOException */ public native void bind(InetSocketAddress bindPoint) throws IOException; /** * Closes the socket. Releases all native resources. Further reads and * writes on this socket will fail after calling this method. * * @throws IOException */ public void close() throws IOException { // Whatever happens in the actual native code - we can't use this object // anymore closed = true; connected = false; closeImpl(); openSockets.decrementAndGet(); } /** * Tries to connect to the given address. There is no timeout option exposed * by the native API, but the connection attempt will fail after some * hardcoded internal value. * * @param endPoint * @throws IOException * if the connection attempt failed. */ public void connect(InetSocketAddress endPoint) throws IOException { connectImpl(endPoint); // If no exception occurred, we're now connected connected = true; remoteAddress = getRemoteAddressImpl(); } /** * Returns an InputStream for this socket. * * @return * @throws IOException */ public InputStream getInputStream() throws IOException { if (in == null) { in = new UDTInputStream(); } return in; } /** * Returns the address this socket was bound to. * * @return the address or null, if it hasn't been bound yet. */ public native InetSocketAddress getLocalAddress(); /** * Returns an OutputStream for this socket. * * @return * @throws IOException */ public OutputStream getOutputStream() throws IOException { if (out == null) { out = new UDTOutputStream(); } return out; } /** * Returns the address of the remote peer. * * @return the address or null, if it's not connected */ public InetSocketAddress getRemoteAddress() { return remoteAddress; } /** * Returns true if the socket is closed * * @return */ public boolean isClosed() { return closed; } /** * Returns true if the socket is connected * * @return */ public boolean isConnected() { return connected; } /** * Sets this socket to listen state (ServerSocket). Bind() must be be called * before invoking this method. * * @param backlog * the maximum number of pending incoming connections * @throws IOException * if an error occured */ public native void listen(int backlog) throws IOException; /** * Sets the option value for UDT_RENDEZVOUS. In rendezvous mode, both peers * have to to connect to each other, instead of one peer listening and the * other connecting. This can be (and is) used to perform UDP hole punching. * * @param enabled */ public native void setSoRendezvous(boolean enabled); /** * Returns the option value for UDT_RENDEZVOUS. * * @return */ public native boolean getSoRendezvous(); /** * Sets the SO_LINGER option. Sets the time close() will wait for * sending/receiving before closing the connection. * * @param true to linger on * @param seconds * how long to linger, if on is true */ public native void setSoLinger(boolean on, int seconds); /** * Returns the time to linger in seconds. * * @return the linger setting, or -1 if it's not on */ public native int getSoLinger(); /** * The receiver buffer limit is used to limit the size of temporary storage * of receiving data. Recommended size: Bandwidth * RTT * * @param value */ public native void setSoReceiverBufferLimit(int value) throws IOException; /** * The receiver buffer limit is used to limit the size of temporary storage * of receiving data. Recommended size: Bandwidth * RTT * * @return the currrent buffer limit */ public native int getSoReceiverBufferLimit() throws IOException; /** * The sender buffer limit is used to limit the size of temporary storage of * sending data. Recommended size: Bandwidth * RTT * * @param value */ public native void setSoSenderBufferLimit(int value) throws IOException; /** * The sender buffer limit is used to limit the size of temporary storage of * sending data. Recommended size: Bandwidth * RTT * * @return the current buffer limit */ public native int getSoSenderBufferLimit() throws IOException; /** * The UDP buffer size used for receiving. This can be relatively small. * * @param value */ public native void setSoUDPReceiverBufferSize(int value) throws IOException; /** * The UDP buffer size used for receiving. This can be relatively small. * * @return the current buffer size */ public native int getSoUDPReceiverBufferSize() throws IOException; /** * The UDP buffer size used for sending. This can be relatively small. * * @param value */ public native void setSoUDPSenderBufferSize(int value) throws IOException; /** * The UDP buffer size used for sending. This can be relatively small. * * @return the current buffer size */ public native int getSoUDPSenderBufferSize() throws IOException; @Override public String toString() { return "{UDTSocket: closed: " + closed + ", connected:" + connected + ", fd:" + sock + "}"; } @Override protected void finalize() throws Throwable { if (sock != -1) { close(); } } private native UDTSocket acceptImpl() throws IOException; private native void closeImpl() throws IOException; private native void connectImpl(InetSocketAddress endPoint) throws IOException; private native InetSocketAddress getRemoteAddressImpl(); private native int recv(byte[] buffer, int off, int len) throws IOException; private native void send(byte[] buffer, int off, int len) throws IOException; /** * Initializes access IDs in JNI wrapper */ private native static void initIDs(); /** * Allocates a new UDT Socket. */ private native static int socket(); }