当前位置: 首页 > news >正文

自行控制loadrunner的socket协议性能测试 (转)

 

  1. 一前言
  2. 二任务的提出
  3. 三实现方案讨论
  4. 四技术要点讲解
    1. 如何开始录制一个最简单的收发数据包脚本
    2. 写日志文件
    3. 一行一行读数据包文件
    4. 字符串转换为十六进制数据包
    5. 发送自己定义的数据包
    6. 接收数据包到自定义缓冲区
    7. 从自定义缓冲区读出数据
    8. 如何释放自定义缓冲区
    9. 如何根据数据包返回计算为十进制数
  5. 五小节

 

 

摘要:通过实例讲解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


转自:http://blog.csdn.net/testingba/article/details/7571911

相关文章:

  • 图片懒加载:基于jQuery判断某元素是否显示在可视范围内,并实现背景图片根据随机下标进行懒加载
  • 占位图推荐:web前端开发,推荐4个常用在线占位图网址
  • MongoDB副本集搭建
  • nodejs搭建服务篇章三:nodejs搭建服务项目目录的简单介绍及app.js文件简单说明
  • 搭建rsync服务器,实现文件备份同步
  • nodejs搭建服务篇章四:前端通过nodejs搭建的服务连接MySQL数据库中的数据(数据库安装,数据库管理,ajax查询数据),超多图超详细
  • XCode升级到7后,规范注释生成器VVDocumenter插件没有用了,怎么办?
  • vue传值处理:vue中父组件通过props传值给子组件,子组件表单控件使用值,子组件改变该值时如何避免报错
  • axios的二次封装:在vue中如何灵活运用axios请求,二次封装更加灵活,更多参数可配置
  • 清理C盘内存:电脑C盘飘红了,那么如何清理垃圾文件,总结几种亲测方案
  • Part05 - (图文)NSX系列之检查NSX Controller状态
  • JSON.parse转化:如何使得一个字符串类型‘false‘等于布尔类型的false(使‘1‘===1?成立的2种处理办法,与JSON.stringify的)
  • 常用工具函数推荐:前端开发常用的工具函数(拷贝、排序、防抖、去重、合并、时间处理、DOM操作...).md
  • 正则表达式校验文件路径
  • img制图技巧:给img图片添加背景颜色和背景图片,制作出新图片
  • 【从零开始安装kubernetes-1.7.3】2.flannel、docker以及Harbor的配置以及作用
  • 4. 路由到控制器 - Laravel从零开始教程
  • Angular6错误 Service: No provider for Renderer2
  • canvas 高仿 Apple Watch 表盘
  • CAP 一致性协议及应用解析
  • ES6, React, Redux, Webpack写的一个爬 GitHub 的网页
  • java取消线程实例
  • js ES6 求数组的交集,并集,还有差集
  • React Native移动开发实战-3-实现页面间的数据传递
  • 区块链技术特点之去中心化特性
  • 什么是Javascript函数节流?
  • 使用 @font-face
  • 微信小程序设置上一页数据
  • 原创:新手布局福音!微信小程序使用flex的一些基础样式属性(一)
  • 源码之下无秘密 ── 做最好的 Netty 源码分析教程
  • 找一份好的前端工作,起点很重要
  • 整理一些计算机基础知识!
  • ​香农与信息论三大定律
  • ​学习一下,什么是预包装食品?​
  • #stm32整理(一)flash读写
  • #我与Java虚拟机的故事#连载08:书读百遍其义自见
  • $redis-setphp_redis Set命令,php操作Redis Set函数介绍
  • (1)bark-ml
  • (Java数据结构)ArrayList
  • (办公)springboot配置aop处理请求.
  • (分布式缓存)Redis分片集群
  • (六) ES6 新特性 —— 迭代器(iterator)
  • (算法)N皇后问题
  • (转)C语言家族扩展收藏 (转)C语言家族扩展
  • (转)socket Aio demo
  • . ./ bash dash source 这五种执行shell脚本方式 区别
  • .NET : 在VS2008中计算代码度量值
  • .NET/C# 项目如何优雅地设置条件编译符号?
  • .NET/C# 在 64 位进程中读取 32 位进程重定向后的注册表
  • ??eclipse的安装配置问题!??
  • [ 数据结构 - C++] AVL树原理及实现
  • [].slice.call()将类数组转化为真正的数组
  • [2017][note]基于空间交叉相位调制的两个连续波在few layer铋Bi中的全光switch——
  • [Excel VBA]单元格区域引用方式的小结
  • [InnoDB系列] -- SHOW INNODB STATUS 探秘