import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.PriorityQueue;
import java.util.Random;
import java.util.Set;
// makeup idle keep-alive HTTP connection, and periodly close and re-open the connection
class PendingClose implements Comparable<PendingClose> {
public final SelectionKey key;
public final long closeTime;
public PendingClose(SelectionKey key, long closeTime) {
this.key = key;
this.closeTime = closeTime;
}
public int compareTo(PendingClose o) {
return (int) (closeTime - o.closeTime);
}
public String toString() {
return "PendingClose [key=" + key + ", closeTime=" + closeTime + "]";
}
}
public class MakeIdleHttpConnection {
// config
static int CONCURENCY = 4000;
final static int PORT = 4348;
// local test, a computer can have many ips
final static InetSocketAddress ADDRS[] = { new InetSocketAddress("127.0.0.1", PORT), };
final static byte[] REQUEST = "GET / HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n".getBytes();
// status
static int opened = 0;
static int connected = 0;
public static int randIdleTime() {
return r.nextInt(1000 * 40) + 1000 * 8;
}
// helper
final static Random r = new Random();
final static ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 64);
static Selector selector;
// close and reopen the connection after a time
final static PriorityQueue<PendingClose> pendingClose = new PriorityQueue<PendingClose>();
public static void handlePendingClose() throws IOException {
long now = System.currentTimeMillis();
PendingClose p;
while ((p = pendingClose.peek()) != null) {
if (p.closeTime < now) {
close(p.key.channel());
pendingClose.poll();
} else {
break;
}
}
}
public static void main(String[] args) throws IOException, InterruptedException {
if (args.length > 0) {
CONCURENCY = Integer.parseInt(args[0]);
}
selector = Selector.open();
long start = System.currentTimeMillis();
long lastPrintTime = 0;
while (true) {
long now = System.currentTimeMillis();
if (now - start > 1000 * 60 * 20) {
// exits after 20 minutes
break;
}
for (int i = 0; i < 20 && connected < CONCURENCY; ++i) {
for (InetSocketAddress addr : ADDRS) {
connect(addr);
}
}
int select = selector.select(2000); // 2s
handlePendingClose();
if (select <= 0) {
continue;
}
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
SocketChannel ch = (SocketChannel) key.channel();
if (key.isConnectable()) {
try {
if (ch.finishConnect()) {
++connected;
key.interestOps(SelectionKey.OP_WRITE);
}
} catch (Exception e) {
ch.close();
}
} else if (key.isWritable()) {
// should write all, TCP buffer
ch.write(ByteBuffer.wrap(REQUEST));
key.interestOps(SelectionKey.OP_READ);
} else if (key.isReadable()) {
buffer.clear();
try {
int read = ch.read(buffer);
if (read == -1) {
close(ch); // remote closed cleanly
} else {
key.cancel(); // not selectalbe
pendingClose.add(new PendingClose(key, now + randIdleTime()));
}
} catch (Exception e) {
close(ch);
}
}
}
selectedKeys.clear();
Thread.sleep(20);
if (now - lastPrintTime >= 1000) {
lastPrintTime = now;
System.out
.println("connection opened: " + opened + "; connected: " + connected);
}
}
}
public static void close(SelectableChannel ch) {
connected--;
try {
ch.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void connect(InetSocketAddress addr) throws IOException, SocketException,
ClosedChannelException {
opened++;
SocketChannel ch = SocketChannel.open();
ch.configureBlocking(false);
ch.socket().setReuseAddress(true);
ch.register(selector, SelectionKey.OP_CONNECT);
ch.connect(addr);
}
}