We've learned a lot of new API calls over the past few sections. It's time to put these new APIs to use with an example program.
This program is a very simple http client. It connects to a web server on the internet, and requests that a web page (or another file) on the server be sent back to it. It then receives the data that the web server returns and displays it on the screen.
It's important to understand that most data that is sent or received over the internet uses the concept of 'lines of text.' A line of text is a variable-length string of bytes, you can tell the end of a line by looking for the 'carriage-return' and 'line-feed' characters. When these characters appear in the text, it means that its time to start a new line.
In ASCII, the 'carriage-return' character (CR) is x'0D', and the 'line-feed' character is x'0A'. When translated to EBCDIC, these are x'0D' and x'25', respectively.
Therefore, the pseudocode for this client program looks like this:
look up the port number for the HTTP service and store it into the variable 'port'.
look up the IP address for the hostname that was passed as a parameter, and store it in the variable 'IP'.
call the socket() API to create a socket that we can use for communicating with the HTTP server.
create a socket address structure ('sockaddr') that will tell the connect() API which host & service to connect to.
call the connect() API to connect to the HTTP server.
place a two-line 'request' into a variable. The first line will contain the phrase "GET /pathname/filename HTTP/1.0" which tells the HTTP server that we wish to get a file from it, and also tells the HTTP server where that file is. The "HTTP/1.0" means that we're using version 1.0 of the HTTP specifications (more about that later) The second line of the request is blank, that's how we tell the server that we're done sending requests.
Translate our request to ASCII so the server will understand it.
Call the send() API to send our request to the server.
Call the recv() API to read back 1 byte of the server's reply.
If an error occurred (that is, the server disconnected us) then we're done receiving, jump ahead to step 13.
If the byte that we've read is not the 'end-of-line' character, and our receive buffer isn't full, then add the byte to the end of the receive buffer, and go to step 9.
Translate the receive buffer to EBCDIC so we can read it, and display the receive buffer. Then go back to step 9 to get the next line of data.
Close the connection.
Pause the screen so the user can see what we received before the program ends.
Without further ado, here's the sample program, utilizing all of the concepts from the past few sections of this tutorial:
File: SOCKTUT/QRPGLESRC, Member: CLIENTEX1
H DFTACTGRP(*NO) ACTGRP(*NEW) D getservbyname PR * ExtProc('getservbyname') D service_name * value options(*string) D protocol_name * value options(*string) D p_servent S * D servent DS based(p_servent) D s_name * D s_aliases * D s_port 10I 0 D s_proto * D inet_addr PR 10U 0 ExtProc('inet_addr') D address_str * value options(*string) D INADDR_NONE C CONST(4294967295) D inet_ntoa PR * ExtProc('inet_ntoa') D internet_addr 10U 0 value D p_hostent S * D hostent DS Based(p_hostent) D h_name * D h_aliases * D h_addrtype 10I 0 D h_length 10I 0 D h_addr_list * D p_h_addr S * Based(h_addr_list) D h_addr S 10U 0 Based(p_h_addr) D gethostbyname PR * extproc('gethostbyname') D host_name * value options(*string) D socket PR 10I 0 ExtProc('socket') D addr_family 10I 0 value D type 10I 0 value D protocol 10I 0 value D AF_INET C CONST(2) D SOCK_STREAM C CONST(1) D IPPROTO_IP C CONST(0) D connect PR 10I 0 ExtProc('connect') D sock_desc 10I 0 value D dest_addr * value D addr_len 10I 0 value D p_sockaddr S * D sockaddr DS based(p_sockaddr) D sa_family 5I 0 D sa_data 14A D sockaddr_in DS based(p_sockaddr) D sin_family 5I 0 D sin_port 5U 0 D sin_addr 10U 0 D sin_zero 8A D send PR 10I 0 ExtProc('send') D sock_desc 10I 0 value D buffer * value D buffer_len 10I 0 value D flags 10I 0 value D recv PR 10I 0 ExtProc('recv') D sock_desc 10I 0 value D buffer * value D buffer_len 10I 0 value D flags 10I 0 value D close PR 10I 0 ExtProc('close') D sock_desc 10I 0 value D translate PR ExtPgm('QDCXLATE') D length 5P 0 const D data 32766A options(*varsize) D table 10A const D msg S 50A D sock S 10I 0 D port S 5U 0 D addrlen S 10I 0 D ch S 1A D host s 32A D file s 32A D IP s 10U 0 D p_Connto S * D RC S 10I 0 D Request S 60A D ReqLen S 10I 0 D RecBuf S 50A D RecLen S 10I 0 C************************************************* C* The user will supply a hostname and file C* name as parameters to our program... C************************************************* c *entry plist c parm host c parm file c eval *inlr = *on C************************************************* C* what port is the http service located on? C************************************************* c eval p_servent = getservbyname('http':'tcp') c if p_servent = *NULL c eval msg = 'Can''t find the http service!' c dsply msg c return c endif c eval port = s_port C************************************************* C* Get the 32-bit network IP address for the host C* that was supplied by the user: C************************************************* c eval IP = inet_addr(%trim(host)) c if IP = INADDR_NONE c eval p_hostent = gethostbyname(%trim(host)) c if p_hostent = *NULL c eval msg = 'Unable to find that host!' c dsply msg c return c endif c eval IP = h_addr c endif C************************************************* C* Create a socket C************************************************* c eval sock = socket(AF_INET: SOCK_STREAM: c IPPROTO_IP) c if sock < 0 c eval msg = 'Error calling socket()!' c dsply msg c return c endif C************************************************* C* Create a socket address structure that C* describes the host & port we wanted to C* connect to C************************************************* c eval addrlen = %size(sockaddr) c alloc addrlen p_connto c eval p_sockaddr = p_connto c eval sin_family = AF_INET c eval sin_addr = IP c eval sin_port = port c eval sin_zero = *ALLx'00' C************************************************* C* Connect to the requested host C************************************************* C if connect(sock: p_connto: addrlen) < 0 c eval msg = 'unable to connect to server!' c dsply msg c callp close(sock) c return c endif C************************************************* C* Format a request for the file that we'd like C* the http server to send us: C************************************************* c eval request = 'GET ' + %trim(file) + c ' HTTP/1.0' + x'0D25' + x'0D25' c eval reqlen = %len(%trim(request)) c callp Translate(reqlen: request: 'QTCPASC') C************************************************* c* Send the request to the http server C************************************************* c eval rc = send(sock: %addr(request): reqlen:0) c if rc < reqlen c eval Msg = 'Unable to send entire request!' c dsply msg c callp close(sock) c return c endif C************************************************* C* Get back the server's response C************************************************* c dou rc < 1 C exsr DsplyLine c enddo C************************************************* C* We're done, so close the socket. C* do a dsply with input to pause the display C* and then end the program C************************************************* c callp close(sock) c dsply pause 1 c return C*=============================================================== C* This subroutine receives one line of text from a server and C* displays it on the screen using the DSPLY op-code C*=============================================================== CSR DsplyLine begsr C*------------------------ C************************************************* C* Receive one line of text from the HTTP server. C* note that "lines of text" vary in length, C* but always end with the ASCII values for CR C* and LF. CR = x'0D' and LF = x'0A' C* C* The easiest way for us to work with this data C* is to receive it one byte at a time until we C* get the LF character. Each time we receive C* a byte, we add it to our receive buffer. C************************************************* c eval reclen = 0 c eval recbuf = *blanks c dou reclen = 50 or ch = x'0A' c eval rc = recv(sock: %addr(ch): 1: 0) c if rc < 1 c leave c endif c if ch<>x'0D' and ch<>x'0A' c eval reclen = reclen + 1 c eval %subst(recbuf:reclen:1) = ch c endif c enddo C************************************************* C* translate the line of text into EBCDIC C* (to make it readable) and display it C************************************************* c if reclen > 0 c callp Translate(reclen: recbuf: 'QTCPEBC') c endif c recbuf dsply C*------------------------ Csr endsr
Compile this program with: CRTBNDRPG PGM(CLIENTEX1) SRCFILE(xxx/QRPGLESRC) DBGVIEW(*LIST)
Run the program by typing: CALL CLIENTEX1 PARM('ods.ods.net' '/index.html')
(You should be able to use this to retrieve just about any web page)
There are a lot of things that we could improve about this client. We'll discuss these, and start implementing the improvements, in the upcoming sections.