1. 程式人生 > >自行控制loadrunner的socket協議效能測試

自行控制loadrunner的socket協議效能測試

[-]

摘要:通過例項講解loadrunner中的socket協議效能測試的一種測試方法,如何不依賴loadrunner既定規則,自行控制收發資料包

關鍵詞:Loadrunnersocket,自行控制,收發資料包

一.前言

用過loadrunnersocket協議進行效能測試的同學都知道,只需要錄製短短的幾句命令,就可以實現socket的連結、收發資料包和關閉連結,一時大爽,不過緊跟著的就是沒完沒了的折磨。剛開始引數化資料包傳送接收都行,慢慢的發現,很多情況下,收發資料包的長度和內容都是不可確定的,加上十六進位制和ASCII,甚至協議和加密等等因素混合在一起,簡直就是災難。於是自行控制資料包收發成了可選項,雖然loadrunner

提供了相關的函式,但是真的面對進位制轉換,面對沒完沒了的<memory violation : Exception ACCESS_VIOLATION received>,很多人只能另外尋找辦法完成任務。

本來想全面剖析loadrunnersocket協議效能測試,發現需要釐清的細節太多了,只能盡力講清楚下面這個例子中遇到的各個知識點了。

二.任務的提出

這個效能測試是很常見的一種情況,前置機連結了各類不同的硬體裝置客戶端,各個硬體裝置客戶端使用了不同的協議,協議承載了大量的不同業務,不過資料包的基本結構相同,由首部、包體和校驗碼組成,既有TCP連結也有UDP連結,資料傳送方式上都是使用的短連結,也就是連結上伺服器,傳送完資料就立刻關閉了連結。現在需要loadrunner

模擬不同的硬體裝置,測試前置機的併發能力。

資料包結構:

 006.jpg

系統架構:

001.jpg

三.實現方案討論

這個場景很常見,不過也比較複雜。

如果採用傳統的錄製回放,需要先選擇幾種有代表性的硬體型別和重點業務,錄製出指令碼,可以想象需要錄製的指令碼有很多,如果進行引數化,必須要搞清楚各種協議,重新組包,這個工作量太大了。

或者開發提供兩個動態連結庫,一個用來對各種協議實現編解碼,另外一個包括了需要模擬的硬體型別的重點業務,第二個動態連結庫呼叫第一個,在loadrunner中載入了動態連結庫以後,直接呼叫相關的業務操作函式就可以了。這個夠通用,不過開發誰有空搭理你呀。況且如果說這個,這篇文章就不用寫了。

那還有第三種方法,現在收發的資料包在前置機上有日誌檔案儲存,可以將各種硬體型別傳送的資料包日誌檔案分類蒐集到,然後做兩個指令碼,一個TCP的,一個UDP的,邏輯都是同樣的,開啟資料表日誌檔案,讀出資料包傳送,將傳送和接收到的資料包寫入本地日誌檔案,這樣就只需要編寫兩個指令碼,拷貝出多份,每個指令碼下放入不同的資料包檔案模擬出不同的硬體型別。

看起來這種方式最簡單,再分析一下,是否可行。

很多協議中會在連結上伺服器後,伺服器端提供一個唯一串返回,做為一次通訊的唯一標識,加入到後續的資料包中,這裡協議倒是沒有這個問題,要不每次傳送資料包前,還得根據返回的唯一串來修改要傳送的資料包,真是幸運。

這樣看來建立連結後,資料包可以不做任何修改就能傳送出去。不過有些業務,例如增加業務,前置機接收到任務後,可能會寫入表,如果已經存在可能會衝突,所以測試前需要清空資料庫,只保留初始化資料。

這樣還有一個好處,測試的業務和實際生產的業務是完全一致的,無論種類還是比例。缺點是這裡的資料包檔案會不會不夠大,發一會就發完了,看來還需要有個工具來生成足夠多的資料包的檔案。不過怎麼說也是鬆散耦合了。

經過確認,也沒有出現某硬體的某個業務,混合使用TCPUDP的情況。

看來這個方案沒有太大的問題,就這樣吧。

四.技術要點講解

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)*BufValBufVal指向的值一個個位元組讀出,按照無符號數解讀為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];

通過atoiASCII碼轉換為int值,比如cT2=”160”atoi後就成了數值的160

五.小節

學多用少是一個大的戰略原則,儘可能用最簡單最適合的法子解決問題,loadrunnersocket測試本篇中沒有提到如何和引數打交道的問題,也沒有描述UDPTCP的細節差異,接收報文也只是長度資料兩段式的收取,沒有講到不確定長度使用終止串的收取方法,一篇文章終歸難以盡言,拋磚引玉,如有錯漏,不吝賜教。

程式碼和工具下載: