转自 http://blog.csdn.net/wanglha/article/details/42777579
1. 線程組,在我們測試方案裏面,每個線程模擬一個用戶,執行用戶的登錄、等等等一系列的操作。由於我們的項目是長連接的,如何能實現多個sample公用一個長連接客戶端,考慮了很久,最後實現方法如下:
1. 線程組,在我們測試方案裏面,每個線程模擬一個用戶,執行用戶的登錄、等等等一系列的操作。由於我們的項目是長連接的,如何能實現多個sample公用一個長連接客戶端,考慮了很久,最後實現方法如下:
1
package tea.client.network;
2
/**
3
*
@author
Teaey
4
* @creation 2012-8-25
5
*/
6
public
class NetworkClientHolder
7 {
8
/**
9
* 這裏使用ThradLocal存儲BaseClient
10
* 方便一輪測試的每個sample都是由同一個socketChannel發送
11
* 更真實的模擬用戶
12
*/
13
private
static ThreadLocal<BaseClient> clientHolder =
new ThreadLocal<BaseClient>();
14
public
static BaseClient getClient(String ip, String port)
15 {
16 BaseClient client = clientHolder.get();
17
if (
null == client)
18 {
19 client =
new BaseClient(ip, port);
20 client.connect();
21 clientHolder.set(client);
22 }
23
return client;
24 }
25 }
26
代碼中使用thread_local保存Socket客戶端,這样每個sample中發送數據的客戶端都是從這裏拿的,就可以保證長連接的情況下,socket不會重复創建,很好的模擬了用戶。
當然不單單是鏈接可以保存,所有需要在線程中共享的數據都可以通過這種方法來實現。
2. 接下來是如何封裝發送請求的客戶端,這裏用的netty,具體可以根據項目情況使用mina或者nio都可以。代碼直接明了^_^:
1
package tea.client.network;
2
3
import java.net.InetSocketAddress;
4
import java.util.concurrent.Executors;
5
import org.jboss.netty.bootstrap.ClientBootstrap;
6
import org.jboss.netty.channel.Channel;
7
import org.jboss.netty.channel.ChannelFuture;
8
import org.jboss.netty.channel.ChannelHandlerContext;
9
import org.jboss.netty.channel.ChannelPipeline;
10
import org.jboss.netty.channel.ChannelPipelineFactory;
11
import org.jboss.netty.channel.ChannelStateEvent;
12
import org.jboss.netty.channel.Channels;
13
import org.jboss.netty.channel.ExceptionEvent;
14
import org.jboss.netty.channel.MessageEvent;
15
import org.jboss.netty.channel.SimpleChannelHandler;
16
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
17
import tea.common.network.ClientDecoder;
18
import tea.common.network.ClientEncoder;
19
import tea.common.network.ClientMessage;
20
21
/**
22
*
@author
Teaey
23
* @creation 2012-8-25
24
*/
25
public
class BaseClient
26 {
27
public BaseClient(String ip, String port)
28 {
29
this.ip = ip;
30
this.port = port;
31 }
32
private String ip;
33
private String port;
34
private Channel channel;
35
private ClientBootstrap bootstrap;
36
private Object syn =
new Object();
37
private
static
final
int Receive_Timeout = 10000;
//
ms
38
private ClientMessage response =
null;
39
public
void connect()
40 {
41 bootstrap =
new ClientBootstrap(
new NioClientSocketChannelFactory(Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));
42 bootstrap.setOption("tcpNoDelay",
true);
43 bootstrap.setPipelineFactory(
new ClientPipelineFactory());
44
while (
true)
45 {
46 ChannelFuture future = bootstrap.connect(
new InetSocketAddress(ip, Integer.parseInt(port)));
47 future.awaitUninterruptibly(5000);
48
if (future.isDone())
49 {
50 channel = future.getChannel();
51
if (channel !=
null && channel.isConnected())
52 {
53
break;
54 }
55 }
56 }
57 }
58
public
void disconnect()
59 {
60
if (channel.isConnected())
61 {
62 channel.disconnect();
63 }
64 }
65
public
boolean isConnected()
66 {
67
return channel.isConnected();
68 }
69
public
void close()
70 {
71
if (
this.channel.isOpen())
72 {
73
this.channel.close();
74 }
75 bootstrap.releaseExternalResources();
76 }
77
/**
78
* 發送消息,無需返回
79
*/
80
public
void send(ClientMessage message)
81 {
82 channel.write(message);
83 }
84
/**
85
* 發送消息,等待返回
86
*/
87
public ClientMessage sendWaitBack(ClientMessage message)
88 {
89 response =
null;
90
try
91 {
92 channel.write(message);
93
synchronized (syn)
94 {
95
try
96 {
97 syn.wait(Receive_Timeout);
98 }
catch (InterruptedException e)
99 {
100 e.printStackTrace();
101 }
102 }
103
if (
null == response)
104 {
105 System.err.println("Receive response timeout");
106 }
107 }
catch (Exception e)
108 {
109 e.printStackTrace();
110 }
111
return response;
112 }
113
class ClientPipelineFactory
implements ChannelPipelineFactory
114 {
115
public ChannelPipeline getPipeline()
throws Exception
116 {
117 ChannelPipeline p = Channels.pipeline();
118 p.addLast("frameDecoder",
new ClientDecoder());
119 p.addLast("fremeEncoder",
new ClientEncoder());
120 p.addLast("logicHandler",
new ClientMsgHandler());
121
return p;
122 }
123 }
124
class ClientMsgHandler
extends SimpleChannelHandler
125 {
126
public
void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
throws Exception
127 {
128 Object obj = e.getMessage();
129
if (obj
instanceof ClientMessage)
130 {
131 ClientMessage msg = (ClientMessage) obj;
132 response = msg;
133
synchronized (syn)
134 {
135 syn.notifyAll();
136 }
137 }
138 }
139
public
void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e)
throws Exception
140 {
141 System.out.println("connected server:" + ctx.getChannel());
142 }
143
public
void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e)
throws Exception
144 {
145 System.out.println("disconnected server:" + ctx.getChannel());
146 }
147
public
void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
throws Exception
148 {
149 System.out.println("Error in exceptionCaught:" + e.getCause());
150 }
151 }
152 }
153
這段代碼展示了我們的客戶端,這裏所有的請求有兩種發送模式,一種是發送並阻塞等待返回(
sendWaitBack
),第二種就是直接發送(
send
)。
3. 有了發送請求的客戶端,那如何能夠更簡單的實現一個協議好讓客戶端發送,再貼一段代碼^_^:
1
package tea.client.network;
2
3
import org.apache.jmeter.config.Arguments;
4
import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient;
5
import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext;
6
import org.apache.jmeter.samplers.SampleResult;
7
import com.google.protobuf.InvalidProtocolBufferException;
8
import com.google.protobuf.MessageLite;
9
10
/**
11
*
@author
Teaey
12
* @creation 2012-8-25
13
*/
14
public
abstract
class BaseSample
extends AbstractJavaSamplerClient
15 {
16
public
static
final String PARAM_IP = "ip";
17
public
static
final String PARAM_PORT = "port";
18
public
static
final String VAR_IP = "${ip}";
19
public
static
final String VAR_PORT = "${port}";
20
protected BaseClient client;
21
public
void addParameter(Arguments params)
22 {
23 }
24
/**
25
* Jmeter獲取消息参數,默認配置ip和port兩個参數
26
* 如果子類有更多参數,調用super.getDefaultParameters()獲取Arguments後,繼續設置其他方法
27
*/
28 @Override
29
public Arguments getDefaultParameters()
30 {
31 System.out.println("1.getDefaultParameters");
32 Arguments params =
new Arguments();
33 params.addArgument(PARAM_IP, VAR_IP);
34 params.addArgument(PARAM_PORT, VAR_PORT);
35 addParameter(params);
36
return params;
37 }
38
/**
39
* runTest的前置方法
40
*/
41 @Override
42
public
void setupTest(JavaSamplerContext context)
43 {
44 System.out.println("2.setupTest:" + context.containsParameter(PARAM_IP));
45 String ip = context.getParameter(PARAM_IP);
46 String port = context.getParameter(PARAM_PORT);
47
this.client = NetworkClientHolder.getClient(ip, port);
48 System.out.println("thread--->" + Thread.currentThread().getId() + " client--->" + client);
49 }
50
/**
51
* Jmeter調用,用於實際的測試
52
*/
53 @Override
54
public SampleResult runTest(JavaSamplerContext context)
55 {
56 SampleResult sample = getSample();
57 sample.sampleStart();
58
try
59 {
60 MessageLite response = doTest();
61 String msg = response ==
null ? "" : response.toString();
62 sample.setResponseMessage(msg);
63 sample.setSuccessful(
true);
64 }
catch (Exception e)
65 {
66 sample.setSuccessful(
false);
67 e.printStackTrace();
68 }
finally
69 {
70 sample.sampleEnd();
71 }
72
return sample;
73 }
74
/**
75
* 獲取本Sample的標簽,子類實現
76
*/
77
public
abstract String getLabel();
78
/**
79
* 獲取一個帶標簽的Sample
80
*/
81
public SampleResult getSample()
82 {
83 SampleResult sample =
new SampleResult();
84 sample.setSampleLabel(getLabel());
85
return sample;
86 }
87
/**
88
* Jmeter調用,用於
89
*/
90 @Override
91
public
void teardownTest(JavaSamplerContext context)
92 {
93 System.out.println("4.teardownTest");
94 }
95
/**
96
* 需實現,具體測試的方法,調用client的send/sendWithBack發送請求
97
* 如無返回,放回null即可
98
*/
99
public
abstract MessageLite doTest()
throws InvalidProtocolBufferException;
100 }
好的,這裏封裝了下AbstractJavaSamplerClient,每個消息默認包含ip和port参數,這可以再jmeter的用戶變量中定義好。为了方便大家添加消息的参數,這裏實現了空的
addParameter(Arguments params)方法,這样在具體消息中直接重寫這個方法,來添加具體的参數。是不是很方便?^_^,具體協議還需要實現的兩個方法分別是:getLabel和
doTest。第一個方法時用於報告顯示的請求名字,一般定義为消息名字+“Label”就OKay。第二個方法就是我們重點重寫的方法,這裏再貼段代碼,是一個具體消息的實現:
1
package tea.client;
2
3
import com.google.protobuf.InvalidProtocolBufferException;
4
import com.google.protobuf.MessageLite;
5
import tea.client.network.BaseSample;
6
import tea.common.network.ClientMessage;
7
import tea.common.network.RPC.HeartBeat_C2S;
8
import tea.common.network.RPC.HeartBeat_S2C;
9
10
/**
11
*
@author
Teaey
12
* @creation 2012-8-24
13
*/
14
public
class HeartBeatSample
extends BaseSample
15 {
16 @Override
17
public MessageLite doTest()
throws InvalidProtocolBufferException
18 {
19 HeartBeat_C2S.Builder request = HeartBeat_C2S.newBuilder();
20 request.setTimestamp(System.currentTimeMillis());
21 ClientMessage cm =
new ClientMessage();
22 cm.setContent(request.build().toByteArray());
23 cm.setName("HeartBeat");
24 ClientMessage sm = client.sendWaitBack(cm);
25 HeartBeat_S2C response = HeartBeat_S2C.parseFrom(sm.getContent());
26
return response;
27 }
28 @Override
29
public String getLabel()
30 {
31
return "HeartBeatSample";
32 }
33 }
34
可以看到doTest的工作就是封裝請求,並拿到父類的client發送,然後返回響應(send方式返回null),Okay,大功告成。