9. Client/Server


Now that we have seen how transactions work in db4o conceptually, we are prepared to tackle concurrently executing transactions.

We start by preparing our database in the familiar way.

[setFirstCar]
Dim pilot As Pilot = New Pilot("Rubens Barrichello", 99)
Dim car As Car = New Car("BMW")
car.Pilot = pilot
db.[Set](car)


[setSecondCar]
Dim pilot As Pilot = New Pilot("Michael Schumacher", 100)
Dim car As Car = New Car("Ferrari")
car.Pilot = pilot
db.[Set](car)



    9.1. Embedded server


    From the API side, there's no real difference between transactions executing concurrently within the same VM and transactions executed against a remote server. To use concurrent transactions within a single VM, we just open a db4o server on our database file, directing it to run on port 0, thereby declaring that no networking will take place.

    [accessLocalServer]
    Dim server As ObjectServer = Db4oFactory.OpenServer(Util.YapFileName, 0)
    Try
        Dim client As ObjectContainer = server.OpenClient()
        ' Do something with this client, or open more clients
        client.Close()
    Finally
        server.Close()
    End Try


    Again, we will delegate opening and closing the server to our environment to focus on client interactions.

    [queryLocalServer]
    Dim client As ObjectContainer = server.OpenClient()
    ListResult(client.[Get](New Car(Nothing)))
    client.Close()
    OUTPUT:
    2
    Ferrari[Michael Schumacher/100]/0
    BMW[Rubens Barrichello/99]/0
    [db4o 5.5.1   2006-07-05 10:33:50]
    Connection closed by client %.
    [db4o 5.5.1   2006-07-05 10:33:51]
    '/tmp/formula158157.yap' close request
    [db4o 5.5.1   2006-07-05 10:33:51]
    '/tmp/formula158157.yap' closed


The transaction level in db4o is read committed . However, each client container maintains its own weak reference cache of already known objects. To make all changes committed by other clients immediately, we have to explicitly refresh known objects from the server. We will delegate this task to a specialized version of our listResult() method.

Public Shared Sub ListRefreshedResult(ByVal container As ObjectContainer, ByVal items As ObjectSet, ByVal depth As Integer)
    Console.WriteLine(items.Count)
    For Each item As Object In items
        container.Ext().Refresh(item, depth)
        Console.WriteLine(item)
    Next
End Sub


[demonstrateLocalReadCommitted]
Dim client1 As ObjectContainer = server.OpenClient()
Dim client2 As ObjectContainer = server.OpenClient()
Dim pilot As Pilot = New Pilot("David Coulthard", 98)
Dim result As ObjectSet = client1.[Get](New Car("BMW"))
Dim car As Car = DirectCast(result.[Next](), Car)
car.Pilot = pilot
client1.[Set](car)
ListResult(client1.[Get](New Car(Nothing)))
ListResult(client2.[Get](New Car(Nothing)))
client1.Commit()
ListResult(client1.[Get](GetType(Car)))
ListRefreshedResult(client2, client2.[Get](GetType(Car)), 2)
client1.Close()
client2.Close()
OUTPUT:
2
Ferrari[Michael Schumacher/100]/0
BMW[David Coulthard/98]/0
2
Ferrari[Michael Schumacher/100]/0
BMW[Rubens Barrichello/99]/0
2
Ferrari[Michael Schumacher/100]/0
BMW[David Coulthard/98]/0
2
Ferrari[Michael Schumacher/100]/0
BMW[David Coulthard/98]/0
[db4o 5.5.1   2006-07-05 10:33:51]
Connection closed by client %.
[db4o 5.5.1   2006-07-05 10:33:51]
Connection closed by client %.
[db4o 5.5.1   2006-07-05 10:33:51]
'/tmp/formula158157.yap' close request
[db4o 5.5.1   2006-07-05 10:33:51]
'/tmp/formula158157.yap' closed


Simple rollbacks just work as you might expect now.

