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

Reply via email to