Greetings,
Let’s say you want to verify how many peak, concurrent connection’s your TCP server process or website can handle. One such way of accomplishing this using Java would be to leverage NIO using the Netty framework.
The power of NIO is a nutshell is that rather then having to deal with managing TCP streams in their own threads of execution, one can establish and maintain anynumber of TCP connection’s in a single thread while using a small pool of worker threads to handle the actual sending and receiving of data asynchronously.
From the perspective of a TCP client, it allows us to easily whip up a highly scalable simulation tool able to capture some critical performance metrics:
* Breaking point, if any, of the target server process, occurring at N connections
* Performance bottlenecks occurring at N connections
* Average response times at N connections
We will create 3 classes for this example, a main class HttpSender to handle setting up the connections, HttpClientPipelineFactory to define our pipeline, and a WorkerThread which will take care of doing the actual work on each connection.
HttpSender:
This is our main thread, which does the following:
* Setup our ClientBootstrap
* Initialize our threadpool
* Establish connections,returning an array of ChannelFutures
* For each connected channel within the channelFutures array, add a new WorkerThread task to our threadpool
* Wait for all connection’s to complete before we exit by looping through the channelFuture[] until all channels are closed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
import
java.net.InetSocketAddress;
import
java.util.concurrent.ArrayBlockingQueue;
import
java.util.concurrent.ExecutorService;
import
java.util.concurrent.Executors;
import
java.util.concurrent.ThreadPoolExecutor;
import
java.util.concurrent.TimeUnit;
import
org.jboss.netty.bootstrap.ClientBootstrap;
import
org.jboss.netty.channel.ChannelFuture;
import
org.jboss.netty.channel.group.ChannelGroup;
import
org.jboss.netty.channel.group.DefaultChannelGroup;
import
org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
public
class
httpSender {
public
httpSender(String host, String port, String connections){
ExecutorService executorService1 = Executors.newCachedThreadPool();
ExecutorService executorService2 = Executors.newCachedThreadPool();
ArrayBlockingQueue<Runnable>taskQueue =
new
ArrayBlockingQueue<Runnable>(
5000
);
ThreadPoolExecutor threadPoolExecutor =
new
ThreadPoolExecutor(
2
,
8
,
10
, TimeUnit.SECONDS, taskQueue);
ClientBootstrap clientBootstrap =
new
ClientBootstrap(
new
NioClientSocketChannelFactory(executorService1, executorService2));
clientBootstrap.setPipelineFactory(
new
HttpClientPipelineFactory());
ChannelFuture[] channelFutures = establishConnections(clientBootstrap, Integer.parseInt(connections), host, Integer.parseInt(port));
for
(ChannelFuture channelFuture : channelFutures) {
threadPoolExecutor.execute(
new
WorkerThread(host, channelFuture));
}
// Wait until all connections have closed, or the timeout is reached
int
connectedChannels;
boolean
connectionLoop =
true
;
while
(connectionLoop ==
true
) {
connectedChannels =
0
;
for
(ChannelFuture channelFuture : channelFutures) {
if
(channelFuture.getChannel().isConnected()) {
connectedChannels++;
}
}
if
(connectedChannels ==
0
) {
connectionLoop =
false
;
}
System.out.println(
"Waiting on "
+ connectedChannels +
" connections to complete...total time: "
);
try
{
Thread.sleep(
2000
);
}
catch
(InterruptedException e) {
e.printStackTrace();
}
}
// All connections complete, shutdown...
executorService1.shutdown();
executorService2.shutdown();
threadPoolExecutor.shutdown();
clientBootstrap.releaseExternalResources();
}
private
ChannelFuture[] establishConnections(ClientBootstrap clientBootstrap,
int
numConnections, String host,
int
port) {
ChannelFuture[] channelFuture =
new
ChannelFuture[numConnections];
ChannelGroup channelGroup =
new
DefaultChannelGroup();
for
(
int
i =
0
; i < numConnections; i++) {
channelFuture[i] = clientBootstrap.connect(
new
InetSocketAddress(host, port),
null
);
channelFuture[i].awaitUninterruptibly();
if
(channelFuture[i].isSuccess()) {
channelGroup.add(channelFuture[i].getChannel());
}
else
{
System.err.println(
"Connection #"
+ i +
" failed, "
+ channelFuture[i].getCause());
}
}
return
channelFuture;
}
public
static
void
main(String[] args)
throws
Exception {
if
(args.length !=
3
) {
System.err.println(
" Usage: [host] [port] [connections]"
);
}
else
{
new
httpSender(args[
0
], args[
1
], args[
2
]);
}
}
}
|
HttpClientPipelineFactory:
Here we define how our pipeline will be setup, we will be making HTTP GET requests so we implement the HttpClientCodec and the HttpChuckAggregator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
import
static
org.jboss.netty.channel.Channels.*;
import
org.jboss.netty.channel.ChannelPipeline;
import
org.jboss.netty.channel.ChannelPipelineFactory;
import
org.jboss.netty.handler.codec.http.HttpChunkAggregator;
import
org.jboss.netty.handler.codec.http.HttpClientCodec;
public
class
HttpClientPipelineFactory
implements
ChannelPipelineFactory {
public
ChannelPipeline getPipeline()
throws
Exception {
ChannelPipeline pipeline = pipeline();
pipeline.addLast(
"codec"
,
new
HttpClientCodec());
pipeline.addLast(
"aggregator"
,
new
HttpChunkAggregator());
return
pipeline;
}
}
|
WorkerThread:
Here we send an HTTP request on the connected channel
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
import
java.io.IOException;
import
org.jboss.netty.channel.Channel;
import
org.jboss.netty.channel.ChannelFuture;
import
org.jboss.netty.handler.codec.http.DefaultHttpRequest;
import
org.jboss.netty.handler.codec.http.HttpHeaders;
import
org.jboss.netty.handler.codec.http.HttpMethod;
import
org.jboss.netty.handler.codec.http.HttpRequest;
import
org.jboss.netty.handler.codec.http.HttpVersion;
public
class
WorkerThread
implements
Runnable {
private
ChannelFuture channelFuture;
private
String host;
public
WorkerThread(String host, ChannelFuture channelFuture) {
this
.host = host;
this
.channelFuture = channelFuture;
}
private
void
sendRequest()
throws
IOException {
Channel channel = channelFuture.getChannel();
HttpRequest request =
new
DefaultHttpRequest(HttpVersion.HTTP_1_0, HttpMethod.GET,
"testamundo"
);
request.setHeader(HttpHeaders.Names.HOST, host);
request.setHeader(HttpHeaders.Names.USER_AGENT,
"HTTP/1.0"
);
request.setHeader(HttpHeaders.Names.CONTENT_LENGTH,
0
);
channel.write(request);
}
@Override
public
void
run() {
try
{
sendRequest();
}
catch
(IOException e) {
e.printStackTrace();
}
}
}
|