[size=0.76em][size=1.5em]引言
- 在网络世界中,网络中的节点间的信息的传递无疑是一个重要的环节。在 IBM 大型主机进入这个网络世界后,网络节点中的新成员又多了一种。要实现开放平台与 IBM 大型主机之间的数据通信其实并非难事,对于程序员而言,掌握一种实现模型和实现的接口就会使网络编程变得简单,易于实现。Java 的 JDK 就提供可一系列的 API 来完成网络编程。Socket 就是其中的一种。
- 本文的研究背景源于主机环境的 IMS 产品的 IMS Connect 组件的自动化测试的需求。该产品运行于主机环境,但是对于需要在开放平台进行自动化测试的工程师来说,数据的通信无疑是一个需要解决的问题。
- 本文首先会给出一些基本的概念,包括 Socket 编程、FTP 传输协议的基本概念及原理以及 Java 实现 Socket 编程的特点。然后将阐述 IMS Connect 项目面临的问题,即本文实现的背景环境。接着将给出这个背景下的解决方法和实现细节。然后以项目为背景应用及验证。最后本文会将该实现方法推广为通用的解决方法。
[size=0.76em][size=1.2em]示例必备条件
[size=0.76em]示例需要的环境:
- 需要在 Windows 上安装的软件:
- Rational Functional Tester V7.0
- 需要在 IBM z/OS 上安装的环境:
- IMS Version 9 (或者更新的版本)
- IMS Connect Version 9 (或者更新的版本)
- OTMA
- TCP/IP
[size=0.76em]
[size=0.76em][size=1.5em]概念介绍
[size=0.76em][size=1.2em]Socket
[size=0.76em]在网络出现之前的单机系统时代,更多的是进程之间的通信。由于每个进程有自己的地址空间,为了保证两个进程的通信互不干扰又协调工作,操作系统提供了相应的设施。如 UNIX 系统中的管道(pipe)、命名管道(named pipe)和软中断信号(signal)。而在网络中,两个不同计算机中的进程需要通信首先要解决的是进程识别的问题。同一主机上,不同进程可以用进程号(process ID)作为唯一标识,但是在网络的环境里不同的主机上完全可以用同一进程号,所以这种方法来区别进程是不可行的。另外 , 操作系统支持的网络协议很多,不同的协议的工作方式是不一样的,包括网络的地址格式也不同。因此,还需要考虑不同网络协议的识别问题。
[size=0.76em]这个问题催生了 Socket。Socket 又称为“套接字”,用于描述网络地址与端口,它是一个通信的接口。它是应用层与 TCP/IP 协议族通信的中间的软件抽象层,它位于运输层和网际层之上,又位于应用层之下,作为一个抽象层存在。在设计模式中,可以把 Socket 的设计想象成“门面模式”。它把负责的 TCP/IP 协议族隐藏在 Socket 接口的后面,对用户而言,之需要找到 Socket,然后后面的事就让 Socket 的去组织。图 1 描述了 Socket 的作用和所处的位置。
图 1. Socket 抽象层的位置
[size=0.76em][size=1.2em]FTP 传输协议
[size=0.76em]FTP(File Transfer Protocol) 远程文件传输协议是众多应用层协议的一种。它工作在 OSI 模型的第七层,TCP 模型的第四层,即应用层。它是为了简化 IP 网络上系统之间文件传送的协议,所以与两台计算机所处的位置,连接的方式甚至是否使用同样的操作系统是无关的。这也正符合本文研究的开放平台与 IBM 主机环境传输文件的问题背景。
[size=0.76em]相比于 HTTP(HyperText Transfer Protocol,超文本传输协议),FTP 协议工作需要两个端口(HTTP 需要一个端口 80),一个端口是作为控制连接端口,也就是 21 端口,用于发送指令给服务器以及等待服务器响应;另一端口是 20(仅限于 PORT 模式),用于数据的传输,主要作用是从客户向服务器发送文件的。
[size=0.76em]下面给出了 Passive 模式和 Port 模式的示例图 :
图 2. Port 模式 端口示意图
图 3. Passive 模式 端口示意图
[size=0.76em][size=1.2em]Java Socket 编程
[size=0.76em]要想用 Java 来实现 Socket 这种客户机到服务器的模型,其实并非难事。Java 的 JDK 中有很多 API 帮助程序员来实现这个模型,这些 API 封装在 java.net 这个包里。正是由于这些 API 的存在,使用 Java 来创建您所在位置的 Server 十分简单。
ServerSocket server=new ServerSocket(1234);
|
[size=0.76em]这样您就创建了一个 Server 的实例,另外要注意,端口从 0 到 65535 之间,但是前 1024 端口已经被 TCP/IP 作为保留端口,所以只能选择 1024 之后的端口。这段 code 就建立了以
[size=0.76em]1234 为端口的 Server。
[size=0.76em]Server 建立好了,现在需要的是一个 Client 来连接至刚才建立好的 Server。
[size=0.76em]Socket client = new Socket(InetAddress.getLocalHost(),1234);由于这个 Client 和 Server 处在同一台机器,所以使用 InetAddress.getLocalHost() 来获得本机的 IP 地址,如果您的 Server 是一个远程的机器,则可以使用它的 IP。例如:Socket client = new Socket( www.test.com, 4321); 此处的域名就是您需要连接的 Server 的 hostname。
[size=0.76em]建立连接之后,数据的传输还依赖于 Java 的 IO 相关的包,所以还必须引入 java.io 包。Java 的操作也并不复杂,它提供了字节流和 Unicode 的读写,也提供了一些缓冲用于数据的读写。代码 1 给出一个 Client 端的 Socket 编程示例:
清单 1. Client 端的 Socket 编程示例
public class Client
{
Socket socket;
BufferedReader in;
PrintWriter out;
public Client()
{
try
{
socket = new Socket("xxx.xxx.xxx.xxx", 1234);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(),true);
BufferedReader line = new BufferedReader(new InputStreamReader(System.in));
out.println(line.readLine());
line.close();
out.close();
in.close();
socket.close();
}
catch (IOException e)
{ }
}
……
}
|
[size=0.76em]
[size=0.76em][size=1.5em]项目面临的问题
[size=0.76em]本文的研究背景是一个正在实施的项目,所以具有一定的现实意义。该项目是对一个具有 40 多年历史的 IBM 主机层次型数据库产品 - IMS 的一个通信组件 IMS Connect 进行自动化测试。本节将首先简要介绍 IMS 及 IMS Connect 的环境及架构,然后描述自动化测试面临的问题。
[size=0.76em]IMS 是 IBM 的一个软件产品,具有为大型商业应用系统提供事务管理和数据库管理的功能,最初发布于 1968 年,是一个历史悠久的产品。IMS 由数据通信管理器(IMS TM,最初称为 DC),数据库管理器(DB)及一套系统服务设施组成。
[size=0.76em]IMS 是一个层次型数据的管理系统,只能运行在 z/OS 系统上。DB 主要负责支持 IMS 的层次型数据库模型,它为应用层提供了存储数据库的 API 并且保持数据库的一致性。应用层不需要知道数据库的底层组织,因为有 Data Language/Interface(DL/I,类似于 SQL 的 API)将其分离。而 IMS TM 负责与应用程序进行交互,通过消息队列保持整个通信的顺畅,当然在入队和出队时也会调用到 DL/I 的 API。系统服务组件 TM 和 DB 提供公用的服务,以保持两者的正常工作。图 4 描述了整个 IMS 的简要架构。
图 4. IMS 结构的简略图
[size=0.76em]IMS Connect 是一个 IMS 的通信组件。通过 IMS Connect 可以使多个 IMS 系统与多个 IMS Connect 的客户端程序保持高效的 TCP/IP 的通信。可以简单把 IMS Connect 理解为外部客户端需要访问的 IMS TCP/IP 服务器。通过这个服务器,任何的 TCP/IP 客户端程序可以与 IMS 建立连接。目前,IMS Connect 对 IMS TM 和 IMS DB 都是支持的。图 5 描述了 IMS Connect 组件在整个 IMS 系统中的位置。图中的 OTMA 是开放事务管理访问,是一个访问协议,ODBM 是开放数据库管理器,负责数据库端的接口。
图 5. IMS Connect 组件的位置
[size=0.76em]由于 IMS Connect 是一个 TCP/IP 的服务器,就一定需要开放相应的端口,让客户端程序能够与之通信。IMS Connect 有自己的配置文件,在配置文件中能够自定义需要开放的端口,以及一些 TCP/IP 的 Socket 连接的缓冲设置等。
面临的问题
[size=0.76em]本文涉及的这个项目正是针对 IMS Connect 配置文件升级的测试。由于 IMS Connect 配置文件更新了,相应的 IMS Connect 作为 TCP/IP 服务器所表现的一些特征也会改变。本文以 IMS Connect 的最大 Socket 连接数配置参数(MAXSOC)为例进行说明。
[size=0.76em]测试的流程如下:
- 创建 IMS Connect 配置文件 HWSCFG01
- 设置 HWSCFG01 中的 MAXSOC = 60(默认值 50)
- 使用 HWSCFG01 配置文件重新启动 IMS Connect
- 输入 IMS Connect 查看配置参数命令(VIEWHWS),查看 IMS Connect 的最新参数 MAXSOC 是否已经和预期的 60 匹配
- 输入 IMS Connect 查看工作状况的命令(QUERY MEMBER),确保 IMS Connect 按照新的 MAXSOC 参数工作
- 模拟客户端程序发送最大值的 Socket 连接测试 IMS Connect 能按照预期的工作,当到达峰值时拒绝连接。
[size=0.76em]按照这个测试流程,本文需要实现的情况是在开放平台上创建多个配置文件(HWSCFG01 ~ HWSCFGxx),以边界值测试法尽量覆盖可能的最大 Socket 参数,确保 IMS Connect 正确的工作。图 6 描述了这个测试环境的示意图。
图 6. 项目测试环境示意图
[size=0.76em]如果所有的工作都在主机环境上进行无疑会减少很多不必要的麻烦,但是由于测试的后续工作主要在模拟客户端发送 Socket 连接以及判断测试是否被拒绝上,所以项目经理选择了在开放平台上做这个交互式的测试。所以本项目的前期工作无疑是解决如何在开放平台与 IBM 大型主机之间进行文件传输的问题。
[size=0.76em]
[size=0.76em][size=1.5em]解决方法及实现细节
[size=0.76em]本文在测试 IMS Connect 项目时,使用了 Rational Functional Tester(RFT) 和 Rational Functional Tester Extension (FTE,一个 z/OS 终端 3270 和 5250 的模拟界面)两个工具。根据 FTE 的界面,另外有一个 TerminalUtility 的工具 Jar 包,用于对 FTE 进行必要输入和操作。
[size=0.76em]在 RFT 中使用的自动化测试脚本实际上是 Java 语言的 Script,所以要本文在解决开放平台和 IBM 大型主机之间文件传输问题也采用 Java 语言实现。要将之前所述的测试流程自动化,需要在测试流程 1 之后加入关键的一步,即使用 Java 实现将开放平台的配置文件上传至 IMS Connect 需要读取配置文件的功能。这个功能在底层将会用到 FTP 协议,所以可以按照 FTP 协议的要求来逐步完成。
[size=0.76em]首先,需要获得 IMS Connect 所在主机 IP 地址,在本案例中将会使用 IMS Connect 的产品的特有命令 QUERY MEMBER 来查看 IMS Connect 的运行状况,从而获得 IP 地址。如下图所示 :
[size=0.76em]为了用 Socke 编程实现文件通过 FTP 协议上载文件的功能,本文创建了 FTPUpload 类。代码 2 给出该类的结构:
清单 2. FTPUpload 类结构:
public class FTPUpload {
String ipAddress; // 需要上传文件的 Server 的 IP address
String remotDir; // 需要上传文件到主机的目录
String localFilePath; // 需要上传的开放平台文件路径
String userName; // 用户名
String passWord; // 密码
Socket controlSocket;// 控制用 Socket
public PrintWriter controlOutput;// 控制输出用的流
public BufferedReader controlInput;// 控制输入用的流
final int controlPort = 21;// FTP 的控制用端口
ServerSocket serverDataSocket;
// 构造函数
FTPUpload(String ipAddress, String username, String password,
String remotDir, String localFilePath ){ …… }
// 由 ipAddress 和 controlPort 号构造 Socket,形成控制用的流
public void openConnection(String host) throws IOException,
UnknownHostException { …… }
// 关闭控制用的 Socket
public void closeConnection() throws IOException { …… }
// 登陆 FTP 服务器
public void login() { …… }
// 改变远程的目录
public void changeRemDir() { …… }
// 将文件由本地上传至服务器
Public void uploadFile(String remoteFileName){ …… }
// 构造与服务器交换数据用的 Socket
// 再用 PORT 命令将端口通知服务器
public Socket dataConnection(String ctrlcmd) { …… }
// 根据 type 的输入选择不同的传输类型(1-ASCII 2-Binary)
public void transferType(int type){ …… }
……
……
}
|
[size=0.76em]从结构可以看出,该类有十个基本属性。其中,IBM 主机的 IP 地址、需要的访问账号、密码,本地需要上传的文件目录以及上传需要的远程路径,这个五个属性需要在构造该类的时候给出以便其他方法的执行:
清单 3.FTPUpload 类构造函数
FTPUpload(String ipAddress, String username, String password,
String remotDir, String localFilePath ){
this.ipAddress = ipAddress;
this.userName = username;
this.passWord = password;
this.remotDir = remotDir;
this.localFilePath = localFilePath;
}
|
[size=0.76em]openConnection 方法通过构造的 IP 地址的属性和默认的 FTP 控制端口 21 初始化该类的另一个属性用于控制的 Socket(controlSocket),并且通过 controlSocket 生成需要相应的输入输出流实例。
清单 4 FTPUpload 类 openConnection 函数 :
public void openConnection() throws IOException,
UnknownHostException {
controlSocket = new Socket(ipAddress, controlPort);
controlOutput = new PrintWriter(
controlSocket.getOutputStream());
controlInput = new BufferedReader(
new InputStreamReader(controlSocket.getInputStream()));
}
|
[size=0.76em]doLogin、changeRemDir 和 uploadFile 三个方法是该类的重要方法,负责登陆服务器,改变远程目录和上传文件。dataConnection 方法的功能是建立用于数据传输的 Socket,uploadFile 方法将会调用这个方法来实现。
清单 5 FTPUpload 类 login,changeRemDir,uploadFile 和 dataConnection 函数 :
public void login() {
try {
controlOutput.println("USER " + userName);
controlOutput.flush();
controlOutput.println("PASS " + passWord);
controlOutput.flush();
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
public void changeRemDir () {
try {
controlOutput.println("CWD " + remotDir);// CWD 命令
controlOutput.flush();
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
public void uploadFile(String remoteFileName) {
try {
int n;
byte[] buff = new byte[1024];
FileInputStream sendfile = null;
// 准备读出客户端上的文件
try {
sendfile = new FileInputStream(localFilePath);
} catch (Exception e) {
System.out.println("文件不存在");
return;
}
// 准备发送数据的流
Socket dataSocket = dataConnection("STOR " + remoteFileName);
OutputStream outstr = dataSocket.getOutputStream();
while ((n = sendfile.read(buff)) > 0) {
outstr.write(buff, 0, n);
}
dataSocket.close();
sendfile.close();
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
public Socket dataConnection(String ctrlcmd) {
String command = "PORT ";
int i;
Socket dataSocket = null;
try {
// 本地 IP 地址
byte[] address = InetAddress.getLocalHost().getAddress();
// 使用任意端口构造传输的 socket,backlog 为 1
serverDataSocket = new ServerSocket(0, 1);
// 准备传送 PORT 命令用的数据
for (i = 0; i < 4; ++i)
command = command + (address & 0xff) + ",";
command = command + (((serverDataSocket.getLocalPort()) / 256) & 0xff)
+ "," + (serverDataSocket.getLocalPort() & 0xff);
// 利用控制流先发送 PORT 命令
controlOutput.println(command);
controlOutput.flush();
// 发送控制命令
controlOutput.println(ctrlcmd);
controlOutput.flush();
dataSocket = serverDataSocket.accept();
serverDataSocket.close();
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
return dataSocket;
}
|
[size=0.76em]
[size=0.76em][size=1.5em]应用及验证
[size=0.76em]本节将就之前描述的项目背景测试的流程,使用上一节的方法来实现。在本项目中,通过 FTE 的界面使用 TSO 登陆远程的 IBM 主机的测试环境。在测试环境中,IMS Connect 产品使用 USER.PRIVATE.PROCLIB 的 DataSet 作为配置文件的目录。首先验证,在没有上传需要测试的配置文件时,该目录下的情况如下图,可以看出该 DataSet 中目前只有 HWSCFA01 和 SQARCJCL 两个 Member:
图 7. 上传文件之前 USER.PRIVATE.PROCLIB 目录下的 Datasets
[size=0.76em]需要上传的 IMS Connect 的配置文件如下,以测试 MAXSOC 为例,修改 MAXSOC 为 60:
图 8. IMS Connect 的配置文件
[size=0.76em]需要执行的自动化脚本会调用本文实现的 FTPUpload 类的 uploadFile 方法,代码 6 给出了部分脚本:
清单 6 自动化脚本示例
public void runScenarios1(String configNumber,String[]
verifyTitles,String[] verifyContents,int pages){
//Put IMS Connect Config file in UPP and restart
FTPupload ftpupload = new FTPupload(hostName,
userName,psw,remoteDir1);
// 上传 IMS Connect 配置文件
ftpupload.UploadFile(localPath +
"C:\\config"+configNumber+".txt", "HWSConfig"+ configNumber);
//terminal 为 TerminalUtility 的实例,用来操作 FTE
// 重启 IMS Connect,让配置文件生效
terminal.restartIMSConnect(HWS1);
// 通过 VIEWHWS 命令验证输出
terminal.imsConnectCommand(
ClearBefore,
HWS1,
IMSConnect.CommandInput.VIEWHWS,
"MAXSOC = 60");
// 通过 Query Member 命令验证输出
terminal.mvsCommand(
ClearBefore,
IMSConnect.CommandInput.QRY_MEMBER("IMSCON"),
"MAXSOC = 60");
}
|
[size=0.76em]通过 IMS Connect 的查看 VIEWHWS 命令和 QUERY MEMBER 命令,可见配置文档已经生效,脚本执行成功。
图 9. 通过 VIEWHWS 查看 IMS Connect 的配置
[size=0.76em]执行脚本之后,重新登陆 TSO,查看相应的目录,新的 IMS Connect 配置文件已经上传成功。
图 10. 上传文件之后 USER.PRIVATE.PROCLIB 目录下的 Datasets
[size=0.76em]在 FTPUpload 中加入相应的输出 FTP 反馈信息的函数,可以在脚本运行完后可以从 console 窗口打印出 FTP 上传的过程,如下是 console 的部分打印信息:
图 11. RFT 运行脚本的后台输出
[size=0.76em]
[size=0.76em][size=1.5em]推广应用
[size=0.76em]在本文中,基于项目的需求通过 Java Socket 编程实现了从开放平台到 IBM 主机环境的文件传输的 upload 的功能。基于 FTP 的命令其实有很多,一些常用的如下:
表 1.FTP 常用命令
命令
描述
ABOR
中断数据连接程序
ACCT <account>
系统特权帐号
CDUP <dir path>
改变服务器上的父目录
MODE <mode>
传输模式(S= 流模式,B= 块模式,C= 压缩模式)
NOOP
无动作,除了来自服务器上的承认
PASV
请求服务器等待数据连接
REIN
重新初始化登录状态连接
SMNT <pathname>
挂载指定文件结构
DELE <filename>
删除服务器上的指定文件
PWD
显示当前工作目录
NLST <directory>
列出指定目录内容
RETR <filename>
从服务器上找回(复制)文件
[size=0.76em]根据不同的场景,可以将 FTPUpload 进行扩展,在一些跨越两个环境的项目中,可能需要 download 的功能,或者需要上传二进制文件的功能等等,都可以对这种方法进行扩展,以实现不同项目的需求。
[size=0.76em]为了说明推广的可行性,代码 7 给出了 NLST 命令的示例:
清单 7 NLST 命令实现示例
public void list() {
try {
int n;
byte[] buff = new byte[65530];
Socket dataSocket = dataConnection("NLST ");
BufferedInputStream dataInput = new BufferedInputStream(
dataSocket.getInputStream());
while ((n = dataInput.read(buff)) > 0) {
System.out.write(buff, 0, n);
}
dataSocket.close();
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
|
[size=0.76em]运行该方法后可以得到主机上某个 DataSet 下面的所有 Member 信息,如下:
125 Storing data set USER.PRIVATE.PROCLIB(HWSCFG01)
250 Transfer completed successfully.
200 Port request OK.
HWSCFA01
HWSCFG01
SQARCJCL
|
[size=0.76em]
[size=0.76em][size=1.5em]总结
[size=0.76em]在 IBM 大型主机的发展的历程中,伴随的出现了许多主机的产品,例如 CICS、IMS、DB2 等等。这些大型主机的产品的许多配置文件,日志文件都被存在大型主机的 Dataset 中,对在开放平台上的测试人员来说无疑是一个巨大的挑战。当需要进行跨越不同环境的自动化测试时,不可避免的需要使用跨平台的文件传输协议。
[size=0.76em]本文以 IMS 的 IMS Connect 组件的自动化测试为场景,研究并实现了通过 Java Socket 编程实现的 FTP 文件传输的功能。该实现方法是对 FTP 传输协议客户端的实现,并且还有许多可以扩展的空间,是 Java Socket 编程初学者的较好的示例。本文给出了较为详细的代码示例,并演示和验证了代码的可行性。
[size=0.76em]最后,本文为可能面临同样的或类似问题的人提供了一些推广和扩展的思路,希望能有利于应用开发或者测试工程师更好的完成工作。 |