隨筆-4  評(píng)論-10  文章-0  trackbacks-0
          2012年8月29日編寫   
          2012年8月29日修改
          1. 線程組,在我們測試方案里面,每個(gè)線程模擬一個(gè)用戶,執(zhí)行用戶的登錄、等等等一系列的操作。由于我們的項(xiàng)目是長連接的,如何能實(shí)現(xiàn)多個(gè)sample公用一個(gè)長連接客戶端,考慮了很久,最后實(shí)現(xiàn)方法如下:
           1 package tea.client.network;
           2 /**
           3  * @author Teaey
           4  * @creation 2012-8-25
           5  */
           6 public class NetworkClientHolder
           7 {
           8     /**
           9      * 這里使用ThradLocal存儲(chǔ)BaseClient
          10      * 方便一輪測試的每個(gè)sample都是由同一個(gè)socketChannel發(fā)送
          11      * 更真實(shí)的模擬用戶
          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客戶端,這樣每個(gè)sample中發(fā)送數(shù)據(jù)的客戶端都是從這里拿的,就可以保證長連接的情況下,socket不會(huì)重復(fù)創(chuàng)建,很好的模擬了用戶。
          當(dāng)然不單單是鏈接可以保存,所有需要在線程中共享的數(shù)據(jù)都可以通過這種方法來實(shí)現(xiàn)。
          2. 接下來是如何封裝發(fā)送請(qǐng)求的客戶端,這里用的netty,具體可以根據(jù)項(xiàng)目情況使用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      * 發(fā)送消息,無需返回
           79      */
           80     public void send(ClientMessage message)
           81     {
           82         channel.write(message);
           83     }
           84     /**
           85      * 發(fā)送消息,等待返回
           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 
          這段代碼展示了我們的客戶端,這里所有的請(qǐng)求有兩種發(fā)送模式,一種是發(fā)送并阻塞等待返回(sendWaitBack
          ),第二種就是直接發(fā)送(send)。
          3. 有了發(fā)送請(qǐng)求的客戶端,那如何能夠更簡單的實(shí)現(xiàn)一個(gè)協(xié)議好讓客戶端發(fā)送,再貼一段代碼^_^:
            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獲取消息參數(shù),默認(rèn)配置ip和port兩個(gè)參數(shù)
           26      * 如果子類有更多參數(shù),調(diào)用super.getDefaultParameters()獲取Arguments后,繼續(xù)設(shè)置其他方法
           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調(diào)用,用于實(shí)際的測試
           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的標(biāo)簽,子類實(shí)現(xiàn)
           76      */
           77     public abstract String getLabel();
           78     /**
           79      * 獲取一個(gè)帶標(biāo)簽的Sample 
           80      */
           81     public SampleResult getSample()
           82     {
           83         SampleResult sample = new SampleResult();
           84         sample.setSampleLabel(getLabel());
           85         return sample;
           86     }
           87     /**
           88      * Jmeter調(diào)用,用于
           89      */
           90     @Override
           91     public void teardownTest(JavaSamplerContext context)
           92     {
           93         System.out.println("4.teardownTest");
           94     }
           95     /**
           96      * 需實(shí)現(xiàn),具體測試的方法,調(diào)用client的send/sendWithBack發(fā)送請(qǐng)求
           97      * 如無返回,放回null即可 
           98      */
           99     public abstract MessageLite doTest() throws InvalidProtocolBufferException;
          100 }
          好的,這里封裝了下AbstractJavaSamplerClient,每個(gè)消息默認(rèn)包含ip和port參數(shù),這可以再jmeter的用戶變量中定義好。為了方便大家添加消息的參數(shù),這里實(shí)現(xiàn)了空的
          addParameter(Arguments params)方法,這樣在具體消息中直接重寫這個(gè)方法,來添加具體的參數(shù)。是不是很方便?^_^,具體協(xié)議還需要實(shí)現(xiàn)的兩個(gè)方法分別是:getLabel和doTest。第一個(gè)方法時(shí)用于報(bào)告顯示的請(qǐng)求名字,一般定義為消息名字+“Label”就OKay。第二個(gè)方法就是我們重點(diǎn)重寫的方法,這里再貼段代碼,是一個(gè)具體消息的實(shí)現(xiàn):
           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的工作就是封裝請(qǐng)求,并拿到父類的client發(fā)送,然后返回響應(yīng)(send方式返回null),Okay,大功告成。
          要說的就是這么多,寫得不好但屬原創(chuàng),體量作者轉(zhuǎn)載請(qǐng)標(biāo)明出處。提意見請(qǐng)留言或者163郵箱:masfay,感謝。
          posted on 2012-08-29 11:44 沖杯茶喝 閱讀(7156) 評(píng)論(2)  編輯  收藏

          評(píng)論:
          # re: Jmeter“Java請(qǐng)求”使用總結(jié) 2015-10-11 17:35 | 打雜程序員
          看了你的blog,受益匪淺~! 有個(gè)問題請(qǐng)教你:如果是異步的請(qǐng)求,應(yīng)該怎樣設(shè)置SampleResult? 比如我在runTest里發(fā)一個(gè)登陸請(qǐng)求,它的登陸回復(fù)應(yīng)該是在messageReceived里,這時(shí)runTest函數(shù)要阻塞等待么?還是jmeter根本就不適用這種場景?  回復(fù)  更多評(píng)論
            
          # re: Jmeter“Java請(qǐng)求”使用總結(jié) 2015-10-12 12:18 | 沖杯茶喝
          @打雜程序員
          在登錄的Sample里,需要阻塞等待登錄返回的,Jmeter可以這么做,一般單獨(dú)作為一個(gè)sample,這樣也可以統(tǒng)計(jì)出登錄所消耗的時(shí)間  回復(fù)  更多評(píng)論
            

          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 惠东县| 扎鲁特旗| 温州市| 原阳县| 吐鲁番市| 呼玛县| 丹巴县| 武威市| 财经| 阳高县| 六枝特区| 固原市| 固始县| 呼伦贝尔市| 淄博市| 凌海市| 楚雄市| 京山县| 泾川县| 化德县| 曲松县| 集贤县| 汉中市| 富川| 洛宁县| 西和县| 尚志市| 镇巴县| 泽库县| 安多县| 新竹市| 化德县| 怀安县| 万安县| 喜德县| 北流市| 禹州市| 包头市| 呼和浩特市| 精河县| 汶川县|