Introduction to TCP/IP Programming

Article ID: 51701

Years ago, if you wanted to write an application that communicated with another program on another computer, you used tools such as Advanced Program-to-Program Communications (APPC), Intersystem Communications Function (ICF), or data queues. Today, it's TCP/IP. Not only is it the technology that the Internet runs on, but it has become the standard for corporate networks, too.

This article introduces you to the concepts of writing TCP/IP applications in RPG.

Network Basics
Think for a moment about the job of a postal worker. Each mail carrier has a big bag full of letters that need to be delivered to different people. The mail carrier walks from house to house, pulling the mail for each house from his or her bag and placing it in the appropriate mailbox.

Networks work the same way! All the data on a network is broken into small pieces of data called packets. Think of packets as being like the letters that the mail carrier delivers. Each network card has a Media Access Control (MAC) address built into it. Each packet contains the MAC address of the sender as well as of the destination.

Introducing TCP/IP
The network that I just described lets two computers on the same physical network send data to one another. The TCP/IP protocol suite is designed to interconnect many of these networks.

TCP/IP is a suite of protocols named after the two that are the most commonly used, TCP and IP. The other frequently used protocols in the suite are User Datagram Protocol (UDP) and Internet Control Message Protocol (ICMP). A brief description of these protocols follows.

IP = Internet Protocol
MAC addresses are useful only within their LAN. So how can data be transmitted across many networks? That's the Internet Protocol's job.

Inside the data portion of each network packet is an IP "datagram." You can think of a datagram as a "packet within a packet." It contains another set of addresses, called IP addresses, that work across the whole set of interconnected networks. The datagram is the data that gets copied from network to network.

The IP standard also specifies how data is to be routed across networks, so that it finally reaches its destination.

Although IP is an interesting technology, it doesn't do much by itself. It just routes datagrams, and that's about it. It provides an underlying mechanism that the other protocols (TCP, UDP, and ICMP) use to exchange data. As a programmer, you never work with IP directly. Instead, you work with either TCP or UDP.

UDP = User Datagram Protocol
When a program wants to work with datagrams, it uses UDP. UDP runs on top of IP. When you want to send a datagram, UDP adds a port number to your data and then uses IP to deliver it to the remote host.

The port number is a number that identifies the application that should handle the data when it arrives.

UDP is a very thin layer over IP. It doesn't add much to it and therefore lets your program run "very close to the metal."

Unfortunately, IP and UDP are unreliable protocols. If anything goes wrong, your datagram might never arrive at the other end of the connection. To be sure that nothing is going wrong, your program has to come up with a method of checking to see whether the datagrams are arriving. Also, the potential exists for UDP datagrams to arrive in a different sequence than they were sent.

TCP = Transmission Control Protocol
It can be cumbersome for a program to break its data into datagrams, make sure they've arrived, and make sure they're in order. TCP takes care of all that for you.

TCP converts the datagram-oriented IP into a stream-oriented protocol. You can write any amount of data into one end of the TCP conversation, and it appears on the other end in the same sequence. If, somehow, a network error occurs and prevents the data from arriving, the protocol detects this and reports an error to your application.

TCP is the main focus of this article, so I say more about it later.

ICMP = Internet Control Message Protocol
ICMP is the "error message" protocol of the TCP/IP suite. When something goes wrong in a UDP or TCP transmission, a computer or router can use the ICMP protocol to send back a message that describes the problem.

In addition to reporting errors, ICMP can test the underlying IP protocol by transmitting an "echo" message. When the destination system receives an ICMP echo, it sends back an echo response. A diagnostic program called PING uses these echoes and responses to check whether a network is functioning correctly.

TCP Client Programs
Most TCP/IP programs use TCP to exchange data over an IP network because it's reliable and relatively easy to use. TCP follows a client/server paradigm. There's always a client program that needs some work to be done. It connects to a server and sends some instructions to the server. The server does the work and sends back a response.

An example of this is sending e-mail. A user types a message into an e-mail client. That client then connects to a Simple Mail Transfer Protocol (SMTP) server, tells that server that it wants to send mail — including who the mail is intended for and who it's from — and then sends the e-mail message itself. The server then does the work of transmitting this e-mail message and then tells the client whether it succeeded or failed.

Introducing Sockets
Each computer system that works with the TCP/IP protocol suite contains a set of APIs called socket APIs. These APIs let programs connect and exchange data over the network.

You can think of a socket as being like a telephone. Think of how a telephone conversation works:

  • You look up the telephone number and extension of the person you want to speak to.
  • You pick up the telephone receiver.
  • You dial the number and the extension and wait for someone to say "hello."
  • You then hold a conversation with someone. Anything you say, that person hears. Anything that person says, you hear.
  • When you're done, you hang up the phone.

