import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
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;
class Connnection implements Comparable<Connnection> {
public Connnection(SelectionKey key, long idleTs) {
this.key = key;
this.idelUtilTs = idleTs;
}
public boolean hasIdelEnoughTime(long now) {
return now > idelUtilTs;
}
public final SelectionKey key;
public final long idelUtilTs;
public int compareTo(Connnection o) {
return (int) (idelUtilTs - o.idelUtilTs);
}
}
class Attachment {
int responseLength = -1;
int bytesNeedRead = -1;
}
public class ConcurrencyBench {
final static int PER_IP = 20000;
final static InetSocketAddress ADDRS[] = new InetSocketAddress[20];
final static int CONCURENCY = PER_IP * ADDRS.length;
static {
final int PORT = 8000;
final int IP_START = 200;
for (int i = 0; i < ADDRS.length; i++) {
ADDRS[i] = new InetSocketAddress("192.168.1." + (i + IP_START), PORT);
}
}
final static Random r = new Random();
public static ByteBuffer randRequest() {
int length = r.nextInt(10240); // 1 ~~ 10k
String uri = "/?length=" + length;
return ByteBuffer.wrap(("GET " + uri + " HTTP/1.1\r\nHost: localhost\r\n\r\n")
.getBytes());
}
public static int randidelTime() {
int seconds = 10 + r.nextInt(90);
return seconds * 1000;
}
final static PriorityQueue<Connnection> connections = new PriorityQueue<Connnection>(
CONCURENCY);
// status
static int opened = 0;
static int connected = 0;
static int requestsSent = 0;
static long bytesReceived = 0;
static long startTime = System.currentTimeMillis();
// helper
final static ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 64);
static Selector selector;
public static void activeIdelConnection(long now) {
Connnection c;
while ((c = connections.peek()) != null) {
if (c.hasIdelEnoughTime(now)) {
c.key.attach(new Attachment());
c.key.interestOps(SelectionKey.OP_WRITE);
connections.poll();
} else {
break;
}
}
}
static long lastReportTime = 0;
static void reportPerSeconds(long now) {
if (now - lastReportTime > 1000) {
long time = now - startTime;
double thoughput = ((double) bytesReceived / time) * 1000 / 1024 / 1024;
double rps = ((double) requestsSent / time) * 1000;
System.out
.printf("time %ds, concurrency: %d, total requests: %d, thoughput: %.2fM/s, %.2f requests/seconds\n",
time / 1000, connected, requestsSent, thoughput, rps);
lastReportTime = now;
}
}
public static void main(String[] args) throws IOException, InterruptedException {
selector = Selector.open();
while (true) {
long now = System.currentTimeMillis();
// connect to server, 100 at a time
for (int i = 0; i < 100 && opened < CONCURENCY; i++) {
SocketChannel ch = SocketChannel.open();
ch.configureBlocking(false);
ch.socket().setReuseAddress(true);
ch.register(selector, SelectionKey.OP_CONNECT, new Attachment());
ch.connect(ADDRS[opened % ADDRS.length]);
opened++;
}
int select = selector.select(2000); // 2s
if (select > 0) {
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
if (key.isConnectable()) {
finishConnect(key);
} else if (key.isWritable()) {
writeRequest(key);
} else if (key.isReadable()) {
readResponse(key, now);
}
}
selectedKeys.clear();
}
activeIdelConnection(now);
reportPerSeconds(now);
if (opened < CONCURENCY) {
Thread.sleep(20); // open 5000 per seconds most
}
}
}
private static void readResponse(SelectionKey key, long now) {
SocketChannel ch = (SocketChannel) key.channel();
buffer.clear();
try {
int read = ch.read(buffer);
if (read == -1) {
System.out.println("remote closed cleanly");
close(ch); // remote closed cleanly
} else if (read > 0) {
bytesReceived += read;
buffer.flip();
Attachment att = (Attachment) key.attachment();
if (att.responseLength == -1) {
String line = readLine(buffer);
while (line.length() > 0) {
line = line.toLowerCase();
if (line.startsWith(CL)) {
String length = line.substring(CL.length());
att.responseLength = Integer.valueOf(length);
att.bytesNeedRead = att.responseLength;
}
line = readLine(buffer);
}
att.bytesNeedRead -= buffer.remaining();
} else {
att.bytesNeedRead -= read;
}
if (att.bytesNeedRead == 0) { // all read
connections.add(new Connnection(key, now + randidelTime()));
}
}
} catch (Exception e) {
e.printStackTrace();
close(ch);
}
}
static final byte CR = 13;
static final byte LF = 10;
static final String CL = "content-length: ";
// need to be more robust, but works fine on Linux
public static String readLine(ByteBuffer buffer) {
StringBuilder sb = new StringBuilder(64);
char b;
loop: for (;;) {
b = (char) buffer.get();
switch (b) {
case CR:
if (buffer.get() == LF)
break loop;
break;
case LF:
break loop;
}
sb.append(b);
}
return sb.toString();
}
private static void writeRequest(SelectionKey key) throws IOException {
SocketChannel ch = (SocketChannel) key.channel();
ch.write(randRequest());
requestsSent += 1;
key.interestOps(SelectionKey.OP_READ);
}
private static void finishConnect(SelectionKey key) {
SocketChannel ch = (SocketChannel) key.channel();
try {
if (ch.finishConnect()) {
++connected;
key.interestOps(SelectionKey.OP_WRITE);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void close(SelectableChannel ch) {
connected--;
try {
ch.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}