United States Argentina Australia Austria Belgium Canada Chile Colombia Costa Rica Dominican Republic France Germany Bangladesh/India Italy Kenya Mexico Netherlands Puerto Rico South Africa Sweden Switzerland Venezuela
BASIS International Ltd.
Home | Site Map | Contact Us | Partner Login  

 








 
Practical Ports And Serviceable Sockets
By Nick Decker

We've written about using TCP/UDP sockets in PRO/5®, Visual PRO/5® and BBj® before, but as time goes on, we invariably think of new ways to use them. On the BASIS demo CD, for example, we include server and client programs that utilize sockets to communicate with one another. These programs do a good job demonstrating sockets in general, how to write client and server programs, and even give an example of a user-defined protocol between the two. This article takes sockets in a different direction - writing a socket client that communicates with pre-established servers using pre-defined protocols. The two programs discussed in this article function as clients to a POP mail server and to an FTP server.

Although you probably don't want to reinvent the wheel and write your own client applications, these examples can still be useful, as they demonstrate how BBj interacts with networked servers in ways not previously covered. They also explain how to incorporate some of the basic functionality of more complex client applications into your application. For example, you may not want or need all of the bells and whistles provided by a full-blown web browser or FTP client. However, you could use BBj programs similar to these examples to provide a means for your application to query your web or FTP site and to determine if updates are available. If they are available, you can arrange for an automatic download. Features such as these enhance your application in many ways, and the fact that all of the functionality is integrated into your application is even more appealing. There's no need to rely on specific client software to exist on a workstation, and no distracting third-party applications will pop up on the user's desktop.

Additionally, these program examples provide insight on how to use sockets in BBj in a variety of different ways, and perhaps in ways you never considered. With sockets you can communicate with almost any TCP or UPD-based server, regardless of whether it's a web server, FTP server, mail server, etc. By accessing servers like this, you can send and retrieve email, download web pages and files, obtain news and weather information, get stock quotes, track packages, and more!

Before digging into the programs themselves, it's worth mentioning that they're fairly basic client implementations. They are not fully-featured nor do they do much in the way of error handling, as that would add to their complexity and detract from their value as learning tools. They also take advantage of some BBj-specific features, such as symbolic labels (*NEXT) and the MASK function's regular expression operators for this same reason. However, they can be adapted to run in PRO/5 with little effort. There are also some links at the end of this article that provide all of the necessary information regarding the POP and FTP protocols.

pop.lst Program
The pop.lst program (Figure 1) is fairly straightforward due to the simplistic nature of the POP3 protocol. As a brief overview, it establishes a TCP connection to the mail server, sends the desired login and password, and then retrieves the account's number and size of unread mail messages. All of the communications between the BBj socket client program and the POP mail server are done via an alternating exchange of commands and responses.

The pop.lst program initiates the connection to the server by opening a TCP socket connection. This is accomplished with the OPEN statement to the "N0" alias line. The "N0" alias line exists in the config.bbx file as:

    ALIAS N0 tcp "" NODELAY

Because the alias line is generic, the OPEN statement includes the hostname of the mail server and the port on which you'll be talking. Port 110 is the default for POP-based mail servers, so that is what is used in this example.

Once the connection is opened to the server, the program enters the REPEAT-UNTIL looping construct that is responsible for guiding the subsequent conversation between the two. Because the server sends a greeting upon connection, the loop starts off with an INPUT from the socket channel. This reads in data that was sent by the mail server and assigns it to the tcpData$ string variable.

The next step is to analyze what was returned by the server. The POP protocol dictates that the server will always preface messages with a "-ERR" in the case of an error and a "+OK" for everything else. Therefore, the program does a quick check to see if the server returned a POP error or not. If there was a problem, such as an incorrect password for the specified login, the program prints out the error message, closes the socket connection and exits. In all other cases, the "+OK" is assumed, and the program continues.

Since the communication between the client and server follows a structured series of events, the program goes from event to event via a series of numbered states. This lends itself nicely to the SWITCH verb and makes the code easy to follow. As the program executes, it follows the pattern of sending a command to the server, verifying that it got a successful response, and moving on to the next command.

The command of particular interest is the STAT command. It instructs the server to respond with the total number of mail messages on the server and the size of the messages. The format of the response will be "+OK xx yy", where xx is the number of messages and yy is the total size of the messages. These numbers are parsed out of the return message and printed out. Once this has been done, the program sends a QUIT command to the server.

There's more to the POP protocol than demonstrated in this article, and the program could easily be enhanced to download e-mail messages and even delete them from the server.

Figure 1. pop.lst listing:

