Build an RPG IV client/server function to remotely print a listing of library objects using TCP/IP socket functions written in C.
Prior to RPG IV, the RPG developer could not develop a TCP/IP socket program without using C or some other language that could bind to C functions. With RPG IV, you can now bind to C service program functions without having to resort to another language and licensing an additional compiler for the AS/400. This article will demonstrate the use of the C-based TCP/IP socket functions in RPG IV applications.
So, What Are TCP/IP Sockets Anyway?
The UNIX operating system was developed with a series of communication interfaces to make the operating system somewhat independent of the underlying network hardware, thus promoting interprocess communication, portability, and openness of applications. These interfaces thus promoted plug compatibility and became known as sockets. A socket is an endpoint connection for communications that can be named and addressed in a network. A socket is a unique number assigned to an interprocess connection and consists of a machines Internet Protocol (IP) address and the port number selected for the connection. Therefore, different machines can communicate on the same port since their IP addresses are different. Each machine in a connection has a socket number that uniquely identifies the machine at the other end of the connection. Through this socket number (also termed a socket descriptor), a machine sends data to and receives data from the other. Each function invoked after the socket function (the function that assigns the number) will use the socket descriptor to identify itself.
On the AS/400, the set of socket functions are available in the form of a service program, but on a PC they are available as a dynamic link library (DLL). The table in Figure 1 presents each socket function needed to complete a typical conversation between two processes. (For more information on how to bind C functions with RPG IV, see Using C Functions in ILE RPG in the November 1998 issue of MC.) As you can see in Figure 1, a server application needs to perform only a minimal set of the sockets functions: socket, set socket options (setsockopt), bind, listen, and accept. A client application needs to perform only two of the sockets functions: socket and connect.
Both the client and server can perform read and write functions for two-way communication. And both are required to close the connection to end the processes. Although the table in Figure 1 shows certain functions being invoked only by the server process, any of these functions may be invoked by the client as well, but that would indicate a role change between the two communicating processes.
Finding and Binding
To properly utilize socket functions, you need to become familiar with their arguments and return types. Most of the TCP/IP sockets function prototypes can be found in the member called SOCKET of the SYS source file in the QSYSINC library. The prototypes for the sockets read and write functions can be found in the QSYSINC library in the UNISTD member of the source file called H. The QSYSINC library is available as a free, optionally installable feature of the operating system (5769SS1: OS/400-System Openness Includes). The include headers are required to determine the number of arguments passed and their data types to be passed to the sockets functions (that are implemented in C) as well as values returned from these functions. Only when your application program has access to this information can it be successfully bound to the TCP/IP sockets functions.
Figure 2 presents a table of the function prototypes for the functions we will use for our sockets programs. With the proper C function prototypes in hand, you can map arguments passed and values returned to comparable data type definitions in RPG IV. I used the C prototypes to develop the comparable RPG D-spec prototypes (see Figure 3) for the socket functions that I will be using to implement this articles example sockets application. The complete RPG copy code for the socket function prototypes can be downloaded from Midrange Computings Web site at www.midrangecomputing.com/mc/99/02. The name of that copy file is $SocketsH. It contains not only the RPG D-spec prototypes for the socket functions but also the TCP/IP constants that will be used as arguments to socket functions.
The TCP/IP Display Library (DSPLIB) Utility
To demonstrate the implementation of sockets in RPG IV, I contrived a small example application that allows users to list the objects of a library. The Display Library (DSPLIB) command would have been an easier approach, but the purpose of this simple application is as a TCP/IP tutorial. To implement this DSPLIB application, we will write a TCP/IP server application that will respond to a request by printing out the listing of a library. Ill begin with the development of the server application, then Ill wrap up by developing a client application.
The Server
For the server application to accomplish the task, it must be able to do the following:
Create a socket with the applications Start Socket Server (STRSKTSVR) command
Set socket options
Bind an IP address and port address for the host AS/400 to the socket
Place the server in listening mode
Accept connection requests
Read data from the client socket descriptor
Translate the library name from ASCII to EBCDIC
Execute the DSPLIB OUTPUT(*PRINT) command
Return the server to read new requests
Return the server to accept new connection requests
End the server in response to the applications End Socket Server (ENDSKTSVR) command
The RPG D-spec prototypes for the socket functions that will be used to implement the DSPLIB server application are listed in Figure 3. The $SocketsH copy file is included in the RPG client program at compile time via the /COPY statement in Figure 4, Label A:
/COPY QRPGLESRC,$SocketsH
Figure 4, Label B presents the data structure that will contain the information necessary to bind to the socket created in Figure 4, Label E with the socket function execution. Label C of Figure 4 contains standalone field definitions for fields that will be used to support other functions in the application such as error handling, display library command execution, and data translation from ASCII to EBCDIC.
To begin the server application, it will need to create a socket to communicate to the client process, which it does at Figure 4, Label E with the following statement:
C EVAL SDID = Socket(AF_INET:SOCK_STREAM:UNUSED)
To create the socket , three arguments need to be specified:
1. The family protocol for which the socket will be created (Internet, SPX, SNA,
2. The type of data stream to be sent/received
3. The protocol for the data stream The AF_INET constant in the first argument informs the socket function that the address family of the socket is to be in the Internet domain. The SOCK_STREAM constant informs the socket function that the type of data stream is to be a byte-stream configuration. The third argument is set to zero with the constant called UNUSED, which tells the socket function to use the default protocol for data stream type specified in the second argument. In this case, since SOCK_STREAM was specified for the type, TCP is the default protocol.
In Figure 4, Label F, I will make the socket reusable for multiple client connections by setting the reuse indicator on, with the following:
C EVAL rc = SetSockOpt(SdId:SOL_SOCKET:
C SOL_REUSEADDR:%ADDR(On):%SIZE(On))
In Figure 4, Label G, I bind the IP address and the port that I have selected for the DSPLIB service to the socket ID created by the socket function at Label E:
C EVAL rc = Bind(SDID:%ADDR(serveraddr):
C svaddrlen)
Setting most of the values is a straightforward procedure of simply assigning the proper values to the proper data structure elements in the serveraddr socket structure. However, you must use another function, gethostid, to return the integer representation of the AS/400s IP address (for example, 190.50.38.7 in this case). The prototype for this function is included in the $SocketsH member. And the following function shows how it is called:
C EVAL sin_addr = GetHostId
After the RPG server program binds to the socket, you now have a name to address the socket by, and using that socket address, your client process can request connection events. Figure 4, Label H shows how the server accepts connection requests by placing itself in listening mode:
etc.)
C EVAL rc = Listen(SdId:10)
Once this function has been executed, the RPG server program can execute the accept function as in Figure 4, Label I, to enable its services to a client program that has made a connection request:
C EVAL rc = Accept(SDID:%ADDR(serveraddr):%ADDR(
C svaddrlen))
Once a connection request by a client has been made and accepted by the server, the server can address the client through the socket descriptor of the client that was returned by the accept function execution.
In Figure 4, Label J, a call to the read function, is made using the socket descriptor of the client, which is addressable through the SdId2 variable:
C EVAL TotCharRead = Read(SdId2:%ADDR(Buffer):
C BufferLen)
That function passes a pointer to the buffer that will hold the data read-in and the length of the buffer. Completion of the read function is signaled when it has read the number of characters enumerated by the buffer length argument. The return value from the read function will be either the number of characters read (which should equal the buffer length), -1 (indicating a failed read), or zero (indicating a close socket signal from the client partner process). After a successful read, the data conversion (QDCXLATE) API will be called at Label K to convert the data from ASCII to EBCDIC so that the server application can continue to the next step of its service. In the code of Label L in Figure 4, I pass the translated library name to the command execution (QCMDEXC) API to print out the list of library objects. Then the RPG server program returns to the read function at Label J to restart the wait for another request from that client. If the client terminates the connection, the server branches to Label M of Figure 4 to close the socket for the client:
C Eval rc = Close(SdId2)
The code then loops back to the accept function at Label I, where it waits for a connection request from another client.
The only way the server process will end is through a client request to stop the server using the applications End Socket Server (ENDSKTSVR) command. When such a request is made, all loops will be exited, and the socket close function for the server process will be executed, as shown at Label N of Figure 4, along with termination of the process.
The RPG IV Client
As stated earlier, a TCP/IP client/server application does not need to be on separate machines. TCP/IP enables communication between two processes on the same machine. Figure 5 illustrates the source code for a client process running on the AS/400 that is activated by the user submitting a request for a library to be printed with the command Send Socket Request (SNDSKTRQS) command (also available at MCs Web site, at www.midrangecomputing.com/mc/99/02).
As with the server application, the client includes the $SocketsH copy file at Label A of Figure 5. At Label B of Figure 5, again we create a unique socket identifier with the Socket function:
C EVAL SdId = Socket(AF_INET:SOCK_STREAM:UNUSED)
Since this client will be running on the same AS/400, the socket address data structure is established with identical values that I set for the server application (i.e., same port, same IP address). There I used the gethostid function to retrieve the servers IP address. If my client existed on a different AS/400 than the server application, I would need to set that address manually (because the gethostid function would retrieve the IP address for the client AS/400 not the server AS/400). In that case, you must overlay the field sin_addr, the unsigned integer field that is to contain the IP address with a 4-byte alphabetic field sin_addrA, to assign the hexadecimal equivalent of the 32-bit (or 4-byte octal values) of the IP address. For example, you cannot simply assign 190.50.38.7 to the sin_addr field because it is not a valid assignment. You need to convert each octet (190, 50, 38, 7) to its hexadecimal equivalent and assign it on that basis. The sin_addrA variable is set to the value XBE322607 in the commented code at Figure 4, Label G for the IP address of 190.50.38.7. Next, at Label C, of Figure 5, rather than doing a bind, as I did with the server application, I will use the Connect function to request a connection from the server application, which at this point will be waiting at the call to the sockets accept function:
C EVAL rc = Connect(SdId:%ADDR(serveraddr):
C svaddrlen)
Next, at Label D, I will translate the library name to its ASCII equivalent using a call to QDCXLATE, because the server application is expecting the name to be encoded as an ASCII value. Then, I will submit the request to the server with the Write, as shown at Label E:
C EVAL rc = Write(SdId:%ADDR(Buffer):BufferLen)
Finally, at Label F, after the request has been submitted, I will close the connection:
C Eval rc = Close(SdId)
In this section, we have dealt only with an AS/400 client process. There are some special considerations that you need to be aware of when interfacing the AS/400 server application with a PC client application. Those considerations are addressed in the Web companion to this article (see TCP/IP Socket Programming in RGP? Cool! at www.midrange computing.com/mc/99/02). You will also want to pay close attention to the Gotchas and Miscellaneous! sidebar to this article.
Compiling the TCP/IP Server and RPG IV Client To compile the TCP/IP Server and RPG IV client, use the following commands:
CRTBNDRPG PGM(YourLib/SKTSERVER) DFTACTGRP(*NO) +
ACTGRP(QILE) BNDDIR(QC2LE QUSAPIBD)
CRTBNDRPG PGM(YourLib/SKTCLIENT) DFTACTGRP(*NO) +
ACTGRP(QILE) BNDDIR(QC2LE QUSAPIBD)
The QC2LE binding directory contains all the service programs that makeup the standard C function library, and contains the __errno and strerror functions needed to retrieve the error information described in this article. These functions are located specifically in the QC2UTIL1 service program. The QUSAPIBD binding directory contains the service programs that comprise the socket functions demonstrated in this article. You will find most of the functions we have discussed in the QSOSRV1 service program in QSYS. The read, write, and close socket descriptor functions can be found in the QP0LLIB1 service program, also in the QUSAPIBD binding directory. Compile
instructions for the programs demonstrated in this application and commands used to start, stop, and send requests to the socket server application are detailed in the downloadable code on the Web.
Makes You Think...
Given what this article has presented, it makes you think what can now be accomplished by mixing languages in the ILE. Why re-create the wheel when another language, such as C or Java, already has the needed functionality built right in? It can no longer be taken for granted that RPG IV applications in the immediate and near future will naturally be purely RPG. This situation, in turn, will raise the expectations for RPG applications and the base requirement of skills and capabilities for the RPG IV developer. I think this is a good thing. And given a reported increased interest in certification programs for RPG/400 (up 25 percent, according to the June 29, 1998, issue of InfoWorld), apparently so do others.
Perhaps this will also give RPG developers more credibility in the eyes of IBM, a company that reportedly does not have a good opinion of typical RPG programmers and doesnt think much of their skills and capabilities. According to sources inside IBM, the company feels that only a small segment of legacy AS/400 applications programmers will make the transition to fully object-oriented Java development. Additionally, the source stated that the company holds the opinion that legacy AS/400 programmers are strong business applications programmers, but poor computer scientists; conversely, UNIX programmers are strong computer scientists, but poor applications programmers. What do you think?
References
AS/400 System API Reference V4R2 (SC41-5801-01, CD-ROM QB3AMA01)
ILE C/400 Programmers Reference (V3R7 - SC09-2070-01, CD-ROM QBJAQY01; V4R2 - SC09-2514-00, CD-ROM QB3AG100)
OS/400 Sockets Programming V4R2 (SC41-5422-01, CD-ROM QB3ANN01)
OS/400 UNIX-Type APIs V4R2 (SC41-5875-01, CD-ROM QB3AM401)
Sockets Programming V4R1 (SC41-5422-00, CD-ROM QB3ANN00)
Gotchas and Miscellaneous!
If you experience a problem when your server application attempts to bind to your selected port on the AS/400, check the port restrictions for the port in question to determine if that port is restricted to a particular user profile. If it is, select another port (and dont forget to change it in your applications) that does not have any restrictions. Avoid port numbers below 255, as these are typically reserved for various TCP/IP functions (23 for Telnet, 80 for HTTP, 20 for FTP...). You can review your AS/400 port restrictions by entering GO CFGTCP at the command line and selecting option 4 (Work with TCP/IP port restrictions).
Adding a port restriction is as simple as entering the Add TCP/IP Port Restriction (ADDTCPPORT) command, prompting with F4, and completing the parameters. You can review ports allocated by particular services by selecting option 21 (Configure related tables) at that same menu, then option 1 (Work with service table entries) at the subsequently presented menu.
You can add a service table entry for the server program developed in this article by entering the Add Service Table Entry (ADDSRVTBLE) command, prompting with F4, and completing the required parameters. While your server application is running, you can review TCP/IP connection status of the job by entering the Display the Work with TCP/IP Connection Status List (NETSTAT *CNN) command or by entering the Work with TCP/IP Network Status (WRKTCPSTS) command and selecting option 3 (Work with TCP/IP connection status). Either method presents the same information and can yield valuable information concerning your server application.
Socket Function Description Invoked By
socket() Creates a unique socket descriptor number Client & Server
setsockopt() Sets socket options Server
bind() Binds a socket to a particular IP address and port number Server
listen() Places a listener on the port specified by the bind function Server
accept() Accepts a connection request from another process Server
connect() Connects a client to the server endpoint specified in the socket address structure Client
read() Reads data using the socket descriptor returned by the accept function Client & Server
write() Writes data using the socket descriptor Client & Server
close() Closes the socket descriptor preventing further connection requests Client & Server
Figure 1: Socket functions
Function Function Prototype in C
socket() int socket_descriptor = socket(int address_family, int type, int protocol)
setsockopt() int return_code = setsockopt(int socket_descriptor, int level, int option_name, char *option_value, int option_length)
bind() int return_code = bind(int socket_descriptor, struct sockaddr *local_address, int address_length)
connect() int return_code = connect(int socket_descriptor, struct sockaddr *destination_address, int address_length)
listen() int return_code = listen(int socket_descriptor, int back_log)
accept() int return_code = accept(int socket_descriptor, struct sockaddr *address, int *address_length)
read() int #of_bytes_read = read(int socket_descriptor, void *buffer, int #of_bytes_to_read)
write() int #of_bytes_written = write(int socket_descriptor, const void* buffer, #of_bytes_to_write)
Figure 2: Socket function prototypes in C
D Socket PR 10i 0 ExtProc('socket')
D AF_INET 10i 0 Value
D SOCK_STREAM 10i 0 Value
D UNUSED 10i 0 Value
D SetSockOpt PR 10i 0 ExtProc('setsockopt')
D SocketDId 10i 0 Value
D SOL_SOCKET 10i 0 Value
D SOL_REUSEADDR 10i 0 Value
D PtrToOn * Value
D SizeOfOn 10u 0 Value
D Bind PR 10i 0 ExtProc('bind')
D SocketDId 10i 0 Value
D PtrToSAddr * Value
D AddrLen 10u 0 Value
D Listen PR 10i 0 ExtProc('listen')
D SocketDId 10i 0 Value
D NbrOfClients 10i 0 Value
D Accept PR 10i 0 ExtProc('accept')
D SocketDId 10i 0 Value
D PtrToSAddr * Value
D PtrToAddrSz * Value
D Read PR 10i 0 ExtProc('read')
D SocketDId 10i 0 Value
D PtrToBuffer * Value
D SizeToRead 10i 0 Value
D Write PR 10i 0 ExtProc('write')
D SocketDId 10i 0 Value
D PtrToBuffer * Value
D SizeToRead 10i 0 Value
D Connect PR 10i 0 ExtProc('connect')
D SocketDId 10i 0 Value
D PtrToSAddr * Value
D SizeOfAddr 10u 0 Value
D Close PR 10i 0 ExtProc('close')
D SocketDId 10i 0 Value
D GetHostId PR 10u 0 ExtProc('gethostid')
D $$Errno PR * ExtProc('__errno')
D StrError PR * ExtProc('strerror')
D Errno 10i 0 Value
** C MACROS (CONSTANTS)
*D AF_INET C Const(2)
D SOCK_STREAM C Const(1)
D UNUSED C Const(0)
D SOL_SOCKET C Const(-1)
D SOL_REUSEADDR C Const(55)
* Error numbers from QSYSINC/SYS/ERRNO
* 3401 Permission denied.
D EACCES C Const(3401)
* 3422 The type of socket is not supported in this protocol family.
D EAFNOSUPPORT C Const(3422)
* complete code available on www.midrangecomputing.com
Figure 3: Socket function prototypes in RPG IV
/COPY QRPGLESRC,$SocketsH
* SOCKET ADDRESS STRUCTURE (TCP AND UDP)
D serveraddr DS
D sin_family 5i 0
D sin_port 5u 0
D sin_addr 10u 0
D sin_addrA 4a OVERLAY(sin_addr)
D sin_zero 8a
D Miscellaneous DS
D DspError 4S 0 INZ
D DspErrorA 4A OVERLAY(DspError)
* Miscellaneous Standalone fields.
D Message S 80a Based(pMessage)
D MsgToDsply S 52a INZ
D Errno S 10i 0 Based(perrno)
D Command S 80a INZ
D CommandLen S 15p 5 INZ(80)
D svaddrlen S 10u 0 INZ
D On S 10u 0 INZ(1)
D SDID S 10i 0 INZ
D SDID2 S 10i 0 INZ
D TotCharRead S 10i 0 INZ
D rc S 10i 0 INZ(0)
D SockError S 40A INZ('**Error on Socket create')
D SetSError S 40A INZ('**Error on Socket set')
D BindError S 40A INZ('**Error on binding to socket')
D ListenError S 40A INZ('**Error on listening')
D AcceptError S 40A INZ('**Error on accepting requests')
D Buffer S 10A INZ
D DataLen S 5P 0 INZ
D XLateTable S 10A INZ
D XLateTblLib S 10A INZ
* CONSTANTS A
B
C
D Dsplib C Const('DSPLIB LIB(')
D OutPut C Const(') OUTPUT(*PRINT)')
D BufferLen C Const(10)
* Create a socket descriptor for the server process to bind.
C EVAL SDID = Socket(AF_INET:SOCK_STREAM:UNUSED)
C IF (SDID < 0 )
C EVAL perrno = $$Errno
*
C SELECT
C WHEN Errno = EACCES
C Error3401 DSPLY
{ Do some special processing here}
C WHEN Errno = EAFNOSUPPORT
C Error3422 DSPLY
{ Do some special processing here}
C OTHER
C EVAL DspError = Errno
C EVAL pMessage = StrError(Errno)
C EVAL MsgToDsply = DspErrorA + ' ' + Message
C MsgToDsply DSPLY
C ENDSL
C SockError DSPLY
*
C EVAL *INLR = *ON
C RETURN
C ENDIF
*--------- Set socket descriptor to be reuseable.
C EVAL rc = SetSockOpt(SdId:SOL_SOCKET:
C SOL_REUSEADDR:%ADDR(On):%SIZE(On))
C IF (rc < 0 )
C EVAL perrno = $$Errno
C EVAL DspError = Errno
C EVAL pMessage = StrError(Errno)
C EVAL MsgToDsply = DspErrorA + ' ' + Message
C MsgToDsply DSPLY
C SetSError DSPLY
C EVAL *INLR = *ON
C RETURN
C ENDIF
*
*--------- Bind server IP & port to socket descriptor.
*
C EVAL sin_family = AF_INET
C EVAL sin_port = 3005
* replace X'be322607' with your as/400's ip or use gethostid function if client is
* on the same as400 as the server.
C******* OR EVAL sin_addrA = X'BE322607'
C EVAL sin_addr = GetHostId
C EVAL sin_zero = X'0000000000000000'
C EVAL svaddrlen = %SIZE(serveraddr)
C EVAL rc = Bind(SDID:%ADDR(serveraddr):
C svaddrlen)
C IF (rc < 0 )
C EVAL perrno = $$Errno
C EVAL DspError = Errno
C EVAL pMessage = StrError(Errno)
C EVAL MsgToDsply = DspErrorA + ' ' + Message
C MsgToDsply DSPLY
C BindError DSPLY
C EVAL *INLR = *ON
C RETURN
C ENDIF
*--------- willingness to accept connection requests.
C EVAL rc = Listen(SdId:10)
C IF (rc < 0 )
C EVAL perrno = $$Errno
C EVAL DspError = Errno
C EVAL pMessage = StrError(Errno)
C EVAL MsgToDsply = DspErrorA + ' ' + Message
C MsgToDsply DSPLY
C ListenError DSPLY
C EVAL *INLR = *ON
C RETURN
C ENDIF
*--------- Accept new connection requests. If rc is greater than zero,
*--------- the returned value is the socket descriptor identifier for
*--------- the client partner that requested the current connection.
C DOW ( Buffer <> '*STOPSVR' )
C EVAL rc = Accept(SDID:%ADDR(serveraddr):%ADDR(
C svaddrlen))
C IF ( rc < 0 )
C EVAL perrno = $$Errno
C EVAL DspError = Errno
C EVAL pMessage = StrError(Errno)
C EVAL MsgToDsply = DspErrorA + ' ' + Message
C MsgToDsply DSPLY
C AcceptError DSPLY
C EVAL *INLR = *ON
C RETURN
C ELSE
C EVAL SdId2 = rc
C ENDIF
C DOW ( rc > 0 )
*--------- Read from socket descriptor of accepted connection request
*--------- above.
C EVAL TotCharRead = *ZERO
C EVAL TotCharRead = Read(SdId2:%ADDR(Buffer):
C BufferLen)
*--------- Convert buffer data to EBCDIC to simulate source as a
*--------- PC client (ASCII Data).
C IF ( TotCharRead > *ZERO )
C CALL 'QDCXLATE' 68
C PARM BufferLen DataLen
C PARM Buffer
C PARM 'QEBCDIC' XLateTable
C PARM 'QSYS' XLateTblLib
C IF ( *IN68 = *OFF AND Buffer <> '*STOPSVR')
C EVAL Command = Dsplib + Buffer + OutPut
C CALL 'QCMDEXC' 68
C PARM Command
C PARM CommandLen
C ELSE
C Eval rc = Close(SdId2)
C LEAVE
C ENDIF
C ELSE
C Eval rc = Close(SdId2)
C LEAVE
C ENDIF
C ENDDO
C ENDDO
C Eval rc = Close(SdId)
C EVAL *INLR = *ON
C RETURN
D
E
F
G
I
J
K
L
M
N
H
Figure 4: Socket server source
A
B
C D
E
F
/COPY SYSINC,$SocketsH
C *Entry PList
C Parm Request
C EVAL Buffer = Request
*--------- Create a socket descriptor for the client process to
*--------- make a connection with.
C EVAL SdId = Socket(AF_INET:SOCK_STREAM:UNUSED)
C IF (SdId < 0 )
C EVAL perrno = $$Errno
C EVAL DspError = Errno
C EVAL pMessage = StrError(Errno)
C EVAL MsgToDsply = DspErrorA + ' ' + Message
C MsgToDsply DSPLY
C SockError DSPLY
C EVAL *INLR = *ON
C RETURN
C ENDIF
*--------- Connect to server process with client socket descriptor.
C EVAL sin_family = AF_INET
C EVAL sin_port = 3005
C EVAL sin_addrA = X'BE322607'
C EVAL sin_zero = X'0000000000000000'
C EVAL svaddrlen = %SIZE(serveraddr)
C EVAL rc = Connect(SdId:%ADDR(serveraddr):
C svaddrlen)
C IF (rc < 0 )
C EVAL perrno = $$Errno
C EVAL DspError = Errno
C EVAL pMessage = StrError(Errno)
C EVAL MsgToDsply = DspErrorA + ' ' + Message
C MsgToDsply DSPLY
C ConnectError DSPLY
C EVAL *INLR = *ON
C RETURN
C ENDIF
*--------- Convert buffer data to ASCII to simulate source as a
*--------- PC client. The server will convert it to EBCDIC before
*--------- executing DSPLIB OUTPUT(*PRINT) command.
C CALL 'QDCXLATE' 68
C PARM BufferLen DataLen
C PARM Buffer
C PARM 'QASCII' XLateTable
C PARM 'QSYS' XLateTblLib
*--------- Write library name to buffer.
C IF *IN68 = *OFF
C EVAL rc = Write(SdId:%ADDR(Buffer):BufferLen)
C ENDIF
*--------- Close the socket descriptor.
C Eval rc = Close(SdId)
C RETURN
Figure 5: Socket client source
LATEST COMMENTS
MC Press Online