自行控制loadrunner的socket協議效能測試
摘要:通過例項講解loadrunner中的socket協議效能測試的一種測試方法,如何不依賴loadrunner既定規則,自行控制收發資料包
關鍵詞:Loadrunner,socket,自行控制,收發資料包
一.前言
用過loadrunner的socket協議進行效能測試的同學都知道,只需要錄製短短的幾句命令,就可以實現socket的連結、收發資料包和關閉連結,一時大爽,不過緊跟著的就是沒完沒了的折磨。剛開始引數化資料包傳送接收都行,慢慢的發現,很多情況下,收發資料包的長度和內容都是不可確定的,加上十六進位制和ASCII,甚至協議和加密等等因素混合在一起,簡直就是災難。於是自行控制資料包收發成了可選項,雖然loadrunner
本來想全面剖析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的細節差異,接收報文也只是長度資料兩段式的收取,沒有講到不確定長度使用終止串的收取方法,一篇文章終歸難以盡言,拋磚引玉,如有錯漏,不吝賜教。
程式碼和工具下載: