TCP/IP Socket Programming in REXX
Written by Patrick Mueller
Remember when your mother used to tell you "Don't stick things into the wall outlets - sockets are dangerous!" Well, you've grown up, and guess what: sockets are fun to play with! At least TCP/IP sockets are. TCP/IP sockets are the programming interface used by all your favorite TCP/IP programs, including FTP, TELNET, news readers, IRC, and so forth.
Most implementations of TCP/IP provide a programming toolkit that includes a C language library for socket functions. I've created a REXX interface to the TCP/IP socket functions as provided by the IBM TCP/IP product, allowing programs that use sockets to be written in the REXX language as well as the C language.
In this article, I will discuss how to write programs in REXX that access TCP/IP sockets. First, I give an overview of sockets, followed by a description of the socket programming interface available for REXX. Then, I explain the sample programs shipped with the article (TMSG.CMD and TMSGD.CMD). Finally, I list a set of references that provide more information on programming with sockets, and what you can use sockets for.
After reading the article, you should have enough information to start implementing clients and servers for your favorite TCP/IP protocols, or implementing new protocols and applications in REXX that run over TCP/IP.
What are sockets?
The TCP/IP programming interface provides a way to allow multiple computers to work together, assuming that they are connected somehow. Computers that are connected form a network, and there are a number of different ways computers can be connected to one another: Token Ring LANs, Ethernet, telephone connections, and so on. Although it is possible to write programs with a specific type of connection in mind, it is often not practical to do so. Instead, there are abstraction layers for network programming that provide common programming interfaces and handle the underlying physical network connections for you.
TCP/IP is one such abstraction layer. There are TCP/IP implementations available for nearly all popular networks. It is also widely available on various harware platforms - from lowly 8088 PCs, to supercomputers - and everything in between. So, programming to the TCP/IP layer not only gives you network independence, but also some level of platform independence.
The lowest level of programming to the TCP/IP layer is done with sockets. Sockets are in many ways similar to file handles in traditional programming languages. Within a program, you open a socket, read from the socket, write to the socket, and close the socket. The main difference is that instead of a file handle being associated with a file on a disk drive, a socket is associated with another program that is also reading and writing data on the socket. In this manner, sockets are similar in behavior to pipes on OS/2 and Unix.
Programs that use sockets often use a client/server model. In this model, a server program and a client program both open sockets and connect them together. The client sends some data to the server; the server reads the data, does some processing, and sends data back to the client. The client then reads that data back from the server. This data exchange can continue back and forth for some time. Eventually, both the client and server program close their sockets and end the communication.
As an example, consider news readers and news servers. When you read Usenet news, you start up your news reader (client). The client connects to an already-running news server (server). The client requests information from the server, for instance, "list all the subject lines for articles in the comp.os.os2.programmer.misc news group". The client requests the information by sending this command (in a more standardized form than the English example given above) to the server. The server consults its database of Usenet news and sends back the list of subject lines. This sort of command/response flow is typical of the client/server model.
Socket functions for sending and receiving data
Below is a description of the basic socket functions that TCP/IP socket programs can use. There are functions to open and close a socket and functions to read from and write to a socket. The functions described are the REXX functions available in the rxSock function package.
The properties described above imply that the client and server programs have to know something about the data being sent over the network. For instance, if you plan on sending binary data as 2-or 4-byte integers between a client and server, you'll have to decide before writing the programs what the order of the bytes in the integer will be. On most PC workstations, integers are stored internally with their bytes reversed. For instance, the number 0x12AB is stored internally as AB 12. Many of the higher-powered, non-PC workstations store integers without the bytes reversed. Choose either the reversed or non-reversed format, and then make sure the hardware your program is running on uses that format. If it doesn't, reverse the bytes before sending them (or after receiving them, as the case may be).
Also, because text sent with SockSend() can be broken into multiple packets, you will have to determine how the program doing the SockRecv() will decide when it has received enough data. It can't read until it gets everything, since it will eventually block. Instead, you will have to rely on one of the following techniques:
Socket functions for connecting clients and servers
The functions SockRecv() and SockSend() described above only can be used once a socket is connected to another machine. But how does the connection between two programs take place? In order for a client program to talk to a server program, the client has to know on what machine the server is running. Every machine on the TCP/IP network is uniquely identified by a 32-bit number, called its Internet address, or IP address. Because 32-bit numbers can get rather unwieldy to use for humans, they are often displayed as four 8-bit numbers, converted to decimal numbers with dots between them, called dot decimal addresses. For example, the address 18.104.22.168 corresponds to the 32-bit number 9 * 256^3 + 67 * 256^2 + 225 * 256 + 165 = 155,443,621. Even dot decimal addresses are unwieldy, so there's usually a human readable name also associated with each IP address, called its hostname.
The socket functions generally deal with IP addresses. There is a function - SockGetHostByName() - that can be used to convert a hostname to an IP address, so many programs accept either an IP address or hostname as a machine address. When a hostname is passed in, it is converted to an IP address, and that address is used in the remainder of the program.
Because there is often more than one server program running on a machine,
there must be a way to distinguish between them. This is done with a port.
If you are writing your own client and server program with sockets, you'll need to have the client and server agree on a port. This can be done by hard-coding it in your program or by allowing it to be passed in as a parameter to the program.
The combination of an address and port are enough to distinguish any particular server running on any machine in the network. These two numbers are set in the address stem variable, which is used in functions to connect clients and servers. These functions are described below. See the rxsock.doc file shipped with the rxSock function package for more information on the address stem variable.
Note that the loop for SockSend() and SockRecv() is dependant on the application - some clients will just send, receive, or send and receive data once. The loop enclosing SockAccept() on the server is to handle each client ; each time through the loop corresponds with one client session. The SockClose() call in the loop on the server is to close the socket obtained by SockAccept(), not the socket obtained by SockSocket().
client server 컴컴컴컴컴컴컴컴 컴컴컴컴컴컴 SockSocket() SockBind() SockSocket() loop ... SockConnect() SockAccept() loop ... loop ... SockSend() SockRecv() SockRecv() SockSend() SockClose() SockClose() SockClose()Up until now I've only discussed the socket functions themselves. The socket functions are not included in OS/2 REXX itself, but implemented in a function package. The function package is implemented in an OS/2 Dynamic Link Library (DLL), available from the IBM Employee Written Software (EWS) program. EWS files are available from a number of places, including CompuServe, and the ftp.cdrom.com and software.watson.ibm.com anonymous FTP sites. Look for a subdirectory called EWS. On ftp.cdrom.com, rxsock is located in the /pub/os2/ibm/ews subdirectory. The rxSock function package is in a file called rxsock.zip. Unpack the .ZIP file, and read the rxsock.doc file for installation instructions and reference material on the functions in rxSock.
The REXX functions implemented in rxSock closely match the socket functions available to C programs. If you already know C, once you learn the REXX socket functions, you won't have any problem learning the C socket functions. In this respect, rxSock provides a way to prototype socket programs in REXX, and then implement them in C for speed.
The rxSock function package is supported with the OS/2 TCP/IP product, versions 1.2.1 and 2.0. At this time, I know of no other non-IBM TCP/IP implementations that support rxSock.
Example programs - TMSG and TMSGD
Now it's time to put all the pieces together and write an application. This example is an application to send a textual message from one OS/2 machine to another.
Two programs are used to implement the application: TMSG is the client, and TMSGD is the server. The client program is used to send a message to the server. The server receives the message and displays it on the console.
To start the server, just run the TMSGD program. It will display a startup message and then wait for clients to send messages. The program will not terminate unless you press Ctrl-C or Ctrl-Break.
To use the client, run the TMSG program, passing it the hostname of the machine to display the message on, followed by the text of the message.
To test the programs on your own system,
1. start the server
Both programs assign a port number (the same number) at the top of the program, and make sure the rxSock package is loaded.
The TMSG program does the following:
A few notes on some of the other processing the programs do:
Two good books on socket programming are:
Many of the TCP/IP protocols used by programs are documented in RFC files (Request For Comments). These include NNTP (news), TELNET, FTP, and hundreds more. These files may be obtained via the Internet from venera.isi.edu in the in-notes directory. The rfc-index.txt file contains an index of all the RFCs currently available.
There are a few programs publically available that make use of the rxSock function package. These programs are available via anonymous ftp to ftp.cdrom.com, in the /pub/os2/2_x/network subdirectory. The programs are: