摘要:通过实例讲解loadrunner中的socket协议性能测试的一种测试方法,如何不依赖loadrunner既定规则,自行控制收发数据包

关键词:Loadrunner,socket,自行控制,收发数据包

一.前言

用过loadrunner的socket协议进行性能测试的同学都知道,只需要录制短短的几句命令,就可以实现socket的链接、收发数据包和关闭链接,一时大爽,不过紧跟着的就是没完没了的折磨。刚开始参数化数据包发送接收都行,慢慢的发现,很多情况下,收发数据包的长度和内容都是不可确定的,加上十六进制和ASCII,甚至协议和加密等等因素混合在一起,简直就是灾难。于是自行控制数据包收发成了可选项,虽然loadrunner提供了相关的函数,但是真的面对进制转换,面对没完没了的<memory violation : Exception ACCESS_VIOLATION received>,很多人只能另外寻找办法完成任务。

本来想全面剖析loadrunner的socket协议性能测试,发现需要厘清的细节太多了,只能尽力讲清楚下面这个例子中遇到的各个知识点了。

二.任务的提出

这个性能测试是很常见的一种情况,前置机链接了各类不同的硬件设备客户端,各个硬件设备客户端使用了不同的协议,协议承载了大量的不同业务,不过数据包的基本结构相同,由首部、包体和校验码组成,既有TCP链接也有UDP链接,数据发送方式上都是使用的短链接,也就是链接上服务器,发送完数据就立刻关闭了链接。现在需要loadrunner模拟不同的硬件设备,测试前置机的并发能力。

数据包结构:

006.jpg

系统架构:

001.jpg

三.实现方案讨论

这个场景很常见,不过也比较复杂。

如果采用传统的录制回放,需要先选择几种有代表性的硬件类型和重点业务,录制出脚本,可以想象需要录制的脚本有很多,如果进行参数化,必须要搞清楚各种协议,重新组包,这个工作量太大了。

或者开发提供两个动态链接库,一个用来对各种协议实现编解码,另外一个包括了需要模拟的硬件类型的重点业务,第二个动态链接库调用第一个,在loadrunner中加载了动态链接库以后,直接调用相关的业务操作函数就可以了。这个够通用,不过开发谁有空搭理你呀。况且如果说这个,这篇文章就不用写了。

那还有第三种方法,现在收发的数据包在前置机上有日志文件保存,可以将各种硬件类型发送的数据包日志文件分类搜集到,然后做两个脚本,一个TCP的,一个UDP的,逻辑都是同样的,打开数据表日志文件,读出数据包发送,将发送和接收到的数据包写入本地日志文件,这样就只需要编写两个脚本,拷贝出多份,每个脚本下放入不同的数据包文件模拟出不同的硬件类型。

看起来这种方式最简单,再分析一下,是否可行。

很多协议中会在链接上服务器后,服务器端提供一个唯一串返回,做为一次通讯的唯一标识,加入到后续的数据包中,这里协议倒是没有这个问题,要不每次发送数据包前,还得根据返回的唯一串来修改要发送的数据包,真是幸运。

这样看来建立链接后,数据包可以不做任何修改就能发送出去。不过有些业务,例如增加业务,前置机接收到任务后,可能会写入表,如果已经存在可能会冲突,所以测试前需要清空数据库,只保留初始化数据。

这样还有一个好处,测试的业务和实际生产的业务是完全一致的,无论种类还是比例。缺点是这里的数据包文件会不会不够大,发一会就发完了,看来还需要有个工具来生成足够多的数据包的文件。不过怎么说也是松散耦合了。

经过确认,也没有出现某硬件的某个业务,混合使用TCP和UDP的情况。

看来这个方案没有太大的问题,就这样吧。

四.技术要点讲解

1. 如何开始录制一个最简单的收发数据包脚本

开始录制脚本的时候,使用了一个绿色软件SocketTool.exe,在本机启动了一个TCP服务器端:

002.jpg

使用loadrunner录制windows application,启动一个新的SocketTool.exe,创建一个TCP Client,链接刚才启动的服务器,钩选上显示十六进制值,发送313233,别写空格进去,点击发送数据,然后再在服务器端发送点数据回客户端,最后客户端点击断开,脚本就录制完成了。

003.jpg

004.jpg

脚本就四句:

lrs_create_socket("socket0", "TCP", "LocalHost=0", "RemoteHost=server:60000", LrsLastArg);

lrs_send("socket0", "buf0", LrsLastArg);

lrs_receive("socket0", "buf1", LrsLastArg);