[demonstrateLocalRollback]
Dim client1 As ObjectContainer = server.OpenClient()
Dim client2 As ObjectContainer = server.OpenClient()
Dim result As ObjectSet = client1.[Get](New Car("BMW"))
Dim car As Car = DirectCast(result.[Next](), Car)
car.Pilot = New Pilot("Someone else", 0)
client1.[Set](car)
ListResult(client1.[Get](New Car(Nothing)))
ListResult(client2.[Get](New Car(Nothing)))
client1.Rollback()
client1.Ext().Refresh(car, 2)
ListResult(client1.[Get](New Car(Nothing)))
ListResult(client2.[Get](New Car(Nothing)))
client1.Close()
client2.Close()
OUTPUT:
2
Ferrari[Michael Schumacher/100]/0
BMW[Someone else/0]/0
2
Ferrari[Michael Schumacher/100]/0
BMW[David Coulthard/98]/0
2
Ferrari[Michael Schumacher/100]/0
BMW[David Coulthard/98]/0
2
Ferrari[Michael Schumacher/100]/0
BMW[David Coulthard/98]/0
[db4o 5.5.1   2006-07-05 10:33:51]
[db4o 5.5.1   2006-07-05 10:33:51]
Connection closed by client %.
Connection closed by client %.
[db4o 5.5.1   2006-07-05 10:33:51]
'/tmp/formula158157.yap' close request
[db4o 5.5.1   2006-07-05 10:33:51]
'/tmp/formula158157.yap' closed



9.2. Networking


From here it's only a small step towards operating db4o over a TCP/IP network. We just specify a port number greater than zero and set up one or more accounts for our client(s).

[accessRemoteServer]
Dim server As ObjectServer = Db4oFactory.OpenServer(Util.YapFileName, ServerPort)
server.GrantAccess(ServerUser, ServerPassword)
Try
    Dim client As ObjectContainer = Db4oFactory.OpenClient("localhost", ServerPort, ServerUser, ServerPassword)
    ' Do something with this client, or open more clients
    client.Close()
Finally
    server.Close()
End Try


The client connects providing host, port, user name and password.

[queryRemoteServer]
Dim client As ObjectContainer = Db4oFactory.OpenClient("localhost", port, user, password)
ListResult(client.[Get](New Car(Nothing)))
client.Close()
OUTPUT:
[db4o 5.5.1   2006-07-05 10:33:52]
Server listening on port: '56128'
[db4o 5.5.1   2006-07-05 10:33:52]
Client 'user' connected.
2
Ferrari[Michael Schumacher/100]/0
BMW[David Coulthard/98]/0
[db4o 5.5.1   2006-07-05 10:33:52]
Connection closed by client 'user'.
[db4o 5.5.1   2006-07-05 10:33:52]
'/tmp/formula158157.yap' close request
[db4o 5.5.1   2006-07-05 10:33:52]
'/tmp/formula158157.yap' closed


Everything else is absolutely identical to the local server examples above.

[demonstrateRemoteReadCommitted]
Dim client1 As ObjectContainer = Db4oFactory.OpenClient("localhost", port, user, password)
Dim client2 As ObjectContainer = Db4oFactory.OpenClient("localhost", port, user, password)
Dim pilot As Pilot = New Pilot("Jenson Button", 97)
Dim result As ObjectSet = client1.[Get](New Car(Nothing))
Dim car As Car = DirectCast(result.[Next](), Car)
car.Pilot = pilot
client1.[Set](car)
ListResult(client1.[Get](New Car(Nothing)))
ListResult(client2.[Get](New Car(Nothing)))
client1.Commit()
ListResult(client1.[Get](New Car(Nothing)))
ListResult(client2.[Get](New Car(Nothing)))
client1.Close()
client2.Close()
OUTPUT:
[db4o 5.5.1   2006-07-05 10:33:52]
Server listening on port: '56128'
[db4o 5.5.1   2006-07-05 10:33:52]
Client 'user' connected.
[db4o 5.5.1   2006-07-05 10:33:52]
Client 'user' connected.
2
Ferrari[Jenson Button/97]/0
BMW[David Coulthard/98]/0
2
Ferrari[Michael Schumacher/100]/0
BMW[David Coulthard/98]/0
2
Ferrari[Jenson Button/97]/0
BMW[David Coulthard/98]/0
2
Ferrari[Jenson Button/97]/0
BMW[David Coulthard/98]/0
[db4o 5.5.1   2006-07-05 10:33:53]
Connection closed by client 'user'.
[db4o 5.5.1   2006-07-05 10:33:53]
Connection closed by client 'user'.
[db4o 5.5.1   2006-07-05 10:33:53]
'/tmp/formula158157.yap' close request
[db4o 5.5.1   2006-07-05 10:33:53]
'/tmp/formula158157.yap' closed