REM Initial Setup
dim mail$:"Description:C(30),Server:C(30),Login:C(30),Password:C(30)"
mail.Description$ = "Work Account"
mail.Server$ = "myserver.mycompany.com"
mail.Login$ = "mylogin"
mail.Password$ = "mypassword"
POP3_CONNECT = 0
POP3_USER = 1
POP3_PASS = 2
POP3_STAT = 3
POP3_QUIT = 4
POPState = POP3_CONNECT

REM Open a socket connection to the Mail Server
tcpChan = unt
open(tcpChan,mode="host=" + cvs(mail.Server$,3) + ",port=110",err=noServerAvailable)"N0"
print "Connected to mail server '"+ cvs(mail.Server$,3) + "'..."

REM Communicate with the host
Repeat

   input (tcpChan) tcpData$
   if tcpData$(1,1) = "-" then

      REM We got an error from the POP server
      print "POP error: ",tcpData$
      goto closeConnection

   else

      REM Since we didn't get a -ERR, we must have a +OK response
      switch POPState

         case POP3_CONNECT
            REM Send the server the login information
            POPState = POP3_USER
            print(tcpChan) "USER " + cvs(mail.Login$,3)
            print "Sent login information..."
            break

         case POP3_USER
            REM Send the server the password information
            POPState = POP3_PASS
            print(tcpChan) "PASS " + cvs(mail.Password$,3)
            print "Sent password information..."
            break

         case POP3_PASS
            REM Request message information
            POPState = POP3_STAT
            print(tcpChan) "STAT"
            print "Requested statistics..."
            break

         case POP3_STAT
            REM Ensure we got a valid string back from the server
            if (mask(tcpData$,"^\+OK\s\d+\s\d+")) then

               pos1 = mask(tcpData$,"\s\d+\s")
               print "There are" + tcpData$(pos1,tcb(16)),
               print "unread email messages totaling ",
               print cvs(tcpData$(pos1+tcb(16)),3) + " bytes"

            endif
            POPState = POP3_QUIT
            print(tcpChan) "QUIT"
            print "Sent quit command..."
            break

         swend

      endif

until POPState = POP3_QUIT

REM Closing connection to server
closeConnection:
close(tcpChan)
print "Connection to server closed."
end

REM Connection failure
noServerAvailable:
print "Failed to connect to the mail server '" + cvs(mail.Server$,3) + "'."
end

Output of the pop.lst program
======================================================
Connected to mail server 'myserver.mycompany.com'...  
Sent login information...                           
Sent password information...                        
Requested statistics...                             
There are 2 unread email messages totaling 2567 bytes  
Sent quit command...                                
Connection to server closed. 
ftp.lst Program
The ftp.lst program (Figure 2) is very similar to the pop.lst program in Figure 1. It also establishes a connection to a server and exchanges a series of commands and responses. However, the FTP protocol is a bit more involved as it requires two sockets - one for commands and one for data.

The program starts out with initialization, which involves creating the empty target file on the local drive. In this example, the target file is downloaded from the FTP server, and it is the Windows port of BBj from the nightly builds section on the BASIS FTP site.

Once the login and password are sent and accepted, the server responds with a welcome message which can be several lines long. The program looks for a "230" in return, which signifies the acceptance of the login and the last line of the welcome message. After the program logs in, it changes to the desired download directory, specifies a binary download type, and gives the server the PASV command. This command tells the server to go into passive mode, which means that it will determine a free, non-default port to listen for the data connection. This allows the program to make another TCP socket connection to the FTP server and download the desired file from it on that port. The key here is that the FTP server is still acting as the TCP server, and the program is still acting as a client to the server. If the server were not put into passive mode, it would require the program to act as a TCP server on the default port, and the FTP server would then act as a client, connect to the port, and send the data.

The next step is to request the file via the RETR command and determine the port on which it should connect to the FTP server for the transfer of data. The port is computed using the return message from the PASV command which is in the format of:

227 Entering Passive Mode (h1,h2,h3,h4,p1,p2)

The h's specify the internet host address, and the p's specify the data port. p1 is the high order byte, and p2 is the low order byte of the 16-bit port, so the decimal form is calculated by multiplying p1 by 256 and adding that value to p2.

Once the port is constructed, the program opens a second TCP socket connection to the same server on the new port. Because this port is dedicated to the transfer of data instead of commands, the program immediately goes into a read/write loop. It reads the data from the byte-oriented channel and writes it directly out to the target file on the local drive. When it hits an end-of-file on the read, it closes the two channels, cleans up and exits. The result is an exact copy of the 2166000.exe file that exists on the BASIS FTP server.

