一、概述

  Loadrunner拥有极为丰富的工具箱,供予我们制造出各种奇妙魔法的能力。其中就有此次要讨论的socket套接字操作。

  二、socket概述

  socket是操作系统中I/O系统的网络延伸部分,它扩展了操作系统的基本I/O到网络通信,使进程和机器之间的通信成为可能。如果想完全地理解socket在Loadrunner中如何工作的,熟悉一些关于它的历史会很有帮助。

  当前常用的socket,最早起源于BSD UNIX类的操作系统。在UNIX系统上,比如BSD,把对网络的支持加入操作系统,以一种扩展现有文件描述符(后注)结构的方法来实现的。Socket 可以被看成一个标准的文件描述符。在 UNIX 类的平台上,其中包括open()、read()、write()和close()。很多时间,程序并不需要知道它正在把数据写进一个文件、终端、或是一个TCP连接。

  系统调用被加入并和socket一起工作,而很多现有的系统调用同样能和socket一起工作。因此,一个socket允许您使用标准的操作系统和其他的计算机,以及您自己机器上的不同进程来通信。

  然而,socket的确存在一些不同工作方式。最明显地就是建立socket的方法。很多文件是通过调用open()函数来打开的,但socket是通过调用socket()函数来建立的,并且还需要另外的调用来连接和激活他们。recv()和send()这两个系统调用和read()和write()极为相似。

  Socket是一套建立在TCP/IP协议上的接口不是一个协议,只要底层实现TCP IP协议,都可以用socket进行通信。

  应用层: HTTP FTP SMTP Web

  传输层: 在两个应用程序之间提供了逻辑而不是物理的通信基于流的TCP和基于数据包的UDP

  文件描述符一般是指一个文件或某个类似文件的实体。内核(kernel)利用文件描述符(file descriptor)来访问文件。文件描述符是非负整数。打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。

  文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。

  三、SOCKET连接过程

  根据连接启动的方式以及本地套接字要连接的目标,套接字之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。

  服务器监听:是服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。

  客户端请求:是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。

  连接确认:是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

  四、开发原理

  服务器:使用ServerSocket监听指定的端口,端口可以随意指定(由于1024以下的端口通常属于保留端口,在一些操作系统中不可以随意使用,所以建议使用大于1024的端口),等待客户连接请求,客户连接后,会话产生;在完成会话后,关闭连接。

  客户端:使用Socket对网络上某一个服务器的某一个端口发出连接请求,一旦连接成功,打开会话;会话完成后,关闭Socket。客户端不需要指定打开的端口,通常临时的、动态的分配一个1024以上的端口。

  Socket接口是TCP/IP网络的API,Socket接口定义了许多函数或例程,程序员可以用它们来开发TCP/IP网络上的应用程序。要学Internet上的TCP/IP网络编程,必须理解Socket接口。Socket接口设计者最先是将接口放在Unix操作系统里面的。如果了解Unix系统的输入和输出的话,就很容易了解Socket了。网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。

  五、Loadrunner中socket相关函数浅析

  Loadrunner对于脚本函数有一份帮助文档。利用好此文档,其实对于性能测试所需脚本就已足以。

  当我们打开Create/Edit Scripts,并打开脚本录制页面时,摁下F1便可打开《HP LoadRunner Online Function Reference》。在这帮助文档中找到“键入关键字进行查找”输入框。利用它查找我们所需的socket函数了。

  几乎所有关于socket的函数,都是以lrs开头的。

基本操作函数:

lrs_startup 初始化 WinSock DLL

lrs_create_socket 初始化套接字

lrs_send 在数据报上(UDP)或者向流套接字(TCP)发送数据

lrs_receive 接收来自数据报或流套接字的数据

lrs_disable_socket 禁用套接字操作

lrs_close_socket 关闭打开的套接字

lrs_cleanup 终止 WinSock DLL 的使用,回收相关资源。VuGen 在 Windows 上使用 Windows 套接字协议支持应用程序的录制和回放;而在UNIX 平台上仅支持回放

lrs_accept_connection 接受侦听套接字连接

lrs_close_socket 关闭打开的套接字

lrs_create_socket 初始化套接字

lrs_disable_socket 禁用套接字操作

lrs_exclude_socket 重播期间排除套接字

lrs_get_socket_attrib 获取套接字属性

lrs_get_socket_handler 获取指定套接字的套接字处理程序

lrs_length_receive 接收来自指定长度的缓冲区的数据

lrs_receive 接收来自套接字的数据

lrs_receive_ex 接收来自数据报或流套接字的数据(具有特定长度)

lrs_send 将数据发送到数据报上或流套接字中

lrs_set_receive_option 设置套接字接收选项

lrs_set_socket_handler 设置特定套接字的套接字处理程序

lrs_set_socket_options 设置套接字选项

  缓冲区函数:

lrs_free_buffer 释放分配给缓冲区的内存

lrs_get_buffer_by_name 从数据文件中获取缓冲区及其大小

lrs_get_last_received_buffer 获取套接字上接收到的最后的缓冲区及其大小

lrs_get_last_received_buffer_size 获取套接字上接收到的最后一个缓冲区的大小

lrs_get_received_buffer 获取最后接收到的缓冲区或其一部分

lrs_get_static_buffer 获取静态缓冲区或其一部分

lrs_get_user_buffer 获取套接字的用户数据的内容

lrs_get_user_buffer_size 获取套接字的用户数据的大小

lrs_set_send_buffer 指定要在套接字上发送的缓冲区

  环境函数:

  lrs_cleanup 终止Windows套接字 DLL 的使用

  lrs_startup 初始化 Windows 套接字 DLL

  关联语句函数:

  lrs_save_param 将静态或接收到的缓冲区(或缓冲区部分)保存到参数中

  lrs_save_param_ex 将用户、静态或接收到的缓冲区(或缓冲区部分)保存到参数中

  lrs_save_searched_string 在静态或接收到的缓冲区中搜索出现的字符串,将出现字符串的缓冲区部分保存到参数中

  转换函数

  lrs_ascii_to_ebcdic 将缓冲区数据从 ASCII 格式转换成 EBCDIC 格式

  lrs_decimal_to_hex_string 将十进制整数转换为十六进制字符串

  lrs_ebcdic_to_ascii 将缓冲区数据从 EBCDIC 格式转换成ASCII 格式

  lrs_hex_string_to_int 将十六进制字符串转换为整数

  超时函数:(这一堆函数,是可以对同一个socket生效的)

  lrs_set_accept_timeout 为接受套接字设置超时

  lrs_set_connect_timeout 为连接到套接字设置超时

  lrs_set_recv_timeout 执行lrs_receive命令后,等待服务器返回消息的超时时间,即服务器的响应时间。

  lrs_set_recv_timeout2 创建连接成功,接收到服务器返回的消息后,获取匹配消息的超时时间。lrs_receive接收到数据后,会和预期的数据长度进行比较,如果长度不匹配,它将重新从套接字上读取数据,直到超时为止。

  lrs_set_send_timeout 为发送套接字数据设置超时

  六、实战讲解

  在此只做简单的知识普及,便于快速上手编写socket测试脚本。简述创建连接,收发协议,关闭连接的过程。

  初始化

//存放通信返回报文

char * ActualBuffer="";

//存放返回报文长度,切记附初值

int numberOfResponse = -1;

//链接是否创建成功,判断值

int rc = 0;

//返回报文是否成功,判断值

int msgOk=-1;

//存放返回报文

char * position="";

//返回报文是否成功标识

char * passMsg="succee";

 服务器监听

//--------------创建连接-----------------

rc= lrs_create_socket("socket0", "TCP", "LocalHost=0", "RemoteHost=<RemoteHost>", LrsLastArg);

if (rc==0){

//判断连接是否创建成功

lr_output_message("Socket was successfully created ");

}

else{

lr_output_message("An error occurred while creating the socket, Error Code: %d", rc);

}

//--------------创建连接-----------------

  收发协议

lrs_send("socket0", "buf0", LrsLastArg);

//往“socket0”发送"buf0"

lrs_set_receive_option(EndMarker, BinaryStringTerminator, "</html>");

//设置接收协议包选项,注"</html>"以实际定义协议为准,如果不设置次项。执行到lrs_receive的时候,log里面打印Waiting for writable socket 10

//secs, 0 usecs,都需要等待10秒钟。是这样的,因为你在data.ws中定义了recv buffer的长度,例如你定义为100,但是socket上的返回buffer长度不

//是100,这时候,loadrunner会尝试再次去读取,直到读到长度为100的buffer才算成功。

lrs_receive("socket0", "buf1","Flags=MSG_PEEK ", LrsLastArg);

//将“socket0”中返回的数据存放到“buf1”中

  参数配置

  可能细心的同学已经发现了,buf0与buf1是从哪里来的。其实这俩兄弟是在data.ws中被定义的,如下所示:

  ;WSRData 2 1

  send buf0 5120

  "<参数化>"

  recv buf1 1024

  -1

  5120:此数值为socket协议传输内容长度,切记严格输入正确长度值。

  "<参数化>":为buf0所传输内容。相对于loadrunner的http协议参数用{}来说,socket协议参数化采用<>作为定义符。

  接收参数判断

  在做了接收之后,我们需要提取“buf1”中的某些关键字符作为通信成功标识。

//获取套接字上接收到的最后的缓冲区及其大小

lrs_get_last_received_buffer("socket0",&ActualBuffer,&numberOfResponse);

//查询返回报文是否成功

position = (char *)strstr(ActualBuffer, passMsg);

// strstr has returned the address. Now calculate * the offset from the beginning of str

msgOk = (int)(position - ActualBuffer + 1);

if(msgOk>0){

lr_end_transaction("核心对私维护", LR_PASS);

lr_output_message("本次交易:%s",ActualBuffer);

}

else{

lr_end_transaction("核心对私维护", LR_FAIL);

lr_error_message("本次交易:%s",ActualBuffer);

}

关闭连接

//--------------断开socket--------------

lrs_disable_socket("socket0", DISABLE_SEND_RECV);

//--------------关闭socket--------------

lrs_close_socket("socket0");

  六、总结

  简要描述了利用Loadrunner编写socket性能测试脚本的过程,如有错漏,请予以指正。

注:

 

strstr(str1,str2) 函数用于判断字符串str2是否是str1的子串。如果是,则该函数返回str2在str1中首次出现的地址;否则,返回NULL。

函数原型:extern char *strstr(char *str1, const char *str2);

语法:

* strstr(str1,str2)

str1: 被查找目标 string expression to search.

str2: 要查找对象 The string expression to find.

返回值:若str2是str1的子串,则返回str2在str1的首次出现的地址;如果str2不是str1的子串,则返回NULL。

例子:

char str[]="1234xyz";

char *str1=strstr(str,"34");

cout << str1 << endl;

显示的是: 34xyz;显示匹配后全部数据;