lrs_close_socket("socket0");

数据文件data.ws:

;WSRData 2 1

send  buf0 3

"123"

recv  buf1 3

"456"

-1

后面的脚本就在此基础上修改了。

2. 写日志文件

假设脚本并发了五个用户,如果都往一个日志文件里面写入内容,就可能出现各个用户日志交织在一起的情况,如果需要每个用户独立使用自己的日志文件,可以创建一个参数vurid

005.jpg

sprintf(cReqSeqNo,"%s%s20d459b3412a2b",cNow,);

定义变量:

char cLogFile[100]="\0";   //日志文件

long filedeslog=0;         //日志文件句柄

在vuser_init中打开日志文件:

sprintf(cLogFile,"lrsocket%s.log",lr_eval_string("{vurid}"));

if((filedeslog = fopen(cLogFile, "a+")) == NULL)

{

lr_output_message("Open File Failed!");

return -1;

}

//写入日志文件内容

fwrite("\nopen file:", strlen("\nopen file:"), 1, filedeslog);

fwrite(cFileName, strlen(cFileName), 1, filedeslog);

fwrite("\n", 1, 1, filedeslog);

在vuser_end中关闭日志文件:

fclose(filedeslog);

3. 一行一行读数据包文件

定义部分:

char cFileName[100]="\0";  //数据包文件名

long filedes=0;            //数据包文件句柄

char cLine[2048]="\0";      //文件中一行

读文件方法:

sprintf(cFileName,"%s","data.txt");

if((filedes = fopen(cFileName, "r")) == NULL)

{

lr_output_message("Open File Failed!");

return -1;

}

while (!feof(filedes)) {

fscanf(filedes, "%s", cLine);

lr_output_message("read:%s", cLine);

}

fclose(filedes);

4. 字符串转换为十六进制数据包

定义:

unsigned char cOut[1024]="\0";      //记录转换出来的数据包,发送出去的数据包

在这里虽然表面是字符数组,不过请大家千万别把cOut[]当成字符串来处理,而应该理解为一个存放一系列十六进制数据的数组。这有什么区别吗?当然有。

比如你现在要发出一个数据包16进制是:31 32 00 33 34,该数组中就该存储着(十进制):

cOut[0]=49

cOut[1]=50

cOut[2]= 0

cOut[3]=51

cOut[4]=52

发送数据包的时候就应该发送长度为5,如果处理为了字符串,发送strlen(cOut),可以想象,逢零就停止了,只发出去了前两个字节。接收的时候自然也不可以使用strcpy(cOut,BufVal),因为遇到零就会停止,如果包中有00字节,就会造成数据不完整。

//进制转换

m=0;

memset(cOut,0,sizeof(cOut));

for (k=0;k<strlen(cLine);k++) {

if (k % 2==1) {

cTmp[0]=cLine[k-1];

cTmp[1]=cLine[k];

cTmp[2]=0;

sscanf(cTmp,"%x", &lngTrans);

cOut[m]=lngTrans;

m++;

}

}

首先初始化cOut的所有字节为0;

读取从文件中取出的一行;

每遇到偶数字符,就读出来两个字符,放入cTmp字符串,使用sscanf(cTmp,"%x", &lngTrans);

比如cTmp中存着”31”,理解为16进制转换出来,lngTrans=0x31;

然后再把转换出来的数据放入cOut中,得到要发出的数据包

如果想看看cOut里面存的内容:

unsigned char *p;

p=cOut;

for (i=0;i<strlen(cLine)/2;i++) {

lr_output_message("package ready:%x,%d,%x",p,*p,*p);

p++;

}

在loadrunner中不可以直接引用cOut[0]的方式打印值,需要使用指针。连指针的地址都打给你看了,这下够清楚了吧。

5. 发送自己定义的数据包

建立链接我就不写了,发送自己定义的数据包:

lrs_set_send_buffer("socket0", (char *)cOut, strlen(cLine)/2 );

lrs_send("socket0", "buf0", LrsLastArg);

说明:

1.         (char *)cOut 是因为函数的参数定义
int lrs_set_send_buffer ( char *s_desc, char *buffer, int size );

2.         strlen(cLine)/2不可写为strlen(cOut),一定要牢牢记住这里不是发送的字符串,而是一个二进制数据包;

6. 接收数据包到自定义缓冲区

代码:

char *BufVal;              //记录接收到的数据包

int intGetLen=0;           //记录接收数据包的长度

lrs_receive_ex("socket0", "buf1", "NumberOfBytesToRecv=4", LrsLastArg);