[demonstrateRemoteRollback]
Dim client1 As ObjectContainer = Db4oFactory.OpenClient("localhost", port, user, password)
Dim client2 As ObjectContainer = Db4oFactory.OpenClient("localhost", port, user, password)
Dim result As ObjectSet = client1.[Get](New Car(Nothing))
Dim car As Car = DirectCast(result.[Next](), Car)
car.Pilot = New Pilot("Someone else", 0)
client1.[Set](car)
ListResult(client1.[Get](New Car(Nothing)))
ListResult(client2.[Get](New Car(Nothing)))
client1.Rollback()
client1.Ext().Refresh(car, 2)
ListResult(client1.[Get](New Car(Nothing)))
ListResult(client2.[Get](New Car(Nothing)))
client1.Close()
client2.Close()
OUTPUT:
[db4o 5.5.1   2006-07-05 10:33:54]
Server listening on port: '56128'
[db4o 5.5.1   2006-07-05 10:33:54]
Client 'user' connected.
[db4o 5.5.1   2006-07-05 10:33:54]
Client 'user' connected.
2
Ferrari[Someone else/0]/0
BMW[David Coulthard/98]/0
2
Ferrari[Jenson Button/97]/0
BMW[David Coulthard/98]/0
2
Ferrari[Jenson Button/97]/0
BMW[David Coulthard/98]/0
2
Ferrari[Jenson Button/97]/0
BMW[David Coulthard/98]/0
[db4o 5.5.1   2006-07-05 10:33:54]
Connection closed by client 'user'.
[db4o 5.5.1   2006-07-05 10:33:54]
Connection closed by client 'user'.
[db4o 5.5.1   2006-07-05 10:33:54]
'/tmp/formula158157.yap' close request
[db4o 5.5.1   2006-07-05 10:33:54]
'/tmp/formula158157.yap' closed





9.3. Out-of-band signalling


Sometimes a client needs to send a special message to a server in order to tell the server to do something.  The server may need to be signalled to perform a defragment or it may need to be signalled to shut itself down gracefully.

This is configured by calling setMessageRecipient(), passing the object that will process client-initiated messages.

Public Sub RunServer()
    SyncLock Me
        Dim db4oServer As ObjectServer = Db4oFactory.OpenServer(FILE, PORT)
        db4oServer.GrantAccess(USER, PASS)
        ' Using the messaging functionality to redirect all
        ' messages to this.processMessage
        db4oServer.Ext().Configure().SetMessageRecipient(Me)
        Try
            If Not [stop] Then
                ' wait forever for Notify() from Close()
                Monitor.Wait(Me)
            End If
        Catch e As Exception
            Console.WriteLine(e.ToString())
        End Try
        db4oServer.Close()
    End SyncLock
End Sub


The message is received and processed by a processMessage() method:

Public Sub ProcessMessage(ByVal con As ObjectContainer, ByVal message As Object) Implements MessageRecipient.ProcessMessage
    If TypeOf message Is StopServer Then
        Close()
    End If
End Sub


Db4o allows a client to send an arbitrary signal or message to a server by sending a plain Java object to the server.  The server will receive a callback message, including the object that came from the client. The server can interpret this message however it wants.

Public Shared Sub Main(ByVal args As String())
    Dim objectContainer As ObjectContainer = Nothing
    Try
        ' connect to the server
        objectContainer = Db4oFactory.OpenClient(HOST, PORT, USER, PASS)
    Catch e As Exception
        Console.WriteLine(e.ToString())
    End Try
    If Not objectContainer Is Nothing Then
        ' get the messageSender for the ObjectContainer
        Dim messageSender As MessageSender = objectContainer.Ext().Configure().GetMessageSender()
        ' send an instance of a StopServer object
        messageSender.Send(New StopServer())
        ' close the ObjectContainer
        objectContainer.Close()
    End If
End Sub



9.4. Putting it all together: a simple but complete db4o server


Let's put all of this information together now to implement a simple standalone db4o server with a special client that can tell the server to shut itself down gracefully on demand.

First, both the client and the server need some shared configuration information.  We will provide this using an interface:

Namespace com.db4o.f1.chapter5
    ''' <summary>
    ''' Configuration used for StartServer and StopServer.
    ''' </summary>
    Public Class ServerConfiguration
        ''' <summary>
        ''' the host to be used.
        ''' If you want to run the client server examples on two computers,
        ''' enter the computer name of the one that you want to use as server.
        ''' </summary>
        Public Const HOST As String = "localhost"
        ''' <summary>
        ''' the database file to be used by the server.
        ''' </summary>
        Public Const FILE As String = "formula1.yap"
        ''' <summary>
        ''' the port to be used by the server.
        ''' </summary>
        Public Const PORT As Integer = 4488
        ''' <summary>
        ''' the user name for access control.
        ''' </summary>
        Public Const USER As String = "db4o"
        ''' <summary>
        ''' the pasword for access control.
        ''' </summary>
        Public Const PASS As String = "db4o"
    End Class
End Namespace


Now we'll create the server:

Imports System
Imports System.Threading
Imports com.db4o
Imports com.db4o.messaging
Namespace com.db4o.f1.chapter5
    ''' <summary>
    ''' starts a db4o server with the settings from ServerConfiguration.
    ''' This is a typical setup for a long running server.
    ''' The Server may be stopped from a remote location by running
    ''' StopServer. The StartServer instance is used as a MessageRecipient
    ''' and reacts to receiving an instance of a StopServer object.
    ''' Note that all user classes need to be present on the server side
    ''' and that all possible Db4o.Configure() calls to alter the db4o
    ''' configuration need to be executed on the client and on the server.
    ''' </summary>
    Public Class StartServer
    Inherits ServerConfiguration
        Implements MessageRecipient
        ''' <summary>
        ''' setting the value to true denotes that the server should be closed
        ''' </summary>
        Private [stop] As Boolean = False
        ''' <summary>
        ''' starts a db4o server using the configuration from
        ''' ServerConfiguration.
        ''' </summary>
        Public Shared Sub Main(ByVal arguments As String())
            Dim server As New StartServer
            server.RunServer()
        End Sub
        ''' <summary>
        ''' opens the ObjectServer, and waits forever until Close() is called
        ''' or a StopServer message is being received.
        ''' </summary>
        Public Sub RunServer()
            SyncLock Me
                Dim db4oServer As ObjectServer = Db4oFactory.OpenServer(FILE, PORT)
                db4oServer.GrantAccess(USER, PASS)
                ' Using the messaging functionality to redirect all
                ' messages to this.processMessage
                db4oServer.Ext().Configure().SetMessageRecipient(Me)
                Try
                    If Not [stop] Then
                        ' wait forever for Notify() from Close()
                        Monitor.Wait(Me)
                    End If
                Catch e As Exception
                    Console.WriteLine(e.ToString())
                End Try
                db4oServer.Close()
            End SyncLock
        End Sub
        ''' <summary>
        ''' messaging callback
        ''' see com.db4o.messaging.MessageRecipient#ProcessMessage()
        ''' </summary>
        Public Sub ProcessMessage(ByVal con As ObjectContainer, ByVal message As Object) Implements MessageRecipient.ProcessMessage
            If TypeOf message Is StopServer Then
                Close()
            End If
        End Sub
        ''' <summary>
        ''' closes this server.
        ''' </summary>
        Public Sub Close()
            SyncLock Me
                [stop] = True
                Monitor.PulseAll(Me)
            End SyncLock
        End Sub
    End Class
End Namespace


And last but not least, the client that stops the server.