Figure 2. ftp.lst listing:

REM Initial Setup
dim ftp$:"Description:C(30),Server:C(30),Login:C(30),Password:C(30)"
ftp.Description$ = "BASIS FTP Server"
ftp.Server$ = "ftp.basis.com"
ftp.Login$ = "anonymous"
ftp.Password$ = "me@mycompany.com"
FTP_CONNECT = 0
FTP_USER = 1
FTP_PASS = 2
FTP_TYPE = 3
FTP_PASV = 4
FTP_LIST = 5
FTP_RETR = 6
FTP_QUIT = 7
FTPState = FTP_CONNECT

REM Setup downloaded file
erase "\2166000.exe",ERR=*NEXT
string "\2166000.exe"
fileChan=unt
open(fileChan,isz=-1)"\2166000.exe"

REM Open a socket connection to the FTP Server
tcpChan = unt
open(tcpChan,mode="host=" + cvs(ftp.Server$,3) + ",port=21",err=noServerAvailable)"N0"
print "Connected to FTP server..."

REM Communicate with the host
Repeat

   input (tcpChan) tcpData$
   if len(tcpData$)=0 then FTPState = FTP_QUIT

   switch FTPState

      case FTP_CONNECT
         REM Send the server the login information
         FTPState = FTP_USER
         print(tcpChan) "USER " + cvs(ftp.Login$,3)
         print "Sent login information..."
         break

      case FTP_USER
         REM Send the server the password information
         FTPState = FTP_PASS
         print(tcpChan) "PASS " + cvs(ftp.Password$,3)
         print "Sent password information..."
         break

      case FTP_PASS
         REM Bypass message information, CD to the BBj-Developer area
         if (pos("230 " = tcpData$)) then
            FTPState = FTP_TYPE
            print(tcpChan)"CWD /private/bbj-developer/current/windows"
            print "Changed directory..."
         endif
         break

      case FTP_TYPE
         REM Set binary transfer
         FTPState = FTP_PASV
         print(tcpChan)"TYPE i"
         print "Forced binary transfer mode..."
         break

      case FTP_PASV
         REM Tell the server to give me a port to retrieve the file on
         FTPState = FTP_RETR
         print(tcpChan)"PASV"
         print "Forced passive mode..."
         break

      case FTP_RETR
         REM Initiate file transfer
         FTPState = FTP_QUIT
         print(tcpChan)"retr 2166000.exe"

         REM Determine which port the Server specified as the data port to retrieve the file
         dataPort$ = tcpData$(mask(tcpData$,"\d+,\d+\)"),tcb(16)-1)
         dataPort$ = str((num(dataPort$(1,mask(dataPort$,",")-1)) * 256) +
num(dataPort$(mask(dataPort$,",")+1)))
         print "Server specified port ",dataPort$
         print "Retrieving 2166000.exe",

         REM Open up a new connection to the server on the data port and download the file
         dataChan = unt
         open(dataChan,mode="host=" + cvs(ftp.Server$,3) +
",port="+dataPort$,err=noServerAvailable)"N0"
         retrieveFile:
            read record(dataChan,siz=-10240,end=retrieveComplete) temp$
            write record(fileChan) temp$
            print ".",
            goto retrieveFile
         retrieveComplete:
         print " "
         print "File transfer complete!"
         close (dataChan)
         close (fileChan)
         break

      case FTP_QUIT
         REM We're done - send the server the quit command.
         print(tcpChan) "QUIT"
         print "Sent quit command..."
         break

   swend

until FTPState = FTP_QUIT

REM Closing connection to server
closeConnection:
close(tcpChan)
print "Connection to server closed."
end

REM Connection failure
noServerAvailable:
print "Failed to connect to the ftp server '" + cvs(ftp.Server$,3) + "'."
end

Output of the ftp.lst program
======================================================
Connected to FTP server...                                            
Sent login information...                                              
Sent password information...                                             
Changed directory...                                                     
Forced binary transfer mode...                                           
Forced passive mode...                                                   
Server specified port 52966                                              
Retrieving 2166000.exe.....................................................
...........................................................................
...........................................................................
.......................................................                
File transfer complete!                                                   
Connection to server closed.  

Sockets offer a great deal of potential when it comes to communicating with other machines, servers, and programs all over the world. Because they can do so much, it is sometimes difficult to imagine the many ways in which sockets can be used to enrich your applications. These sample programs will hopefully spark your imagination and give you ideas on new ways to use sockets to their fullest potential.

The code in this article is available on the Advantage CD and also on our web site at:
www.basis.com/devtools/coolstuff/index.html

For further information on the POP and FTP protocols, see: