50,000+ connection TCP sender, Java NIO w/Netty

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.

http://www.jboss.org/netty

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();
         }
     }
}
原文链接: http://blog.wastedbandwidth.org/?p=4

你可能感兴趣的:(Connection)