1. 程式人生 > >Jmeter單個長連接發送多個Sample

Jmeter單個長連接發送多個Sample

jmeter nbsp .so default handler 重寫 private 簡單 invalid

Mark自:https://blog.csdn.net/lykangjia/article/details/16337505

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 }
  代碼中使用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 }
  這段代碼展示了我們的客戶端,這裏所有的請求有兩種發送模式,一種是發送並阻塞等待返回(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 }
  可以看到doTest的工作就是封裝請求,並拿到父類的client發送,然後返回響應(send方式返回null),Okay,大功告成。

Jmeter單個長連接發送多個Sample