其实在性能测试中HTTP协议居多。但是Socket也是偶尔能遇到
1. 如何开始录制一个最简单的收发数据包脚本
开始录制脚本的时候,使用了一个绿色软件SocketTool.exe,在本机启动了一个TCP服务器端:
使用loadrunner录制windows application,启动一个新的SocketTool.exe,创建一个TCP Client,链接刚才启动的服务器,钩选上显示十六进制值,发送313233,别写空格进去,点击发送数据,然后再在服务器端发送点数据回客户端,最后客户端点击断开,脚本就录制完成了。
脚本就四句:
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
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的细节差异,接收报文也只是长度数据两段式的收取,没有讲到不确定长度使用终止串的收取方法,一篇文章终归难以尽言,抛砖引玉,如有错漏,不吝赐教。