Imports System
Imports com.db4o
Imports com.db4o.messaging
Namespace com.db4o.f1.chapter5
    ''' <summary>
    ''' stops the db4o Server started with StartServer.
    ''' This is done by opening a client connection
    ''' to the server and by sending a StopServer object as
    ''' a message. StartServer will react in it's
    ''' processMessage method.
    ''' </summary>
    Public Class StopServer
    Inherits ServerConfiguration
        ''' <summary>
        ''' stops a db4o Server started with StartServer.
        ''' </summary>
        ''' <exception cref="Exception" />
        Public Shared Sub Main(ByVal args As String())
            Dim objectContainer As ObjectContainer = Nothing
            Try
                ' connect to the server
                objectContainer = Db4oFactory.OpenClient(HOST, PORT, USER, PASS)
            Catch e As Exception
                Console.WriteLine(e.ToString())
            End Try
            If Not objectContainer Is Nothing Then
                ' get the messageSender for the ObjectContainer
                Dim messageSender As MessageSender = objectContainer.Ext().Configure().GetMessageSender()
                ' send an instance of a StopServer object
                messageSender.Send(New StopServer())
                ' close the ObjectContainer
                objectContainer.Close()
            End If
        End Sub
    End Class
End Namespace



9.5. Conclusion


That's it, folks. No, of course it isn't. There's much more to db4o we haven't covered yet: schema evolution, custom persistence for your classes, writing your own query objects, etc. The following, more loosely coupled chapters will look into more advanced db4o features.

This tutorial is work in progress. We will successively add chapters and incorporate feedback from the community into the existing chapters.

We hope that this tutorial has helped to get you started with db4o. How should you continue now?

- Browse the remaining chapters.

-(Interactive version only)While this tutorial is basically sequential in nature, try to switch back and forth between the chapters and execute the sample snippets in arbitrary order. You will be working with the same database throughout; sometimes you may just get stuck or even induce exceptions, but you can always reset the database via the console window.

- The examples we've worked through are included in your db4o distribution in full source code. Feel free to experiment with it.

- I you're stuck, see if the FAQ can solve your problem, browse the information on our web site, check if your problem is submitted to Bugzilla or visit our forums at http://forums.db4o.com/forums/.



9.6. Full source


Imports System.IO
Imports com.db4o

Namespace com.db4o.f1.chapter5
    Public Class ClientServerExample
    Inherits Util
        Public Shared Sub Main(ByVal args As String())
            File.Delete(Util.YapFileName)
            AccessLocalServer()
            File.Delete(Util.YapFileName)
            Dim db As ObjectContainer = Db4oFactory.OpenFile(Util.YapFileName)
            Try
                SetFirstCar(db)
                SetSecondCar(db)
            Finally
                db.Close()
            End Try
            ConfigureDb4o()
            Dim server As ObjectServer = Db4oFactory.OpenServer(Util.YapFileName, 0)
            Try
                QueryLocalServer(server)
                DemonstrateLocalReadCommitted(server)
                DemonstrateLocalRollback(server)
            Finally
                server.Close()
            End Try
            AccessRemoteServer()
            server = Db4oFactory.OpenServer(Util.YapFileName, ServerPort)
            server.GrantAccess(ServerUser, ServerPassword)
            Try
                QueryRemoteServer(ServerPort, ServerUser, ServerPassword)
                DemonstrateRemoteReadCommitted(ServerPort, ServerUser, ServerPassword)
                DemonstrateRemoteRollback(ServerPort, ServerUser, ServerPassword)
            Finally
                server.Close()
            End Try
        End Sub
        Public Shared Sub SetFirstCar(ByVal db As ObjectContainer)
            Dim pilot As Pilot = New Pilot("Rubens Barrichello", 99)
            Dim car As Car = New Car("BMW")
            car.Pilot = pilot
            db.[Set](car)
        End Sub
        Public Shared Sub SetSecondCar(ByVal db As ObjectContainer)
            Dim pilot As Pilot = New Pilot("Michael Schumacher", 100)
            Dim car As Car = New Car("Ferrari")
            car.Pilot = pilot
            db.[Set](car)
        End Sub
        Public Shared Sub AccessLocalServer()
            Dim server As ObjectServer = Db4oFactory.OpenServer(Util.YapFileName, 0)
            Try
                Dim client As ObjectContainer = server.OpenClient()
                ' Do something with this client, or open more clients
                client.Close()
            Finally
                server.Close()
            End Try
        End Sub
        Public Shared Sub QueryLocalServer(ByVal server As ObjectServer)
            Dim client As ObjectContainer = server.OpenClient()
            ListResult(client.[Get](New Car(Nothing)))
            client.Close()
        End Sub
        Public Shared Sub ConfigureDb4o()
            Db4oFactory.Configure().ObjectClass(GetType(Car)).UpdateDepth(3)
        End Sub
        Public Shared Sub DemonstrateLocalReadCommitted(ByVal server As ObjectServer)
            Dim client1 As ObjectContainer = server.OpenClient()
            Dim client2 As ObjectContainer = server.OpenClient()
            Dim pilot As Pilot = New Pilot("David Coulthard", 98)
            Dim result As ObjectSet = client1.[Get](New Car("BMW"))
            Dim car As Car = DirectCast(result.[Next](), Car)
            car.Pilot = pilot
            client1.[Set](car)
            ListResult(client1.[Get](New Car(Nothing)))
            ListResult(client2.[Get](New Car(Nothing)))
            client1.Commit()
            ListResult(client1.[Get](GetType(Car)))
            ListRefreshedResult(client2, client2.[Get](GetType(Car)), 2)
            client1.Close()
            client2.Close()
        End Sub
        Public Shared Sub DemonstrateLocalRollback(ByVal server As ObjectServer)
            Dim client1 As ObjectContainer = server.OpenClient()
            Dim client2 As ObjectContainer = server.OpenClient()
            Dim result As ObjectSet = client1.[Get](New Car("BMW"))
            Dim car As Car = DirectCast(result.[Next](), Car)
            car.Pilot = New Pilot("Someone else", 0)
            client1.[Set](car)
            ListResult(client1.[Get](New Car(Nothing)))
            ListResult(client2.[Get](New Car(Nothing)))
            client1.Rollback()
            client1.Ext().Refresh(car, 2)
            ListResult(client1.[Get](New Car(Nothing)))
            ListResult(client2.[Get](New Car(Nothing)))
            client1.Close()
            client2.Close()
        End Sub
        Public Shared Sub AccessRemoteServer()
            Dim server As ObjectServer = Db4oFactory.OpenServer(Util.YapFileName, ServerPort)
            server.GrantAccess(ServerUser, ServerPassword)
            Try
                Dim client As ObjectContainer = Db4oFactory.OpenClient("localhost", ServerPort, ServerUser, ServerPassword)
                ' Do something with this client, or open more clients
                client.Close()
            Finally
                server.Close()
            End Try
        End Sub
        Public Shared Sub QueryRemoteServer(ByVal port As Integer, ByVal user As String, ByVal password As String)
            Dim client As ObjectContainer = Db4oFactory.OpenClient("localhost", port, user, password)
            ListResult(client.[Get](New Car(Nothing)))
            client.Close()
        End Sub
        Public Shared Sub DemonstrateRemoteReadCommitted(ByVal port As Integer, ByVal user As String, ByVal password As String)
            Dim client1 As ObjectContainer = Db4oFactory.OpenClient("localhost", port, user, password)
            Dim client2 As ObjectContainer = Db4oFactory.OpenClient("localhost", port, user, password)
            Dim pilot As Pilot = New Pilot("Jenson Button", 97)
            Dim result As ObjectSet = client1.[Get](New Car(Nothing))
            Dim car As Car = DirectCast(result.[Next](), Car)
            car.Pilot = pilot
            client1.[Set](car)
            ListResult(client1.[Get](New Car(Nothing)))
            ListResult(client2.[Get](New Car(Nothing)))
            client1.Commit()
            ListResult(client1.[Get](New Car(Nothing)))
            ListResult(client2.[Get](New Car(Nothing)))
            client1.Close()
            client2.Close()
        End Sub
        Public Shared Sub DemonstrateRemoteRollback(ByVal port As Integer, ByVal user As String, ByVal password As String)
            Dim client1 As ObjectContainer = Db4oFactory.OpenClient("localhost", port, user, password)
            Dim client2 As ObjectContainer = Db4oFactory.OpenClient("localhost", port, user, password)
            Dim result As ObjectSet = client1.[Get](New Car(Nothing))
            Dim car As Car = DirectCast(result.[Next](), Car)
            car.Pilot = New Pilot("Someone else", 0)
            client1.[Set](car)
            ListResult(client1.[Get](New Car(Nothing)))
            ListResult(client2.[Get](New Car(Nothing)))
            client1.Rollback()
            client1.Ext().Refresh(car, 2)
            ListResult(client1.[Get](New Car(Nothing)))
            ListResult(client2.[Get](New Car(Nothing)))
            client1.Close()
            client2.Close()
        End Sub
    End Class
End Namespace




--
generated by
Doctor courtesy of db4objects Inc.