摘要:通過實例講解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