Clipper and Networking
Clipper and Networking
and
Networking
A local area network in its simplest terms is a group of personal computers wired to central serving
unit that provides file handling, printing resources, communications devices, and other hardware and
software. No matter what configuration of network you have (token ring, STARLAN, Etherlink,
ARCnet, and so on), some basic principles apply.
Workstations (usually PCs) are computers or terminals connected by cable or fiber optics to a file
server and in some cases to multiple servers that control printing communications or other network
duties. The file server is large central storage unit for all of the workstations connected to it.
Whenever a workstation requests a service, the file server performs all of the disk operations
necessary to supply the workstation with data and program code.
All of the actual computation is done locally at the workstations. The file server simply passes
information in data packets that are addressed to a particular workstation much like a letter might be
addressed to a particular mailbox. What the workstation does with data before writing it back to the
file server’s hard disk is of no real concern to the network operating system. It is simply information
to be transferred.
The hard drive on a file server is usually quite large. The hard disk is arranged in subdirectories that
can be accessed by any workstation that has been given clearance. By utilizing user names and
passwords, the network operating system provides a means of logging on to the network. A user who
has a valid password is given a predetermined level of access to the network services. This access
also restricts the user to using files in only those subdirectories he or she has permission to use. The
System Administrator decides what files a particular user may alter by setting some files as “read-
only.”
4
another workstation. The UNLOCK command is used within an application to release locks that have
been applied so that other users may have access.
You try to USE a file that is currently being USEd EXCLUSIVEly by another workstation.
You have tried to USE EXCLUSIVEIy a file that is already open on another workstation.
You have tried to APPEND BLANK in a file that is temporarily locked with the FLOCK() function.
You have tried to APPEND BLANK into a file at exactly the same time as another user.
Developing Applications for Networks
Clipper makes it easy to develop network applications -even on an independent (not networked)
system. When you SET EXCLUSIVE OFF, Clipper expect all other commands and functions to operate
within the multiuser environment. If you forget to lock a record before you try to write to it, Clipper
will object and display an error message. This is extremely useful in debugging because these error
messages will be returned even on a system that is not connected to a network.
Try entering this:
SET EXCLUSIVE 0FF
USE datfile
GOTO 12
REPLACE strl WITH “string”
WAIT “You will never see this message”
What you will see is a DOS prompt and an error message telling you that there is a lock required. If
you are using standard procedures and well tested user-defined functions, you do not have to
develop network applications on a network.
Programming with a Local Area Network in Mind
Whenever you set out to create a multiuser application, you need to work out the details of multiple
usage. A large part of your decision-making is determined by the type of application that you are
building.
Are entries to the database short, or does the input screen have 65 entries?
Will certain data files in the application be used more often than others?
Will the System Administrator need to do massive maintenance -that is, bring in large
numbers of records from a mainframe- several times a day?
Is the data the type of information that usually is only entered once, such as
historical tracking or research compilation, and then updated only to correct typos?
Is the data fairly static, such as price lists and museum inventory, and referred to
more than it is written?
5
Only you will be able to determine the exact file and record locking scheme that best
suits your applications.
6
attempts in perform the action once each second until either the time you have inc as an argument is
expired or the lock is achieved.
The one area these functions do not address is the user interface that each application needs. The
file and record locking system that is used for the invoicing application in Part II is based in part on
the basic structures introduced in LOCKS.PRG. The invoicing application, however, provides a standard
user interface enables the user in decide issues of persistence.
Study the code in LOCKS.PRG to understand why the functions work. Next, look at the following user-
defined functions to see the basic differences in philosophy. Then design a file and record locking
scheme to suit yourself or use as a plug-in module the set that appeals most to you.
Opening Files on a Local Area Network
The simplest of all ways to use a file in a network environment is simply to begin your application:
USE datfile
In order to USE files in shared mode within a Clipper app you must use the SET EXCLUSIVE OFF
command. When you have done so, your application opens files in shared mode.
Never open data and index files with the same command. Trying to USE an unavailable data file is a
much lower-level error than trying to open index file s for which no data file is available.
Trying to open an index file without a data file triggers a Clipper run-time error. This aborts your
application. Attempting to USE a data file that is unavailable simply returns the NETERR() function
as true ( .T.).
The expression USE datfil INDEX dat1, dat2, dat3 is dangerous because if datfil is not
available, NETERR() will be set to true (.T.), and the command then will try to open the index files. The
data file is not open, so you get the run-time error message.
When you open a file, test to make sure that a network error has not occurred. When you have
access to the data file, you can add index files, close index files, change their order, and so on
without regard for consequence. Index file handling is totally transparent.
The following is the simplest way to open a network file:
SET EXCLUSIVE OFF
USE filename
IF .NOT. NETERR()
SET INDEX TO filename
ELSE
? “NETWORK ERROR HAS OCCURED, FILE NOT OPEN.”
ENDIF
Index files that are opened along with a shared data file automatically reflect any file on record
locking situations applied to the data file.
Opening a File with USE_UDF()
Here you have the issue of persistence. What do you do if the USE is refused? This user-defined
function leaves persistence for the operator to decide. The function tries to open the file for five
seconds and then prompts the operator for a decision.
7
* USE_UDF function
* ATTEMPTS TO OPEN A FILE FOR NETWORK USE IN EITHER
* SHARED OF EXCLUSIVE USE
* PARAMETERS USED:
* 1. FILE NAME (BE SURE TO SURROUND IN OUOTES)
* 2. MODE ( .T. = EXCLUSIVE .F. = SHARED)
* Example usage:
* IF USE_UDF( “CUST”, .T. )
* SET INDEX TO cust, lnam, bal
* ELSE
* CLEAR
* CLEAR ALL
* RETURN
* ENDIF
FUNCTION USE_UDF
PARAMETERS datfil, usemode
delay = 5
DO WHILE T.
IF usemode && Attempt to open exclusive
USE &datfil, use_mode
ELSE
USE &datfil && Attempt to open shared mode
ENDIF
IF .NOT. NETERR() && File is opened successfully
RETURN .T. && Go back to calling procedure
&& with the value of the function
&& returned as True
ELSE
IF delay > 0
INKEY( 1 )
delay = delay — 1
LOOP
ELSE
SAVE SCREEN
conf = " "
@ 8, 15 clear TO 20, 60
@ 8, 15 TO 20, 60 DOUBLE
@ 10, 20 SAY "Data File: " + datfil
@ 12, 20 SAY "This file is unavailable at this time."
@ 14, 20 SAY "Do you want to continue attempting"
@ 16, 20 SAY "to open it for another five seconds?"
@ 18, 20 SAY " (Y/N) ------------>"
SET CURSOR OFF && These functions do
WAIT TO "" TO CONF && not use a READ
SET CURSOR ON && command, so they
IF UPPER(conf) = "Y" && can be included as
delay = 5 && part of a function
RESTORE SCREEN && invoked with a
LOOP && VALID clause in a
ELSE && GET / READ construct
RESTORE SCREEN
RETURN .F.
ENDIF
ENDIF
ENDIF
to
delay = delay - .5
This would double the number of attempts within the delay frame.
While the loop is within the delay frame, the USE_UDF function repeatedly tries to open the file. If,
after the delay time has counted off the file is still not opened (NETERR() <> .T.), the USE_UDF() function
will save the screen and display a message box.
The message box serves two purposes. First, it tells the operator that the file is unavailable at the
moment. Second, it places the persistence factor in the operator’s hands by asking whether the
operator would like to continue waiting for that file.
9
If the value of conf is Y, the function will reset the delay and continue the loop. If the operator
chooses not to wait, the USE_UDF() function will be terminated and its value returned as false (.F.).
If USE_UDF() is false, the application cannot continue because in all likelihood the operations that
are programmed into the module require the file in be open. Trying to perform operations on
unavailable files creates run-time errors that abort the program. You need a way in back out of the
trial gracefully so that the program can return you to the menus. See the invoicing program in Part II
for some ideas.
In reality, all you need to know in order to use this function is what you want the application to do if
the USE command fails.
Creating a New Record with APND()
When the file and its associated index files are opened, you may proceed to use the file as you
normally would. However, if you want in append a new record to the file, make sure that no one else
has locked the file or is attempting to append a blank record at exact the same moment as you are.
In the network environment, APPEND BLANK not only creates the new record, but locks it as well
with the RLOCK() command.
The safeguards for APPENDing new records are simple. APPEND BLANK is a command that executes
quickly. Two simultaneous APPEND BLANK requests cause one of the requests to be rejected; but
because the command doesn’t make long in complete, the second request is almost always honored.
Persistence is seldom a problem here. The request normally is honored on the first or second try. The
only other situation that might cause an APPEND BLANK in fail is when another workstation has
temporarily placed a file lock. In such situations persistence comes into play.
Here is the APND() function from the invoicing program:
* APND function
*
* Attempts to append a blank record to the file and lock it.
* If the append is successful, the function returns .T.
FUNCTION APND
delay = 5
DO WHILE .T.
APPEND BLANK
IF NOT. NETERR()
RETURN .T.
ENDIF
IF delay 0
INKEY(1)
delay = delay — 1
LOOP
ELSE
SAVE SCREEN
conf =
@ 8, 15 clear TO 18,60
@ 8, 15 TO 18,60 DOUBLE
@ lO, 20 SAY "New record creation was unsuccessful."
@ 12, 20 SAY "Do you want to continue attempting"
@ 14, 20 SAY "to add it for another five seconds?"
@ 16, 20 SAY " (Y/N) ——--—>"
SET CURSOR 0FF
10
WAIT "" TO CONF
SET CURSOR ON
IF UPPER(conf) = "Y"
delay = 5
RESTORE SCREEN
LOOP
ENDIF
ELSE
RESTORE SCREEN
RETURN .F.
ENDIF
ENDD
The APND() function uses the exact same interface as USE_UDF() to tell the user that a requested
service was unsuccessful. The APND() function does not require any argument because the APPEND
BLANK command automatically creates the new record with an RLOCK() command in place. Again,
the delay is set for five seconds.
Within a loop, the APPEND is attempted. If an error does not occur, the value of the function is set to
true (.T.), and the APND()function is terminated.
If a network error did occur, the user-defined function will attempt the APPEND BLANK command
once each second for as many seconds as the delay is set. If the time expires without successful
completion of the APPEND BLANK command, the user interface displays a message to the operator
that the attempt to create the new record was unsuccessful. If the operator declines to wait for the
APND() function to create the new record, your application must be able to back out gracefully.
* RLOK function
*
* Attempts to lock the current record
* for temporary exclusive use.
* No parameters are needed to be passed.
*
* Example usage:
* IF RLOK()
* REPLACE FIELD1 WITH var1
* REPLACE FIELD2 WITH var2
* REPLACE FIELD3 WITH var3
* ELSE
* CLEAR
* CLEAR ALL
* RETURN
* ENDIF
FUNCTION RLOK
delay = 5
DO WHILE .T.
IF RLOCK() && Attempt to lock the record.
RETURN (.T.) && If successful go back to calling
&& procedure with the value of the
&& function returned as True.
ENDIF
IF delay > 0
INKEY(1)
delay = delay - 1
LOOP
ELSE
SAVE SCREEN
conf = ""
@ 8, 15 clear TO 18,60
@ 8, 15 DOUBLE
@ 10, 20 SAY "Record locking was unsuccessful."
@ 12, 20 SAY "Do you want to continue attempting"
@ 14, 20 SAY "to lock it for another five seconds?"
@ 16, 20 SAY " (Y/N)-———>”
SET CURSOR 0FF
WAIT "" TO CONF
SET CURSOR ON
IF UPPER(conf) = "Y"
delay = 5
RESTORE SCREEN
LOOP
ELSE
RESTORE SCREEN
RETURN .F.
ENDIF
ENDIF
ENDDO
The RLOK() function has a great deal in common with the other user defined functions presented in
this chapter. The delay structure is almost exactly that of the others, and the user interface is the
same except for the messages.
12
Locking the Current File with FLOK()
The FLOK() user-defined function does exactly the same thing as the RLOK() function except that
the scope of the function covers the entire file rather than a single record. The two functions work
the same way.
* FLOK function
*
* Attempts to lock the current file
* for temporary exclusive use.
* No parameters are needed to be passed.
*
* Example usage:
* IF FLOK()
* AVERAGE bal TO avbal FOR bal > 5000
* ELSE
* CLEAR
* CLEAR ALL
* RETURN
* ENDIF
FUNCTION FLOK
delay = 5
DO WHILE .T.
IF FLOCK() && Attempt to lock the file.
RETURN (.T.) && If successful, 90 back to calling
&& procedure with the value of the
&& function returned as True.
ENDIF
IF delay > 0
INKEY(1)
delay = delay - 1
LOOP
ELSE
SAVE SCREEN
conf = ""
@ 8, 15 clear TO 18, 60
@ 8, 15 TO 18,60 DOUBLE
@ 10, 20 SAY "File locking was unsuccessful."
@ 12, 20 SAY "Do you want to continue attempting"
@ 14, 2O SAY "to lock it for another five seconds?"
@ 16, 20 SAY " (Y/N)----->"
SET CURSOR 0FF
WAIT "" TO CONF
SET CURSOR ON
IF UPPER(conf) = "Y"
delay = 5
RESTORE SCREEN
LOOP
ELSE
RESTORE SCREEN
RETURN .F.
ENDIF
ENDIF
ENDDO
13
Implementing Record and File
Locking in Your Application
Now that you have a basic set of tools to use, you can plug the tools into the proper places and get
on with making the program do what you want it to do and with the assurance that multiple users
can run it.
Lock a record whenever the possibility exists for the flow of the program to make any change to that
record. Lock the entire file when you perform commands that affect the contents of more than one
record in the data file. For example:
REPLACE ALL balance WITH balance * .05 FOR lastpay < CTOD ("01/01/89")
Because the scope of this command probably alters more than one record within the file, the entire
file should be locked.
Read-only operations can be processed without regard to record locks. Any file or record can be read
even though it is locked. That means a report can run while the data is changing. By the time the
print job is completed, the data is no longer absolutely current. In many respects this is nothing new.
Most reports are somewhat dated by the time they are actually used.
A report usually is understood to be a snapshot of the data at a particular point in time. Once in a
while, however, you need to know the exact total of items in the file. Whether it is a report or one of
the summary commands (AVERAGE, SUM, COUNT, TOTAL), you may need to lock the file or USE
it EXCLUSIVEly in order to get absolute numbers, in most cases, it is perfectly acceptable to let a
report run through the record lock: the next report will reflect the new data.
It is worth repeating that you should place the record lock before you allow the user to edit the data.
Avoid letting the operator make 10 minutes worth of entries, only to inform him or her that you can’t
get the record to lock. Whenever the operator chooses to edit data, apply the lock, and then fill
variables with the values. Edit the variables, not the fields. If the operator wants to back out, all the
program has to do is UNLOCK the record without performing any REPLACE commands.
Some provisions not expressly envisioned in the Clipper file and record locking schemes are MEM
files, ALTERNATE files, and other files that you might be creating with the low-level F functions, such
as FCREATE(), FSEEK(), FWRITE(), and so on.
A powerful method of handling these situations is to build a semaphore database, so named because
it uses records as flags. Each type of operation that you wish to control is given one record in the
.DBF file. Then all you have to do is to use the capability to RLOCK() that record as a determinant.
For example, when an app needs to create or update a .MEM file, it must first set the record pointer
to the proper record in the semaphore file and lock it. Only one user can have a record locked at any
given time, so this is an absolute indication that it is safe to manipulate the .MEM file.
14
The invoicing application in Part II uses a semaphore database with just one record to protect the
integrity of the next invoice number. A quick look at the source code reveals how simple it is to
construct and use a semaphore database.
A semaphore database introduces other possibilities, such as locking messages that tell one user
what other user owns the lock and for what duration of time the lock has existed. A dynamic
semaphore gives you the opportunity for including real-time updated messages. If your application
were to write a record to a semaphore file stating the conditions of the file or record lock
(EXCLUSIVE use, record numbers. time stamps. and so on), the file and locking functions can report
more information to the operator whose request for service was denied.
For example, when the application locks a record, the application writes a record to the semaphore
file. The fields take entries for the file name, record number. TIME(), procedure, and the user’s
name. Then, when the lock is released, the semaphore record is deleted. If another user requests a
lock for the same file at the same record, the locking function attempts the lock. If the lock fails, the
application then searches the semaphore database for a record that corresponds to the failed
attempt. In this case it probably is efficient to use the LOCATE command, because there would be
only a small number of records (no more than one per user) to evaluate.
For example:
LOCATE FOR datfil = ‘cust” .AND. RECN() = 35
IF .NOT. EOF() && or you could use IF FOUND()
@ 10, 10 SAY "User " + TRIM(user_nam) + " has that record locked."
@ 11, 1O SAY "The lock was placed at:" + lok_time
ENDIF
The first command applies only to those commands that GET. Some commands can affect more than
one record, depending on the scope of that particular use of the command. For example:
15
REPLACE balance with 500 && Affects only the current
&& record. A record lock is
&& adequate.
Summary
After reading this chapter, you should have a good idea of the issues involved with letting multiple
users have access to data files simultaneously.
You also should have a set of tools to accomplish the task. The record and file locking functions
included in this chapter make up a complete set that you can use verbatim.
16