A TCP client program works very much the same way:

  • You look up the IP address (telephone number) and port (extension) of the server that you want to talk to. You do this with the gethostbyname() and getservbyname() APIs.
  • You open up a new socket (telephone receiver) by calling the socket() API.
  • You connect to the IP address and port and wait for the server to establish the connection (dial the number and wait for "hello") with the connect() API.
  • You send and receive information with the send() and recv() APIs.
  • When you're done, you disconnect (hang up the phone) with the close() API.

Looking Up the IP Address
Usually when someone wants to connect to a server, they supply you with the name of that host as a domain name. However, an IP address is required to make the actual connection. Therefore, you need to look up the IP address that a domain name refers to. To do that, you call the gethostbyname() API. Consider the following code:

     /copy socket_h
         .
         .
          p_hostent = gethostbyname('iseriesnetwork.com');
          if (p_hostent = *NULL);
             msg = 'Host lookup failed.';
             // show error msg to user!
             return;
          else;
             addr = h_addr;
          endif;

I've prototyped all the socket APIs, as well as the data structures and constants that go with them, and put them in a copy book called SOCKET_H.

The preceding code snippet looks up the IP address for iSeriesNetwork.com. How it does that depends on how your computer is configured. To find out, prompt the CHGTCPDMN (Change TCP/IP Domain) CL command.

                       Change TCP/IP Domain (CHGTCPDMN)                        
Type choices, press Enter.                                                     
Host name  . . . . . . . . . . .   'test'                                      
Domain name  . . . . . . . . . .   'examples.com'                              
Domain search list . . . . . . .   *DFT                                        
Host name search priority  . . .   *LOCAL        *REMOTE, *LOCAL, *SAME        
Domain name server:                                                            
  Internet address . . . . . . .   '192.168.0.1'                               
                                                                        Bottom 
F3=Exit   F4=Prompt   F5=Refresh   F10=Additional parameters   F12=Cancel      
F13=How to use this display        F24=More keys                               

In this example, the Host name search priority is set to *LOCAL. That means that gethostbyname() first tries to look up the domain name in your system's host table. If it finds it, it returns the IP address configured there. If it doesn't, it makes a request to the DNS server at 192.168.0.1 to try to find iSeriesNetwork.com.

If the Host name search priority had been set to *REMOTE, it would try DNS first and fall back on the host table only if the DNS lookup failed.

Gethostbyname() returns *NULL if it can't find the host, or it returns a pointer if it's successful. The pointer points to a host information in a data structure named hostent. This data structure and the pointer that it's based on are defined in the SOCKET_H copy book. The h_addr field is part of that data structure and contains the IP address, if all goes well.

Looking Up the Port
Every TCP server must be given its own port number. This number is how a client connects to it and also how it's differentiated from any other server programs that might be running on the same computer.

The standard servers that run on TCP/IP networks use standard port numbers. For example, FTP is 21, Secure Shell (SSH) is 22, Telnet is 23, SMTP is 25, and HTTP is 80. Rather than hard-code these numbers into your programs, it's recommended that you look them up in your computer's services table.

The services table is just a file that you can use to look up a given service name and get the corresponding port number. It does not mean that the service is running on your computer! All it does is provide a cross-reference between the human-readable name and the port number.

You can browse your system's services table with the WRKSRVTBLE (Work with Service Table Entry) command. Here's a screen shot that shows this command's output on my system:

                        Work with Service Table Entries                       
 Type options, press Enter.                                                   
   1=Add   4=Remove   5=Display                                               
 Opt  Service                                  Port  Protocol                 
      rmtjournal                               3777  tcp                      
      rmtjournal                               3777  udp                      
      routed                                    520  udp                      
      sealms                                   1025  tcp                      
      sftp                                      115  tcp                      
      sftp                                      115  udp                      
      smtp                                       25  tcp                      
      smtp                                       25  udp                      
      snmp                                      161  tcp                      
      snmp                                      161  udp                      
      snmp-trap                                 162  tcp                      
                                                                        More..
 Parameters for options 1 and 4 or command                                    
 ===>                                                                         
 F3=Exit   F4=Prompt   F5=Refresh   F6=Print list   F9=Retrieve   F12=Cancel  
 F17=Top   F18=Bottom    

Your program can use the getservbyname() API to look up values in the services table, as the following code illustrates:

          p_servent = getservbyname('smtp': 'tcp');
          if (p_servent <> *NULL);
             port = s_port;
          else;
             port = 25;
          endif;

The parameters to getservbyname() are the service that you want to look up and the protocol that you're using for that service. In this case, I'm looking up the SMTP service for use with the TCP protocol.

If it's successful, the getservbyname() API returns a pointer to a data structure called servent. It returns *NULL if the service can't be found in the services table. For the sake of this example, I fell back on a hard-coded port number if the SMTP service couldn't be found. This way, the user can change his or her services table so that SMTP is on a different port, and my program uses whichever port the user configures. However, if the user accidentally deletes the SMTP service from the services table, my program still works because it falls back to port 25.

