自行控制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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM