其實在性能測試中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的細節差異,接收報文也只是長度數據兩段式的收取,沒有講到不確定長度使用終止串的收取方法,一篇文章終歸難以盡言,拋磚引玉,如有錯漏,不吝賜教。