Chapter 18
TCP/IP Socket Functions
18.1 Introduction
TPL includes several functions to allow you to develop client and
server applications that communicate through TCP/IP network socket
connections. These applications can talk to other TPL
applications on the same computer or on other computers accessible
through the Internet. In addition, you can use these functions to
connect to other types of applications such as Telnet, World Wide
Web servers, FTP, etc.
The communication channel used for network TCP/IP connections is
called a socket. A socket is very similar to a file channel or
handle that is used to read and write a disk file.
When developing programs to communicate through the network, you
should design one program as the server and the other as the
client. The server program begins execution and executes an
accept() function which waits until a connection request arrives
from a client. Client applications use the connect() function to
issue a connection request to a server. Once the connection has
been established, a bidirectional socket is allocated which can be
read from and written to in order to exchange data between the
programs.
18.2 Domain names, IP addresses, and ports
When a server program begins running, it uses the accept()
function to specify that it wishes to accept connection requests
for a specific IP (Internet Protocol) address and port. The IP
address can be specified either in "dotted" notation of the form
nnn.nnn.nnn.nnn or as a symbolic domain name. If a domain name is
specified, the domain name server is called on to convert the name
to an IP address.
In addition to the IP address, a port number must be specified.
It is the combination of an IP address and a specific port number
that identifies a unique connection point. Some port numbers are
reserved and are known as "well known ports." The following is a
list of the most common well known port numbers:
Port # Function
------ -----------------------------------
10 RXQT
21 FTP (File Transfer Protocol)
23 TELNET
25 SMTP (E-mail)
69 TFTP (Trivial File Transfer Protocol)
70 TALKD
80 World Wide Web server
110 POP (Post Office Protocol)
119 NNTP (Network News Transfer Protocol)
It is useful to know these port numbers because you can use them
when connecting to standard network server programs. For example,
if you wanted to write a robot Telnet program to access some site,
you would connect to port 23 at the site.
If you are developing your own server/client applications, you
must not use one of the reserved ports. It is suggested that you
use port numbers above 5000 for your own applications. Port
numbers may not exceed 65535.
18.3 dnsgetip -- Convert domain name to IP address
Function prototype:
string dnsgetip(domain_name)
string domain_name; /* Domain name to lookup */
This function calls the Domain Name Server (DNS) to convert an
Internet domain name to the corresponding Internet Protocol (IP)
address. The 'domain_name' argument is the domain name whose IP
address is to be found. The returned value is the IP address
string in dotted notation (nnn.nnn.nnn.nnn). A null string is
returned if the domain name could not be looked up.
18.4 dnsgetname -- Convert IP address to domain name
Function prototype:
string dnsgetname(ip_address)
string ip_address; /* IP address to convert */
This function calls the Domain Name Server (DNS) to convert an
Internet Protocol (IP) address to the corresponding Internet site
domain name. The 'ip_address' is the IP address to be converted.
It must be specified in "dotted" notation (nnn.nnn.nnn.nnn). The
value returned by the function is the corresponding domain name.
A null string is returned if the IP address could not be converted
to a domain name.
18.5 connect -- Connect to a TCP/IP socket
Function prototype:
int connect(address,port)
string address; /* Address to connect to */
int port; /* Port to connect to */
The connect function establishes a TCP/IP socket connection to a
specific port at a specific address. The 'address' argument is
the address of the site to connect to. This can be expressed
either as a dotted IP address of the form nnn.nnn.nnn.nnn or as a
symbolic domain name. If a domain name is specified, the domain
name server is called to convert the domain name to the
corresponding IP address. The 'port' number is the port number to
which the connection will be made. See page 249 for a list of the
reserved port numbers.
The value returned by the connect function is a a socket number
that can be used with I/O operations such as fread and fwrite. A
value of 0 is returned if the connect is unsuccessful. In that
case, the perror function can be used to determine the nature of
the failure.
After executing a successful connect function, you can use I/O
functions such as fread, fprintf, freadbin, and fwritebin to
exchange information with the server program to which you are
connected. You should use the fclose function to close a socket
and terminate the connection.
The following example client program connects to port 1948 at a
site named "test.com", sends a request for information, reads a
reply, prints the reply, and then closes the socket and exits:
/*
* Example client application.
*/
void main()
{
int socket;
string reply;
/*
* Attempt to connect to the server.
*/
socket = connect("test.com",1948);
if (socket == 0) {
perror("Error making connection");
stop;
}
/*
* Send a request for information.
*/
fprintf(socket,"GET 1234\n");
/*
* Read the response from the server.
*/
fread(socket,reply);
/*
* Print the reply.
*/
printf("The reply is: %s\n",reply);
/*
* Close socket and exit.
*/
fclose(socket);
return;
}
18.6 accept -- Accept a TCP/IP socket connection
Function prototype:
int accept(address,port,timeout)
string address; /* Address to connect to */
int port; /* Port to connect to */
real timeout; /* (optional) Timeout in seconds */
The accept function is used in server programs to wait for a
connection request from a client program. The 'address' must be
an IP address or domain name on your computer. Normally, it is
the primary address of your system. This can be expressed either
as a dotted IP address of the form nnn.nnn.nnn.nnn or as a
symbolic domain name. If a domain name is specified, the domain
name server is called to convert the domain name to the
corresponding IP address.
The 'port' argument is the port number on which connection
attempts will be accepted. See page 249 for a list of the
reserved port numbers.
The 'timeout' argument is optional. It specifies the maximum
number of seconds to wait for a connection. Fractional values
such as 0.5 may be specified down to a minimum value of 0.01. If
a timeout occurs before a connection is received, the accept
function terminates and returns a value of 0. If the 'timeout'
argument is omitted, or a value of 0 is specified for it, the
accept function will wait forever for for a connection request.
The value returned by the accept function is a a socket number
that can be used with I/O operations such as fread and fwrite. A
value of 0 is returned if the accept is unsuccessful. In that
case, the perror function can be used to determine the nature of
the failure.
After executing a successful accept function, you can use I/O
functions such as fread, fprintf, freadbin, and fwritebin to
exchange information with the client program to which you are
connected.
You must use the fclose function to close a socket and terminate
the connection before calling accept again.
The following example server program accepts a connection, reads a
command line, and echoes the command as the reply which is sent
back to the client. It then closes the socket and waits for
another connection request.
/*
* Example server application.
*/
void main()
{
int socket;
string command;
/*
* Begin loop to accept and process commands.
*/
loop {
/*
* Wait for a connection request.
*/
socket = accept("test.com",1948);
if (socket == 0) {
perror("Error on accept");
}
/*
* Read the command from the client.
*/
fread(socket,command);
/*
* Echo back command as the result.
*/
fprintf(socket,"I received: %s\n",command);
/*
* Close socket to client.
*/
fclose(socket);
}
}
18.7 Reading and Writing to Sockets
Once a socket connection has been established using the connect or
accept functions, you can use ordinary file I/O functions to read
and write data.
Sockets are bidirectional. That means that you can read and write
on the same socket. Data written by you will be received by the
other end; data written at that end can be read by your program.
If you want to write formatted ascii records, the best function to
use if fprintf. Simply specify the socket number (which is
returned by connect or accept) as the first argument to fprintf.
See page 250 for an example program that does this. For example,
the following command sends a string terminated by carriage-return
and line-feed through the socket:
fprintf(socket,"The value is %d\n",value);
If you are receiving data through a socket that is terminated with
line-feed characters, the best function to use is fread. Specify
the socket number as the first argument and a string variable as
the second argument. The fread function will wait until a
complete line of text is received (terminated by line-feed) and
then return the text string without the terminating
carriage-return or line-feed in the string variable. See page 252
for an example program that does this.
If you are reading and writing data that is not terminated by
line-feed, the fwritebin and freadbin functions can be used.
These functions write or read data data from string variables.
fwritebin does not add any additional characters and freadbin does
not remove any characters. Binary data including nulls can be
transmitted using these functions.
One aspect of socket communication that can be somewhat confusing
is that data packets can be delivered in pieces rather than as a
single unit. For example, if you are reading a 100 byte data
record, it may be received as three packets of 40 bytes, 50 bytes,
and 10 bytes. This is not a problem when using fread to receive a
record terminated by line-feed, because fread continues to read
until a line-feed is received. However, when using freadbin to
receive a packet, you have to be aware that the packet may be
delivered in pieces. To help you deal with this situation,
freadbin includes an optional argument that you can specify to
force it to wait until a specified number of bytes have been
received. If you do this, it will accumulate packets until the
specified number of bytes have been accrued.
The format of the freadbin function is:
int freadbin(socket,inrecord,wantlen,timeout,forcelen)
int socket; /* Socket number */
string inrecord; /* Variable to receive packet */
int wantlen; /* Maximum bytes wanted */
real timeout; /* (optional) Timeout value */
int forcelen; /* (optional) Required length */
There are two length-related arguments: 'wantlen' and 'forcelen'.
The 'wantlen' argument specifies the maximum number of bytes that
you want to receive. freadbin will wait until this many bytes are
received or the end of a packet is reached, whichever occurs
first. The 'forcelen' argument is optional. If specified,
freadbin will continue to collect packets until the specified
number of bytes are accumulated; it will then return a string of
exactly 'forcelen' bytes.
18.8 Socket I/O scripts
TPL provides a clsocket function that allows the communication
line functions described in Chapter 17 to be used with I/O
performed through a socket rather than a serial communication
line.
The function used to connect a socket to the communication line
functions is clsocket. The form of this function is:
int clsocket(socket)
int socket; /* Socket number */
Where 'socket' is the socket number returned by the connect
function. You use clsocket rather than clopen to engage
communication line processing for a socket.
The value returned by clsocket is 0 if the connection is
successful. An error code is returned if an error is detected.
Once clsocket has been executed to connect a socket with the
communication line functions, you can then use other communication
line functions to receive, send, and wait for incoming strings.
For example, clwaitfor can be used to wait for a specified string
to be received from the socket.
There is one important restriction on socket connections: You
cannot perform X/X/Z-modem transfers through a socket.
The following example program connects to the Telnet port (23) at
"sandh.com" and then engages the communication line functions to
perform a logon and begin a communication session.
/*
* Example program to do Telnet connection.
*/
void main()
{
int status;
int socket;
/*
* Connect to Telnet port at site.
*/
socket = connect("sandh.com",23);
if (socket == 0) {
perror("Error connecting");
return;
}
printf("Connected\n");
/*
* Connect the socket to communication functions.
*/
clsocket(socket);
/*
* Declare ctrl-X as abort character.
*/
clconnect(24);
clwhen(1,"press a key to continue","\r");
/*
* Wait for logon prompt, then logon.
*/
clwaitfor("real first name:");
clsend("Jud Graves;bolles\r");
/*
* Wait for logoff message.
* During wait, keyboard is connected to socket.
*/
clwaitfor("Thank you for calling");
/*
* Finished
*/
fclose(socket);
return;
}
18.9 Example Web client
/*
* This is sample client program which will fetch a web page
* and print out the title and the links. It could be used
* as the basis for a web crawler.
*
* To invoke the program, specify the initial URL
*/
prototype string doconnect(1);
prototype void doparse(1);
prototype int findtag(6);
void main(string arg)
{
string document;
/*
* Initialize; connect to site and send over GET filename
*/
document = doconnect(arg);
if (document == "") return;
/*
* Break up the document, extract the title and the links
*/
doparse(document);
/*
* Finished
*/
return;
}
/*-----------------------------------------------------
* Massage the URL , get the filename, connect and send
* the request and retrieve the resulting file.
*/
string doconnect(string arg)
{
int socket,pos,sts;
string website,filename,s,accum;
/*
* Strip initial protocol; we're ignoring it.
*/
if (strupr(arg[0:7]) == "HTTP://") arg = arg[7:];
/*
* Break up into address and directory components
*/
pos = locate("/",arg);
if (pos == -1) {
/*
* No directory specified, as in http://www.zerox.com
*/
website = arg;
filename = "/";
} else {
website = arg[0:pos];
filename = arg[pos:];
}
/*
* Contact the web site
*/
printf("Connecting to %s\n",website);
socket = connect(website,80);
if (socket == 0) {
printf("Error connecting to web site\n");
return "";
}
/*
* Send over command to get the file. Say we're HTTP 1.0.
* The null line means that we've sent over all the data
* we want to.
*/
fprintf(socket,"GET %s HTTP/1.0\r\n",filename);
fprintf(socket,"\r\n");
/*
* Read data until we get EOF, indicating server closed
* connection. We could also check for a 0 byte read.
* This read may return less than 1000 bytes.
* Since we're interesting in the entire thing we
* don't care; just concatenate each block we read.
*/
accum = "";
while (1) {
sts = freadbin(socket,s,1000);
if (sts) break;
accum = accum $ s;
}
/*
* Finished with socket.
* Close and return the accumulated document.
*/
fclose(socket);
return accum;
}
/*--------------------------------------------------------
* Parse the resulting document, stripping out the <title>
* and <a> entries.
*
* Arguments:
* String document - entire document
*/
void doparse(string document)
{
int sts,tagstart,tagdata,tagend,startindex,len;
string title,anchor;
/*
* Find the title
*/
sts = findtag(document,"title",0,0,0,tagend);
if (sts) {
findtag(document,"/title",0,tagstart,0,0);
len = tagstart-tagend-1;
title = document[tagend+1:len];
printf("Title is %s\n",title);
}
/*
* Find each anchor and dump it
*/
startindex = 0;
while (1) {
sts = findtag(document,"a",startindex,tagstart,
tagdata,tagend);
if (!sts) break;
len = tagend - tagdata;
anchor = document[tagdata:len];
printf("%s\n",anchor);
startindex = tagend;
}
/*
* Finished
*/
return;
}
/*------------------------------------------------------
* Search for a tag. This will allow white space after
* the '<' and will perform a mixed case comparison.
*
* Arguments:
* string document - document to search
* string tag - tag name
* int startindex - place to start looking
* int tagstart - starting index (location of '<')
* int datastart - start of data after tag name
* int tagend - ending index (location of '>')
*
* Returns:
* 1 if found, 0 if not found
*/
int findtag(string document,string tag,int startindex,
int tagstart,int datastart,int tagend)
{
int bracket,end,pos,len;
string test;
loop {
/*
* Locate the next tag, see if it is the one we want.
* Remember '<' location.
*/
pos = locate("<",document,startindex);
if (pos == -1) return 0;
tagstart = pos;
/*
* Collect tag name
*/
while (1) if (document[pos++] != " ") break;
for (end = pos; ; end++) {
if (document[end] == " ") break;
if (document[end] == ">") break;
}
len = end - pos;
test = document[pos:len];
/*
* Remember location of start of data and ending location
*/
datastart = end;
tagend = locate(">",document,end);
/*
* See if this is our tag.
* If not, advance past this tag and try again.
*/
if (tag == test) return 1;
startindex = tagend + 1;
}
}