Creating a Socket
Earlier in this article, I suggested that a socket is like a telephone. You use it to establish the connection with the program that you want to talk to, and you use it to send and receive data. To do any of that, you have to create a socket.

Under the covers, the operating system has lots of different sockets that have been created and that are in use at any one time. Remember, every TCP/IP connection uses at least one socket! That includes the ones made to your Web server, e-mail server, Telnet server, and so on. How does it keep track of all these sockets? It assigns each one a number called a socket descriptor.

Consider the following code that creates a new socket:

          s = socket(AF_INET: SOCK_STREAM: IPPROTO_TCP);
          if (s = -1);
             msg = 'socket failed!';
             // show error message to user!
             return;
          endif;

The socket() API creates a new socket. Before it can do that, however, it needs to know what type of socket to create, because you can use sockets with many different network protocols, and TCP/IP is but one of them. The three parameters that I pass to the socket() API tell it (1) that I want to use the Internet protocol family (AF_INET), (2) that I want this socket to be a stream socket (SOCK_STREAM) — because that's the type of socket that TCP uses — and (3) that I want to use the TCP network protocol (IPPROTO_TCP). The socket() API returns -1 if something goes wrong, or it returns the socket descriptor if the socket has been created successfully.

Connecting to the Server
It's time to use the connect() API to make the connection to the server. Consider the following code:

     D connto          ds                  likeds(sockaddr_in)
         .
         .
          connto = *ALLx'00';
          connto.sin_family = AF_INET;
          connto.sin_addr   = addr;
          connto.sin_port   = port;

          if ( connect(s: %addr(connto): %size(connto)) = -1 );
             callp close(s);
             msg = 'Connect failed!';
             // report error to user
             return;
          endif;

Before I can call connect(), I need to create a socket address data structure. There are different socket address data structures for different network protocols, and in this case, I want to use the one called sockaddr_in, which is for the Internet Protocol. This data structure is defined in the SOCKET_H copy member, but I've used LIKEDS to make a copy of it called ConnTo.

Because there are fields that aren't used for Internet communications, I start by resetting the entire data structure to hex zeroes (x'00'). That way, I know that the API won't be confused by blanks or other illegal values in the data structure.

Next, I fill in the fields for the Internet address family (AF_INET), followed by the IP address and port number that I looked up earlier with the gethostbyname() and getservbyname() APIs, respectively.

Now, I can connect to the server. The connect() API needs me to supply the socket descriptor so that it knows which socket I want to use for this connection, as well as the ConnTo data structure that provides the address to connect to. The connect() API returns a -1 if the connection can't be established, or it returns 0 if all goes well.

Receiving Data from the Server

Data is received with the recv() API. When I call recv(), I pass it the socket descriptor as well as the address of a variable that I want to receive data into, and the size of that variable. It accepts a fourth parameter called flags, which is rarely used, and because I don't need it in this example, I pass a 0 to indicate that to the API.

            len = recv(s: %addr(data): %size(data): 0);
            if (len < 1);
               return -1;
            endif;

The recv() API returns the length of the data received. Or, if an error occurs, it returns -1. Note that recv() does not wait until the entire data variable is filled before it returns control to my program. It returns data as soon as there's any data, even if it's only 1 byte.

Depending on the speed of your connection, you might get lots of data at once, or you might get very little. Your program has to do the work of buffering that data until it receives everything that it expects.

In the case of SMTP, the data that it receives is in ASCII, so you have to translate it to EBCDIC to make it readable in your program.

Sending Data to the Server
Sending data is very similar to receiving it. You pass the send() API the socket descriptor that you want to use, the address of the variable that you want to send, and the length of data that you want to send. Once again, there's a fourth parameter called flags, which is almost never used, and you need to pass a zero for that parameter.

         len = %len(%trimr(data));
         QDCXLATE( len: data: 'QTCPASC' );
         sent = send(s: %addr(data): len: 0 );

In this example, I figure out the length of the text in my data variable and then use the Convert Data (QDCXLATE) API to translate it to ASCII. Finally, I use the send() API to send it to the server.

Send() returns the length of the data sent, or -1 if an error occurs.

Closing the Connection and Deleting the Socket
The close() API disconnects you from the server, and then it deletes the socket. Because this API's name happens to match that of an RPG opcode, you need to precede it with CALLP, even in free-format RPG.

          callp close(s);

To demonstrate a simple TCP client program, I've written a program that connects to an SMTP server and sends a simple e-mail message. As it communicates with the SMTP server, it writes any data sent or received to the job log so that you can see what's happening.

You can download this sample program from the following link:
http://www.pentontech.com/IBMContent/Documents/article/51701_42_TcpProg1.zip

ProVIP Sponsors

ProVIP Sponsors