blob: f24c458677329ce39521cef82710431b05eddfb3 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2008 Remy Suen, Composent Inc., and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Remy Suen <remy.suen@gmail.com> - initial API and implementation
******************************************************************************/
package org.eclipse.ecf.protocol.bittorrent;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Map;
/**
* The <code>TorrentServer</code> class listens for incoming connections and
* hooks them onto the corresponding {@link Torrent} based on the info hash
* provided in the hand shake.
*/
public class TorrentServer {
private static final Map activeTorrents = new HashMap();
/**
* The shared instance of a <code>TorrentServer</code> that is currently
* monitoring a port for incoming connections.
*/
private static TorrentServer peerListener;
/**
* The port to use to listen for incoming connections.
*/
private static int port = -1;
/**
* Used to read and process an incoming connection's handshake.
*/
private final ByteBuffer buffer = ByteBuffer.allocate(68);
private final byte[] bufferArray = buffer.array();
private final byte[] handshake = new byte[20];
private ServerSocketChannel channel;
private ServerSocket serverSocket;
private Thread listeningThread;
static {
try {
peerListener = new TorrentServer(null);
} catch (IOException e) {
throw new IllegalStateException(e.getMessage());
}
}
/**
* Sets the port that should be used to listen for incoming connections.
*
* @param port
* the port to listen on
* @throws IllegalArgumentException
* If <code>port</code> is negative
* @throws IOException
* If an I/O error occurs while binding around the specified
* port
*/
public static void setPort(int port) throws IOException {
if (port < 0) {
throw new IllegalArgumentException("Cannot listen for incoming connections on a negative port: " + port); //$NON-NLS-1$
}
if (peerListener == null || TorrentServer.port != port) {
try {
peerListener.channel.close();
} catch (IOException e) {
// ignored
}
peerListener = new TorrentServer(port);
peerListener.listen();
}
}
/**
* Retrieves the port that is currently being used to listen for incoming
* connections.
*
* @return the port being used
*/
public static int getPort() {
return port;
}
static void addTorrent(String hash, Torrent torrent) {
if (!activeTorrents.containsKey(hash)) {
activeTorrents.put(hash, torrent);
if (activeTorrents.size() == 1) {
peerListener.listen();
}
}
}
static Torrent get(String hash) {
return (Torrent) activeTorrents.get(hash);
}
static Torrent remove(String hash) {
Torrent host = (Torrent) activeTorrents.remove(hash);
if (activeTorrents.isEmpty()) {
try {
peerListener.channel.close();
} catch (IOException e) {
// ignored
}
peerListener.listeningThread = null;
}
return host;
}
private TorrentServer(InetSocketAddress address) throws IOException {
channel = ServerSocketChannel.open();
serverSocket = channel.socket();
serverSocket.bind(address);
port = serverSocket.getLocalPort();
}
private TorrentServer(int port) throws IOException {
this(new InetSocketAddress("localhost", port)); //$NON-NLS-1$
}
/**
* Starts a new thread and begins listening for incoming connections.
*/
private void listen() {
if (listeningThread == null) {
listeningThread = new ListeningThread();
listeningThread.start();
}
}
private class ListeningThread extends Thread {
public ListeningThread() {
super("Listening Thread"); //$NON-NLS-1$
}
public void run() {
int read = 0;
int ret = 0;
while (true) {
try {
SocketChannel socketChannel = channel.accept();
ret = socketChannel.read(buffer);
if (ret == -1) {
try {
socketChannel.close();
} catch (IOException e) {
// ignored
}
continue;
}
read += ret;
while (read < 68) {
ret = socketChannel.read(buffer);
if (ret == -1) {
try {
socketChannel.close();
} catch (IOException e) {
// ignored
}
break;
}
read += ret;
}
if (ret == -1) {
continue;
}
System.arraycopy(bufferArray, 28, handshake, 0, 20);
Torrent torrent = (Torrent) activeTorrents.get(new String(
handshake, "ISO-8859-1")); //$NON-NLS-1$
if (torrent != null) {
torrent.connectTo(socketChannel);
} else {
// since no such torrent is currently online and
// connected, simply close the connection
try {
socketChannel.close();
} catch (IOException e) {
// ignored
}
}
buffer.clear();
} catch (AsynchronousCloseException e) {
return;
} catch (ClosedChannelException e) {
return;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}