DBFSERVER
DBF Client/Server Suite for Xbase++Server SoftwareClient Connectivity Library
This document as a whole is copyrighted © 2001 by Phil Ide. The software
package (comprising dbfServer.exe, dbSocketc.dll, header [.ch] files, sample
programs, source code) is copyright © Bjørn Kaarigstad & Phil Ide, 2002. All
Rights Reserved.
The package is released under the terms of the GNU GENERAL PUBLIC LICENCE
Disclaimer
This software is supplied free of charge, and so there is no warranty for the
software or accompanying documentation (herinafter called the "package"), to
the extent permitted by applicable law. Except when otherwise stated in writing
the copyright holders and/or other parties provide the package "as is," without
warranty of any kind, either expressed or implied, including, but not limited
to, the implied warranties of merchantability and fitness for a particular
purpose. The entire risk as to the quality of this package is with you.
In no event unless required by applicable law or agreed to in writing will any
copyright holder, or any other party who may modify and/or redistribute the
package as permitted above, be liable to you for damages, including any
general, special, incidental or consequential damages arising out of the use or
inability to use this package (including but not limited to loss of data or
data being rendered inaccurate or losses sustained by you or third parties).
Any contributions made by others to this package have been acknowledged where
possible. Commercial products, retailers, or distributors mentioned here are
not necessarily endorsed by me or any other particular party.
Authors
Phil Ide code and documentation
Bjørn Kaarigstad concept, ideas, inspiration
Acknowledgements
Many thanks to those who tested and broke everything. Special thanks goes to
the following people who made particularly important contributions.
Edgar Borger
Jan Dirk Schuitmaker
Mike Evans
Mike Grace
Jose Luis Otermin
Thanks to everyone else who made comments, gave encouragement etc.
Documentation
Description
The package consists of a server program which accepts requests via TCP/IP to
perform operations on .DBF database tables. All Xbase++ operations are
supported with the exception of dacSession (which includes XbpQuickBrowse) and
compound legacy UI superfunctions (e.g. dbEdit()). The results of these
operations are returned to the client program.
There are many reasons why you might want to remove database operations to a
remote program, but in Xbase++ probably the most compelling reason is the way
that DBF access times deteriorate rapidly as more and more users connect to the
database in a multi-user environment under Windows.
dbfServer still opens the table once for each client connection, spawning a new
thread to service each connecting client. Within Xbase++, each thread maintains
a unique workspace, which has it's own set of unique workareas. The thread
remains active for as along as the server is running and the client is
connected.
Client programs can be forced to use the server by linking in a DLL which
handles all the communication between server and client. A #include file is
supplied which translates all standard database operations to use the functions
in the DLL. For many programs, particularly those generated by the Form
Designer, this is sufficient. However, this is largely dependant upon coding
style, as there are some elements the pre-processor cannot handle, and so some
additional changes are likely to be required.
New code can be written to take advantage of the fact that operations are
performed through the server. Converted legacy code will suffer from a certain
amount of lag since the code was written in the standard 'local connection'
format. Understanding how to reduce lag by caching commands is fundamental to
getting the best performance from the server.
Using code optimised for best performance with regard to the server will
produce fast responsive programs and reduce network traffic immensely thereby
making the network itself faster and more responsive to the benefit of all
users and applications regardless of whether or not they are utilising
dbfServer.
There are several other distinct advantages to using this package:
Want to lock everybody out of the system? Shut down the server program and
everyone will be unable to access the tables!
Using the server program console, you can see exactly who is connected, which
machine they are using, which socket number they are connected through and
which files they have open (both tables and indexes).
Do you have a time-consuming process which you know would run much faster on
the server? dbfServer has the ability to run such procedures for you. Not only
will the process run much faster, but again network traffic will be vastly
reduced to the benefit of all network users.
It should be pointed out that any DBF table can be accessed by any client
program, provided it is visible to dbfServer.
Converting Existing Programs
To link to the dbfServer in your own application, you need to do the following:
In each source file where database activity takes place, you need to add this
directive:
#include "dbfsocket.ch"
In your project make file, you need to add the following statement to your
executable's list of files:
DBFSOCKETC.LIB
In the same directory as your executable resides, and with the same name but
with an .ini extension you need to create an ini file. This file should contain
the following code:
[SOCKETS]
port=1042
server=localhost
You should change the port number to match the port dbfServer is listening on,
and the server name should be the IP name (or dotted octet address) of the
machine where dbfServer can be found.
There are three types of code that dbfServer cannot handle:
Whilst it handles:
(cExpr)->fname
(cExpr)->(fname)
it cannot handle:
alias->fname
alias->(fname)
This is due to limitations in the preprocessor. You should change all such code
to enclose the ALIAS in parenthesis.
Expressions generated as macro's from strings will not be parsed properly, and
will still expect local workarea connectivity.
Fieldnames without aliased expressions cannot be pre- determined by the
pre-processor. You must alias them.
For example, if you have a table with the field NAME in it, you cannot do this:
cName := NAME
Instead, you must do this:
cName := (Alias())->NAME // or
cName := (cAlias)->NAME
Remember that the path you supply to the program for opening tables is FROM THE
SERVER not the client!
That's all there is to it!
Optimising
The Form Designer when used to connect to a database, adds database access
controls to the ::editControls array, which is then evaluated when the dialog
is created or the record pointer is moved (via dialog controls). In a 'local
database connection' e.g. a USE statement, this is ok. In a remote connection
scenario, this becomes innefficient because to refresh 20 controls, you need to
send at least 60 instructions to the server. In large data-intensive dialog's,
you should consider another strategy.
One method is to perform dbfScatter()/dbfGather() to/from an array, and point
your dialog controls at the array rather than the database. dbfServer handles
these read/write operations in a single instruction.
All database functions are mapped to the function dbfSocket(). This function
accepts 3 parameters:
dbfSocket(<nFunction> [,<aParams> [, <lCache>] ] )
<nFunction> This is a numeric constant identifying the database operation to
call. You should use the constants defined in dbfSocket2.ch
<aParams> This optional parameter is an array of parameters to be passed to the
function defined in <nFunction>
<lCache> When this parameter is TRUE, the command is automatically cached and
not sent to the server until a non-cached command is issued.
Certain commands are automatically cached, but you can use dbfSocket() with
the auto-cache option to increase performance. The automatically cached
commands are a subset of all commands which return NIL. Some NIL-returning
commands are excluded from the subset because they are considered immediate,
e.g. dbCommit() and dbCommitAll() should not be cached, nor unlock operations.
If you know that you are not interested in the return value from a command,
then you should consider caching it. As an Example, Select(cAlias) may be used
to select a workarea prior to performing operations on it, and if you are not
interested in saving the return value then you should cache the command.
dbfScatter() and dbfGather() are special functions which request the server to
return and process an array respectively. This is more efficient than a
standard Scatter()/Gather() which sends at least 3 requests to the server for
each element of the array. As with the standard Alaska functions, dbfScatter()
and dbfGather() handle arrays of simple values or objects.
Additionally, dbfScatter() can accept an array of code blocks. This can be
used to explicitely identify the fields which are accessed in the event that
you don't need all the fields or want to get field data from multiple
workareas.
Automatically Cached Commands
The following commands are automatically cached:
dbAppend() dbGoTo() dbSetFilter()
dbClearFilter() dbGoTop() dbSetIndex()
dbClearIndex() dbRefresh() dbSetOrder()
dbClearRelation() dbResumeSelect() dbSetRelation()
dbClearScope() dbRollBack() dbSetScope()
dbCloseRelation() dbRSuspendSelect() dbSkip()
dbGoBottom() dbSelectArea() OrdCondSet()
dbGoPosition() dbSetDescend()
Remote Procedures
Remote Procedures are functions which are called by the client program, but are
run entirely on the server. The client tells the server to run the procedure,
then waits until the process has completed. The return value of the process is
the return value of the called procedure.
A Remote Procedure is programmed as a function in a dynamically loadable DLL
which must be visible to the server. If the function needs parameters, the
function must accept two parameters. The second parameter is an array
containing all the values the function requires, the first parameter is a
numeric indicating the length of the array.
Note that you cannot pass variables by reference to a remote procedure.
To call the remote procedure, use this command:
REMOTE CALL <func> IN <dll> [WITH <parms,...>] [<lUnload:UNLOAD>]
<func> is the name of the function to call
<dll> is the name (and path if required) of the dll where the function can be
found
<parms> is a comma-seperated list of all parameters that need to be passed to
the function
UNLOAD is an optional statement which causes the dll to be unloaded once the
procedure has finished.
An example of a (totally senseless!) function that can be used as a remote
procedure:
Function Useless(argc, argv)
local bFilt := {|| TRUE }
local i := 0
if argc > 0
bFilt := argv[1]
endif
dbGoTop()
While !Eof()
if Eval(bFilt)
i += RecNo() // this is silly...
endif
SKIP
Enddo
return (i)
To call this (assume it is linked into "RProc.DLL"):
REMOTE CALL Useless IN RProc WITH {|| GENDER == 'M' } UNLOAD
Error Handling
If the client attempts to perform an operation which fails at the server, the
server returns an error object to the client, which automaticaly passes it to
the current error handler. Any error message displayed by the client will
probably display the callstack of the client program, so you should be able to
track logic errors in your code. In the event the error really belongs to a bug
in the server (oh, horror!), you can find the callstack of the server embedded
in the error object's cargo slot.
By passing the error back to the client to handle, the error is displayed where
the problem most likely lays - if you attempt to write a record without locking
it first, the problem is in the client code not the server. This also makes the
server robust and resilient, allowing other connected clients to continue
working without interruption.
Rebuilding the Server and dbfSocket.dll
To rebuild either the server or dbfSocketC.dll, you will require ASINET1C.LIB,
which is part of the Professional Subscription.
ASINET1C.DLL, which is part of the Professional Subscription redistributable
libraries, is supplied with the package, and should be placed in your RUNTIME
directory or somewhere in your PATH.
GNU GENERAL
_______________________________________________
Harbour mailing list
[email protected]
http://lists.harbour-project.org/mailman/listinfo/harbour