lrs_get_last_received_buffer("socket0",&BufVal, &intGetLen);

说明:

1.         intGetLen必须定义为int,而不可是long,为啥?函数定义决定的:
int lrs_get_last_received_buffer ( char *s_desc, char **data, int *size );

2.         "NumberOfBytesToRecv=4"此处loadrunner的帮助中例子写错了,当时我照着粘贴下来,死活报那个恐怖的<memory violation : Exception ACCESS_VIOLATION received>,后来仔细看了看,明白了,例子上NumberOfBytesToRecv前面多了一个空格,删除了就可以了;

3.         定义接收数据包长度,这个参数只适应于TCP协议,UDP就不行了

7. 从自定义缓冲区读出数据

代码:

char cGetLen[5]="\0";      //记录接收到的前四个字节

memset(cGetLen,0,sizeof(cGetLen));

for (j=0;j<intGetLen;j++) {

sprintf(cT1,"%02x",(unsigned char)*BufVal);

strcat(cGetLen,cT1);

BufVal++;

}

说明:

1.         初始化接收数组cGetLen所有字节为0;

2.         (unsigned char)*BufVal将BufVal指向的值一个个字节读出,按照无符号数解读为16进制和十进制,如果不设定为无符号数,碰到诸如0xA0,转换成十进制字符串就不是”160”,会变成一个负值”-95”,高位被解读为了符号;

3.         cGetLen不用定义为无符号的,他只是用来将16进制串转化为字符串写入日志用的,并不是存储的数据包

8. 如何释放自定义缓冲区

代码:

for (j=0;j<intGetLen;j++) {

BufVal--;

}

lrs_free_buffer(BufVal);

用完了缓冲区BufVal后需要释放,否则BufVal不断的取得返回,就会越来越长,定位就变得麻烦,用起来不方便。最初释放的时候也是遭遇<memory violation : Exception ACCESS_VIOLATION received>。查看了例子,想了半天,终于明白了,我之前读取缓冲区操作了指针,而释放需要是初始的头指针,于是写了一段狗血的代码,通过循环,回到初始状态进行释放。-_-|||

9. 如何根据数据包返回计算为十进制数

接收数据的时候是分成两个步骤,首先取得四个字节,计算出后续数据包的长度,然后再指定长度进行接收。所以得到返回的四个字节后,需要计算出长度。这里我是一个字节一个字节转换为十进制的值,例如:

0x11 0x22 0x33 0x44=0d17 0d34 0d51 0d68=256^3*17+256^2*34+256^1*51+256^0*68

代码:

定义:

unsigned char cT2[3]="\0"; //记录接收到的10进制字符串

long lngGetData=0;         //记录后续数据包长度

int iByte=0;               //四个字节的单个字节的10进制数

int iaR[4]={0,0,0,0};      //记录四个字节的十进制值

for (j=0;j<intGetLen;j++) {

sprintf(cT2,"%d",(unsigned char)*BufVal);

iByte=atoi(cT2);

iaR[j]=iByte;

BufVal++;

}

lngGetData=iaR[0]*16777216+iaR[1]*65536+iaR[2]*256+iaR[3];

通过atoi把ASCII码转换为int值,比如cT2=”160”,atoi后就成了数值的160;

五.小节

学多用少是一个大的战略原则,尽可能用最简单最适合的法子解决问题,loadrunner的socket测试本篇中没有提到如何和参数打交道的问题,也没有描述UDP和TCP的细节差异,接收报文也只是长度数据两段式的收取,没有讲到不确定长度使用终止串的收取方法,一篇文章终归难以尽言,抛砖引玉,如有错漏,不吝赐教。

代码和工具下载:

http://download.csdn.net/detail/testingba/4305645

loadrunner socket协议问题归纳(3)的更多相关文章

  1. loadrunner socket协议问题归纳(6)

    首先让我们先看一下loadrunner- winsock 函数 一览表: lrs_accept_connection 接受侦听套接字连接 lrs_close_socket 关闭打开的套接字       ...

  2. loadrunner socket协议问题归纳(0)

    一.概述         Loadrunner拥有极为丰富的工具箱,供予我们制造出各种奇妙魔法的能力.其中就有此次要讨论的socket套接字操作.     二.socket概述         soc ...

  3. loadrunner socket协议问题归纳(1)

    前段时间测了loadrunner直接发送报文到socket上的性能测试.在此,稍微回顾整理下. 与socket通讯,有两种方式,一种是建立长连接,建立后,不停的发送,接收.另外一种是建立短连接,建立连 ...

  4. loadrunner socket协议问题归纳(2)

    编写步骤 1.建立与服务端的连接 rc=lrs_create_socket(“socket0”,”TCP”,”LocalHost=0”,”RemoteHost=127.0.0.1:8808”,LrsL ...

  5. loadrunner socket协议问题归纳(5)

    获取服务器的返回值,可以用web_reg_save_param函数,该参数最好放到: 语法: int web_reg_save_param(const char *ParamName, <lis ...

  6. loadrunner socket协议问题归纳(4)---buffer接收变长和定长的数据

    测试场景:聊天系统 用户登录后,要先向服务器发送用户名,然后可以发送聊天信息,同时也可以接受聊天信息. 如果接受的字符为定长时,可以设定接受长度.recv buf2 66 #include " ...

  7. Loadrunner socket协议lrs_receive函数接收到返回数据包 仍然等待服务器返回--解决

    前段时间在使用loadrunner socket协议发送数据包到到服务器,使用lrs_receive接收服务器应答数据包,已经接收到数据包,但LR仍然在等待服务器端返回,而且日志打印显示每次接收返回都 ...

  8. LoadRunner编写Socket协议脚本方法

    本文主要介绍使用LoadRunner手工编写Windows Socket协议测试脚本的方法. 通过LoadRunner编写Windows Socket协议测试脚本,总体说来,比较简单.就像把大象放进冰 ...

  9. Loadrunner 中socket协议RecvBuffer接收到数据长度为空

    socket通讯,有两种方式,一种是建立长连接(TCP),建立后,不停的发送,接收.另外一种是建立短连接(UDP),建立连接,发送报文,接收响应,关闭连接.两种方式 server的开销不同. 今天出现 ...

随机推荐

  1. CoreAnimation|动画

    IOS开发UI篇--IOS动画(Core Animation)总结 - CSDN博客 iOS动画,绝对够分量! - 简书 iOS动画篇:UIView动画 - 简书 iOS动画开发之五--炫酷的粒子效果 ...

  2. Linux基础练习题(五)

    1.创建一个10G分区,并格式为ext4文件系统: (1) 要求其block大小为2048, 预留空间百分比为2, 卷标为MYDATA, 默认挂载属性包含acl: [root@bj-1-142 ~]# ...

  3. CentOS7进行OpenStack(queens)最小化部署实验出现的问题与解决过程

    注:此文为<OpenStack(queens)最小化搭建记录——控制与计算共两个节点>的补充 1.chrony时间同步服务搭建的时候,出现计算节点无法与控制节点同步.(controller ...

  4. 一次JVM内存调优过程

    项目中,有个同事写的JOB,使用到查询数据库大量历史协议数据(大概300W左右),由于对存放数据的list或map没有做“用完即时声明释放”. 导致此Jar部署在windows service后,进程 ...

  5. Django实现支付宝支付(沙箱)

    1.安装SDK 点击右侧沙箱当面付接入指导,之后可以看到一个下载SDK的按钮,点击后,会有python的SDK下载链接,但还是属于公测中,也可以通过在cmd里输入以下代码来安装. pip instal ...

  6. 【visual studio code 的python开发环境搭建 】

    打开vs code,按按F1或者Ctrl+Shift+P打开命令行,然后输入ext install 输入Python,选第一个,这个用的最多,支持自动补全代码等功能,点击安装按钮,即可安装 下面试着编 ...

  7. 需求:加一个下拉框选择条件改变饼图内外环 饼图:百度echarts提供

    1.1:下拉框条件:后台取得ViewBag传给前台 MonitorController: public ActionResult BigData(): //下拉框筛选条件 var result = M ...

  8. java-spark的各种常用算子的写法

    通常写spark的程序用scala比较方便,毕竟spark的源码就是用scala写的.然而,目前java开发者特别多,尤其进行数据对接.上线服务的时候,这时候,就需要掌握一些spark在java中的使 ...

  9. SylixOS 系统初探

    国产嵌入式硬实时操作系统 SylixOS 初体验 关于 SylixOS 详细了解请见:http://wiki.sylixos.com/index.php/%E7%B3%BB%E7%BB%9F%E7%A ...

  10. ASP.NET的服务端验证(干货)

    最近有项目需要使用.net的web,啥也不说,直接开始学习.net的mvc框架.感觉微软的web项目其实还是很好用的,今天和大家分享一下服务端验证的事情.其实原理就是用到了c#的特性,特性不用多说,大 ...