The Client Access (PC Support) data queue application program interfaces (APIs) offer a very efficient means of communicating between a PC and an AS/400. However, designing an application around these APIs can present a challenge. You have to write programs for both the client and the server side of the application to use data queue APIs, and you must decide what responsibility each program will have to ensure cooperation between programs.
In this article, I'll share what I've learned from having designed and written a simple inquiry application using the Client Access data queue APIs. I'll discuss some benefits and potential pitfalls of this design approach, and I'll show you an example of a Visual Basic application.
Data Queue API Concepts
As I mentioned, data queues provide a very efficient way for application programs to pass data to each other. The CL Programmers Guide states, "Data queues are the fastest means of asynchronous communication between two jobs." Even though data queues reside on the AS/400, programs running on both the PC and the AS/400 have access to them.
Client Access provides APIs that allow PC applications to access and manipulate data queues. On the AS/400, similar APIs are provided as part of OS/400. Both sets of APIs provide the ability to perform the following types of actions on data queues:
o Creating a data queue o Deleting a data queue o Sending data to a data queue o Receiving data from a data queue o Clearing all entries from a data queue
Here's an example of how you can use data queues in a client/server environment. Write a client program on the PC that sends data to an AS/400 data queue. Then write a server program on the AS/400 to receive the data and perform actions based on the request contained in the data. If the client requests data from an AS/400 database file, the server can send data back to the client through a data queue.
When the client program sends data to a data queue, the server program needs to be active in order to receive the data and perform a task. If the program isn't active, the data sits in the queue and the server's task goes uncompleted. Therefore, it's common to write the server as a batch program that runs indefinitely. The program waits until it receives data from a data queue and takes only a minimal amount of system resource.
You can configure these types of programs as autostart jobs so that they become active when you start a selected subsystem. You can then start the subsystem from the AS/400's initial program, so that the server program becomes active every time the AS/400 is powered up. For information on how to configure an autostart job, refer to the AS/400 Work Management guide.
When you write the client and server programs, make sure both programs agree on the format of the data. When a program receives data from a data queue, it has no way to know where one field begins and another ends unless it's agreed on ahead of time. Although each of the programs sends and receives data through a buffer, this buffer doesn't contain any information about the format of the data.
You also need to decide which program will perform data translation. Since PCs store data in ASCII and the AS/400 stores it in EBCDIC, one of the programs has to be responsible for translating between the two formats. Both Client Access and OS/400 provide data translation APIs to accomplish this task.
Another decision you must make is where certain functions should be performed. Both the client and the server programs can perform the same data queue operations. For example, if an application needs to create a data queue, you can write code for it in either the client or the server program. But how do you decide where to place that responsibility?
The answer becomes clear when you keep one goal in mind. In any client/server environment, you want to minimize the amount of communications traffic between the two systems. Any request that crosses between the client and the server slows down the overall performance of the application. When deciding which program should create a data queue, it makes sense to choose the server, since that's where the data queue will reside. This type of common sense reasoning is important because of the effect it can have on performance.
It's a good idea to periodically delete and re-create the data queues being used. When an entry is received from a data queue, the entry is removed but its storage is not released, so the data queue grows. Since performance is best when data queues are small, periodically re-creating the data queues keeps them at their optimal size.
Example Application Overview
The client/server application in this article uses data queues to pass data between a client program running on the PC and a server program running on the AS/400. This sample program allows a user to display the names of all database files in an AS/400 library in a window. 1 shows an example of the window.
The client/server application in this article uses data queues to pass data between a client program running on the PC and a server program running on the AS/400. This sample program allows a user to display the names of all database files in an AS/400 library in a window. Figure 1 shows an example of the window.
The sample application contains four components: a client program written in Visual Basic, a client data queue, a server program written in RPG, and a server data queue. These components are illustrated in the diagram in the At a Glance box at the beginning of this article. The arrows show the flow of data through the application. You may want to refer to this diagram as I describe the components.
The client program CLI001VB begins by presenting you with a window that contains an input field for an AS/400 library name. When you enter a library name and click the OK button, the program sends it to the CLI001DQ data queue.
The server program SRV001RG receives the library name and uses it to retrieve the names of all the database files in that library. The server program sends these file names to the SRV001DQ data queue. The client program receives the file names from the data queue and presents them in the window. At this point, you can either enter another library name or select the Exit button to quit.
Program SRV001RG retrieves file names by reading records from the QADBXREF file in QSYS. OS/400 automatically maintains this file, which contains a record for each database file on your system.
I used QADBXREF because it's on every AS/400. However, you could use one of your own files to create a client/server application. For example, you could write an inventory application that presents a list of item descriptions along with their quantities on hand. This type of application could be run by a Windows user who needs access to AS/400 information without requiring terminal emulation.
The Walk-through
2 through 7 contain the code for the client program CLI001VB. Although this code is all part of one program, I've broken it down into small pieces to facilitate this discussion. 2 shows the properties for the form and controls, which are set at design time.
Figures 2 through 7 contain the code for the client program CLI001VB. Although this code is all part of one program, I've broken it down into small pieces to facilitate this discussion. Figure 2 shows the properties for the form and controls, which are set at design time.
3 contains the declaration section of the form. The highlighted section shows the function declaration statements for the Client Access data queue APIs used in this program. The data queue APIs reside in the EHNDQW.DLL dynamic link library (DLL).
Figure 3 contains the declaration section of the form. The highlighted section shows the function declaration statements for the Client Access data queue APIs used in this program. The data queue APIs reside in the EHNDQW.DLL dynamic link library (DLL).
The program starts executing at the beginning of the Form_Load subroutine shown in 4. This routine begins by initializing the run-time attributes of the grid control, which is used to display the database file names. The attributes are changed in order to set the width of the columns and insert column headings.
The program starts executing at the beginning of the Form_Load subroutine shown in Figure 4. This routine begins by initializing the run-time attributes of the grid control, which is used to display the database file names. The attributes are changed in order to set the width of the columns and insert column headings.
The highlighted code in the Form_Load subroutine shows the execution of the EHNDQ_SetMode API. This API controls whether data is converted between ASCII and EBCDIC as it moves between the PC and the AS/400. This is the easiest way to translate data when using the data queue APIs, since the translation occurs automatically once the mode is set. There are other APIs in both Client Access and OS/400 that perform data translation on an individual basis, but you have to use them each time you send and receive data. With the Set Mode API, you can just set it and forget it.
5 shows the cmdOK_Click subroutine, which executes when the user clicks the OK button. This subroutine converts the library name entered by the user to upper case in preparation for sending it to the AS/400.
Figure 5 shows the cmdOK_Click subroutine, which executes when the user clicks the OK button. This subroutine converts the library name entered by the user to upper case in preparation for sending it to the AS/400.
Label A in 5 shows the execution of the EHNDQ_Clear API, which ensures that the server data queue is empty before requesting a new batch of file names. The subroutine checks for errors on the API call and, if none exist, it executes the EHNDQ_Send API at Label B in 5. This API sends the library name in a 10 byte buffer to the client data queue CLI001DQ. The subroutine again checks for errors. If none exist, it executes the ReceiveData subroutine at label C.
Label A in Figure 5 shows the execution of the EHNDQ_Clear API, which ensures that the server data queue is empty before requesting a new batch of file names. The subroutine checks for errors on the API call and, if none exist, it executes the EHNDQ_Send API at Label B in Figure 5. This API sends the library name in a 10 byte buffer to the client data queue CLI001DQ. The subroutine again checks for errors. If none exist, it executes the ReceiveData subroutine at label C.
At this point, the client program has sent a library name to the AS/400. The server program needs time to receive the library name and send back the database file names. While this is happening, the client program waits for the data to arrive on the SRV001DQ data queue.
6 shows the ReceiveData subroutine of the client program. This subroutine performs a loop where it receives entries from the server data queue. Label A in 6 shows the subroutine executing the EHNDQ_Receive API, which receives one entry through a 62 byte buffer from the server data queue. This 62 byte buffer contains 10 bytes for the file name, two bytes for the attribute, and 50 bytes for the text description. The subroutine checks for errors on the API call and, if none exist, it adds the entry into the grid at label B in 6. This loop is repeated until an end of file marker (/*) is received.
Figure 6 shows the ReceiveData subroutine of the client program. This subroutine performs a loop where it receives entries from the server data queue. Label A in Figure 6 shows the subroutine executing the EHNDQ_Receive API, which receives one entry through a 62 byte buffer from the server data queue. This 62 byte buffer contains 10 bytes for the file name, two bytes for the attribute, and 50 bytes for the text description. The subroutine checks for errors on the API call and, if none exist, it adds the entry into the grid at label B in Figure 6. This loop is repeated until an end of file marker (/*) is received.
The server program sends an end of file marker to let the client program know when there are no more entries to receive from the server data queue. The EHNDQ_Receive API has a maximum wait time, which the program sets to five seconds. If the API doesn't receive an entry from the data queue during that time, the API returns an empty buffer. By using an end of file marker, the program knows immediately when to stop receiving entries from the data queue instead of waiting for the maximum time to expire. This makes the application run more efficiently.
7 contains five small subroutines that I'll discuss in the order shown in the figure. The first subroutine, txtLibrary_Change, executes whenever the user changes the library name. I used this subroutine to enable the use of the OK button only if the user has entered some text into the library text box. This prevents the user from sending a blank library name to the AS/400.
Figure 7 contains five small subroutines that I'll discuss in the order shown in the figure. The first subroutine, txtLibrary_Change, executes whenever the user changes the library name. I used this subroutine to enable the use of the OK button only if the user has entered some text into the library text box. This prevents the user from sending a blank library name to the AS/400.
The txtLibrary_KeyPress subroutine executes whenever the user presses a key while the cursor is in the library text box. This subroutine simulates clicking the OK button if the user presses the Enter key, making the program easier to use.
The txtLibrary_GotFocus subroutine executes whenever the library text box receives the focus (i.e. allows input from the user.) This subroutine highlights the library name in the library text box after the program has processed the library. This makes the program easier to use by allowing the user to enter subsequent library names without having to delete the previous one. (Windows automatically deletes highlighted text when you type the first character of new text.)
The cmdExit_Click subroutine executes when the user clicks the Exit button. This subroutine only contains one line of code that unloads the form. Since there is only one form, the program ends. Visual Basic executes the Form_QueryUnload sub-routine before ending.
The Form_QueryUnload subroutine executes when the form is unloaded. There are a number of ways this subroutine could be executed, including the user selecting the Exit button or selecting Close from the control-menu box in the upper left corner of the window. This subroutine ensures that, no matter how the user exits the application, the program will execute the EHNDQ_Stop API. This API stops all data queue activity for the session.
The server program SRV001RG is shown in 8. It begins by running an initialization routine (*INZSR) that uses QCMDEXC to delete and recreate the two data queues used in this application. The program then executes the main routine, where it drops into a loop. At the top of this loop, the program executes the RCVDTA subroutine. The RCVDTA subroutine executes the QRCVDTAQ API, causing the program to wait for a library name.
The server program SRV001RG is shown in Figure 8. It begins by running an initialization routine (*INZSR) that uses QCMDEXC to delete and recreate the two data queues used in this application. The program then executes the main routine, where it drops into a loop. At the top of this loop, the program executes the RCVDTA subroutine. The RCVDTA subroutine executes the QRCVDTAQ API, causing the program to wait for a library name.
When a library name arrives on the client data queue, the loop in the main routine reads all records in the QADBXREF file for that library and executes the SNDDTA subroutine for each record. The SNDDTA subroutine executes the QSNDDTAQ API, which sends a data structure containing a file name, attribute, and text to the server data queue. At the end of the loop in the main routine, the program executes the SNDDTA subroutine one last time to send an end of file marker. This lets the client program know that there are no additional records.
Practical and Efficient
Before starting this project, I was skeptical about the practicality of designing an application around the data queue APIs. I assumed that using the Remote SQL or File Transfer APIs would be a simpler design since those APIs don't require you to write your own server programs. After going through this exercise, I realize this design is not just practical, it's one of the most efficient techniques I've seen for writing client/server applications. Robin Klima is a senior technical editor for Midrange Computing.
REFERENCES
AS/400 CL Programming (SC41-3721, CD-ROM QBKAUO00). AS/400 Work Management (SC41-3306, CD-ROM QBKALG00). Client Access/400 for Windows 3.1 API and Technical Reference (SC41-3531, CD- ROM QBKAKN00). Microsoft Visual Basic Programmer's Guide.
The client/server application presented in this article demonstrates the use of the Client Access data queue APIs. Its purpose is to allow a user to display the names of all database files in an AS/400 library in a Windows dialog box.
To run this application, you'll need Client Access (PC Support) for DOS with Extended Memory in the Microsoft Windows environment.
This application contains the following components:
CLI001VB: Visual Basic program to display AS/400 database file names.
CLI001DQ: Client data queue used to pass the name of a library from the client program to the server.
SRV001RG: RPG program to retrieve database file names.
SRV001DQ: Server data queue used to pass database file names from the server program to the client.
Installation Instructions
To download this application, use a modem to call MC-BBS at (619) 931-9909. Go to the Files area and select "MC Magazine's Published Files for AS/400." Download the file named DTAQPKG.EXE. This is a self-extracting archive file containing the source code and programs for the application. Place this file in an empty subdirectory and execute it.
Once the files are extracted, run SETUP from the Windows Program Manager. This program automatically installs the client components and creates a Windows program group containing an icon to run the application. Before running it, you'll need to upload the source code for the server program SRV001RG, compile it, and submit it to batch.
If you decide to key the code for this application, you'll need Microsoft Visual Basic 3.0 and you'll need to add two files to your project: GRID.VBX and THREED.VBX, located in your WINDOWSSYSTEM directory. The THREED.VBX file is only available in the Professional Edition of Visual Basic. It is used to show the status bar at the bottom of the window. If you don't have the Professional Edition, you can still create this application without the status bar-just remove all references to the pnlStatus control.
Client Access Data Queue APIs
Figure 1 CLI001VB Dialog Window
UNABLE TO REPRODUCE GRAPHICS
Client Access Data Queue APIs
Figure 8 RPG Program SRV001RG
*=============================================================== * To compile: * * CRTRPGPGM PGM(XXX/SRV001RG) SRCFILE(XXX/QRPGSRC) + * ALWNULL(*YES) * *=============================================================== *. 1 ...+... 2 ...+... 3 ...+... 4 ...+... 5 ...+... 6 ...+... 7 FQADBXREFIF E K DISK * E CMD 1 4 38 * IDATA DS I 1 10 DBXFIL I 11 12 DBXATR I 13 62 DBXTXT * I 'DLTDTAQ QGPL/' C DLTDTQ I 'CRTDTAQ QGPL/' C CRTDTQ * * Loop until cancelled C *OFF DOUEQ*ON C EXSR RCVDTA * * Send all records for requested library C LIB SETLLQDBXREF C LIB READEQDBXREF 99 C *IN99 DOWEQ*OFF C EXSR SNDDTA C LIB READEQDBXREF 99 C ENDDO * * Send last record marker C MOVEL'/*' DATA P C EXSR SNDDTA C ENDDO * C MOVE *ON *INLR *=============================================================== C RCVDTA BEGSR * * Wait for an entry on the client data queue C CALL 'QRCVDTAQ' C PARM 'CLI001DQ'DTQNAM 10 C PARM 'QGPL' DTQLIB 10 C PARM LEN 50 C PARM LIB 10 C PARM -1 WAIT 50 * C ENDSR *=============================================================== C SNDDTA BEGSR * * Send an entry to the server data queue C CALL 'QSNDDTAQ' C PARM 'SRV001DQ'DTQNAM C PARM 'QGPL' DTQLIB C PARM 62 LEN C PARM DATA * C ENDSR *=============================================================== C *INZSR BEGSR * * Delete client data queue C CALL 'QCMDEXC' 98 C PARM CMD,1 C PARM 38 CMDLEN 155 * * Delete server data queue C CALL 'QCMDEXC' 98 C PARM CMD,2 C PARM CMDLEN * * Create client data queue C CALL 'QCMDEXC' C PARM CMD,3 C PARM CMDLEN * * Create server data queue C CALL 'QCMDEXC' C PARM CMD,4 C PARM CMDLEN * C ENDSR ** DLTDTAQ DTAQ(QGPL/CLI001DQ) DLTDTAQ DTAQ(QGPL/SRV001DQ) CRTDTAQ DTAQ(QGPL/CLI001DQ) MAXLEN(10) CRTDTAQ DTAQ(QGPL/SRV001DQ) MAXLEN(62)
Client Access Data Queue APIs
Figure 2-7 Figures 2 - 7 Program CLI001VB
'Figure 2 frmCLI001VB Properties VERSION 2.00 Begin Form frmCLI001VB BackColor = &H00C0C0C0& Caption = "Display AS/400 Database File Names" ClientHeight = 5265 ClientLeft = 1035 ClientTop = 1455 ClientWidth = 8910 Height = 5670 Left = 975 LinkTopic = "Form1" ScaleHeight = 5265 ScaleWidth = 8910 Top = 1110 Width = 9030 Begin Grid grdFiles Cols = 3 FixedCols = 0 Height = 3375 Left = 240 TabIndex = 4 Top = 1200 Width = 8415 End Begin SSPanel pnlStatus Alignment = 1 'Left Justify - MIDDLE BackColor = &H00C0C0C0& BevelInner = 1 'Inset Font3D = 0 'None Height = 375 Left = 240 TabIndex = 5 Top = 4800 Width = 8415 End Begin TextBox txtLibrary Height = 285 Left = 240 TabIndex = 0 Top = 600 Width = 2895 End Begin CommandButton cmdExit Caption = "E&xit" Height = 495 Left = 5280 TabIndex = 2 Top = 480 Width = 1455 End Begin CommandButton cmdOK Caption = "&OK" Enabled = 0 'False Height = 495 Left = 3480 TabIndex = 1 Top = 480 Width = 1455 End Begin Label Label1 BackColor = &H00C0C0C0& Caption = "Library:" Height = 255 Left = 240 TabIndex = 3 Top = 360 Width = 2895 End End 'Figure 3 Declaration Section Option Explicit Const HOURGLASS = 11 Const DEFAULT = 0 Const MB_ICONEXCLAMATION = 48 Const CLIENT_DTAQ = "QGPL/CLI001DQ" Const SERVER_DTAQ = "QGPL/SRV001DQ" Declare Function EHNDQ_SetMode Lib "EHNDQW.DLL" (ByVal hWnd As Integer, ByVal sQueueLocation As String, ByVal lMode As Long) As Integer Declare Function EHNDQ_Clear Lib "EHNDQW.DLL" (ByVal hWnd As Integer, ByVal sQueueName As String, ByVal sQueueLocation As String) As Integer Declare Function EHNDQ_Send Lib "EHNDQW.DLL" (ByVal hWnd As Integer, ByVal sQueueName As String, ByVal sQueueLocation As String, ByVal sOutputBuffer As String, ByVal lDataLength As Long) As Integer Declare Function EHNDQ_Receive Lib "EHNDQW.DLL" (ByVal hWnd As Integer, ByVal sQueueName As String, ByVal sQueueLocation As String, ByVal lWaitTime As Long, ByVal nSenderID As Integer, ByVal sInputBuffer As String, lDataLength As Long, ByVal sSenderIDInfo As String) As Integer Declare Function EHNDQ_Stop Lib "EHNDQW.DLL" (ByVal hWnd As Integer, ByVal sQueueLocation As String) As Integer 'Figure 4 Form_Load Subroutine Sub Form_Load () Dim nRtnCode As Integer 'Initialize run-time grid attributes grdFiles.HighLight = False grdFiles.ColWidth(0) = 1500 grdFiles.ColWidth(1) = 500 grdFiles.ColWidth(2) = 6000 grdFiles.Row = 0 grdFiles.Col = 0 grdFiles.Text = "File" grdFiles.Col = 1 grdFiles.Text = "Attr" grdFiles.Col = 2 grdFiles.Text = "Text" 'Set data queue mode to translate between ASCII and EBCDIC nRtnCode = EHNDQ_SetMode(hWnd, "", 9) End Sub 'Figure 5 cmdOK_Click Subroutine Sub cmdOK_Click () Dim nRtnCode As Integer, sOutputBuffer As String Screen.MousePointer = HOURGLASS pnlStatus = "" txtLibrary = UCase(txtLibrary) sOutputBuffer = txtLibrary & Space$(10 - Len(txtLibrary)) 'Clear server data queue nRtnCode = EHNDQ_Clear(hWnd, SERVER_DTAQ, "") 'Check for error condition If nRtnCode <> 0 Then Screen.MousePointer = DEFAULT MsgBox "EHNDQ_Clear API failure. Return code = " & Str$(nRtnCode), MB_ICONEXCLAMATION, "Error" Else 'Send data to client data queue nRtnCode = EHNDQ_Send(hWnd, CLIENT_DTAQ, "", sOutputBuffer, 10) 'Check for error condition If nRtnCode <> 0 Then Screen.MousePointer = DEFAULT MsgBox "EHNDQ_Send API failure. Return code = " & Str$(nRtnCode), MB_ICONEXCLAMATION, "Error" Else 'Receive data from data queue ReceiveData Screen.MousePointer = DEFAULT End If End If txtLibrary.SetFocus End Sub 'Figure 6 ReceiveData Subroutine Sub ReceiveData () Dim nRtnCode As Integer, sInputBuffer As String, nFileCount As Integer grdFiles.Rows = 2 sInputBuffer = Space$(62) 'Receive entries from server data queue Do While nRtnCode = 0 And Left$(sInputBuffer, 2) <> "/*" nRtnCode = EHNDQ_Receive(hWnd, SERVER_DTAQ, "", 5, 0, sInputBuffer, 62, "") 'Check for error condition If nRtnCode <> 0 Then Screen.MousePointer = DEFAULT MsgBox "EHNDQ_Receive API failure. Return code = " & Str$(nRtnCode), MB_ICONEXCLAMATION, "Error" Else If sInputBuffer = Space$(62) Then Screen.MousePointer = DEFAULT MsgBox "System not responding. Check server job.", MB_ICONEXCLAMATION, "Error" sInputBuffer = "/*" Else 'If no errors add entry to grid If Left$(sInputBuffer, 2) <> "/*" Then nFileCount = nFileCount + 1 grdFiles.AddItem Left$(sInputBuffer, 10) & Chr$(9) & Mid$(sInputBuffer, 11, 2) & Chr$(9) & Right$(sInputBuffer, 50), nFileCount End If End If End If 'Update status line pnlStatus = Str$(nFileCount) & " files found." Loop 'add entry if empty grid If nFileCount = 0 Then nFileCount = 1 grdFiles.AddItem "(none)", nFileCount End If 'remove excess grid entry grdFiles.RemoveItem nFileCount + 1 End Sub 'Figure 7 Additional Subroutines Sub txtLibrary_Change () 'Disable OK button if no library name If txtLibrary = "" Then cmdOK.Enabled = False Else cmdOK.Enabled = True End If End Sub Sub txtLibrary_KeyPress (KeyAscii As Integer) 'Select OK button if user pressed Enter key If KeyAscii = 13 And cmdOK.Enabled Then cmdOK.SetFocus cmdOK_Click KeyAscii = 0 End If End Sub Sub txtLibrary_GotFocus () txtLibrary.SelStart = 0 txtLibrary.SelLength = Len(txtLibrary.Text) End Sub Sub cmdExit_Click () 'Unload the form Unload Me End Sub Sub Form_QueryUnload (Cancel As Integer, UnloadMode As Integer) Dim nRtnCode As Integer 'Stop all data queue activity nRtnCode = EHNDQ_Stop(hWnd, "") End Sub
LATEST COMMENTS
MC Press Online