|
11. 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.
11.1. Embedded server
From the API side, there's no real difference between transactions executing concurrently within the same runtime and transactions executed against a remote server. To use concurrent transactions within a single runtime , 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
IObjectServer server = Db4oFactory.OpenServer(Util.YapFileName, 0);
try
{
IObjectContainer client = server.OpenClient();
// Do something with this client, or open more clients
client.Close();
}
finally
{
server.Close();
} |
Again, we will delegate opening and closing the server to our environment to focus on client interactions.
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 static void ListRefreshedResult(IObjectContainer container, IObjectSet items, int depth)
{
Console.WriteLine(items.Count);
foreach (object item in items)
{
container.Ext().Refresh(item, depth);
Console.WriteLine(item);
}
} |
Simple rollbacks just work as you might expect now.
11.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
IObjectServer server = Db4oFactory.OpenServer(Util.YapFileName, ServerPort);
server.GrantAccess(ServerUser, ServerPassword);
try
{
IObjectContainer client = Db4oFactory.OpenClient("localhost", ServerPort, ServerUser, ServerPassword);
// Do something with this client, or open more clients
client.Close();
}
finally
{
server.Close();
} |
The client connects providing host, port, user name and password.
Everything else is absolutely identical to the local server examples above.
11.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 void RunServer()
{
lock(this)
{
IObjectServer db4oServer = Db4oFactory.OpenServer(FILE, PORT);
db4oServer.GrantAccess(USER, PASS);
// Using the messaging functionality to redirect all
// messages to this.processMessage
db4oServer.Ext().Configure().ClientServer().SetMessageRecipient(this);
try
{
if (! stop)
{
// wait forever until Close will change stop variable
Monitor.Wait(this);
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
db4oServer.Close();
}
} |
The message is received and processed by a PROCESSMESSAGE() method:
public void ProcessMessage(IMessageContext con, object message)
{
if (message is StopServer)
{
Close();
}
} |
Db4o allows a client to send an arbitrary signal or message to a server by sending a plain 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 static void Main(string[] args)
{
IObjectContainer IObjectContainer = null;
try
{
// connect to the server
IObjectContainer = Db4oFactory.OpenClient(HOST, PORT, USER, PASS);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
if (IObjectContainer != null)
{
// get the messageSender for the IObjectContainer
IMessageSender messageSender = IObjectContainer.Ext()
.Configure().ClientServer().GetMessageSender();
// send an instance of a StopServer object
messageSender.Send(new StopServer());
// close the IObjectContainer
IObjectContainer.Close();
}
} |
11.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 Db4objects.Db4o.Tutorial.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 string HOST = "localhost";
/// <summary>
/// the database file to be used by the server.
/// </summary>
public const string FILE = "formula1.yap";
/// <summary>
/// the port to be used by the server.
/// </summary>
public const int PORT = 4488;
/// <summary>
/// the user name for access control.
/// </summary>
public const string USER = "db4o";
/// <summary>
/// the pasword for access control.
/// </summary>
public const string PASS = "db4o";
}
}
|
Now we'll create the server:
using System;
using System.Threading;
using Db4objects.Db4o;
using Db4objects.Db4o.Messaging;
namespace Db4objects.Db4o.Tutorial.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 Db4oFactory.Configure() calls to alter the db4o
/// configuration need to be executed on the client and on the server.
/// </summary>
public class StartServer : ServerConfiguration, IMessageRecipient
{
/// <summary>
/// setting the value to true denotes that the server should be closed
/// </summary>
private bool stop = false;
/// <summary>
/// starts a db4o server using the configuration from
/// ServerConfiguration.
/// </summary>
public static void Main(string[] arguments)
{
new StartServer().RunServer();
}
/// <summary>
/// opens the IObjectServer, and waits forever until Close() is called
/// or a StopServer message is being received.
/// </summary>
public void RunServer()
{
lock(this)
{
IObjectServer db4oServer = Db4oFactory.OpenServer(FILE, PORT);
db4oServer.GrantAccess(USER, PASS);
// Using the messaging functionality to redirect all
// messages to this.processMessage
db4oServer.Ext().Configure().ClientServer().SetMessageRecipient(this);
try
{
if (! stop)
{
// wait forever until Close will change stop variable
Monitor.Wait(this);
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
db4oServer.Close();
}
}
/// <summary>
/// messaging callback
/// see com.db4o.messaging.MessageRecipient#ProcessMessage()
/// </summary>
public void ProcessMessage(IMessageContext con, object message)
{
if (message is StopServer)
{
Close();
}
}
/// <summary>
/// closes this server.
/// </summary>
public void Close()
{
lock(this)
{
stop = true;
Monitor.PulseAll(this);
}
}
}
}
|
And last but not least, the client that stops the server.
using System;
using Db4objects.Db4o;
using Db4objects.Db4o.Messaging;
namespace Db4objects.Db4o.Tutorial.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 : ServerConfiguration
{
/// <summary>
/// stops a db4o Server started with StartServer.
/// </summary>
/// <exception cref="Exception" />
public static void Main(string[] args)
{
IObjectContainer IObjectContainer = null;
try
{
// connect to the server
IObjectContainer = Db4oFactory.OpenClient(HOST, PORT, USER, PASS);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
if (IObjectContainer != null)
{
// get the messageSender for the IObjectContainer
IMessageSender messageSender = IObjectContainer.Ext()
.Configure().ClientServer().GetMessageSender();
// send an instance of a StopServer object
messageSender.Send(new StopServer());
// close the IObjectContainer
IObjectContainer.Close();
}
}
}
}
|
11.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. A much more thorough documentation is provided in the reference that you should have also received with the download or online.
We hope that this tutorial has helped to get you started with db4o. How should you continue now?
- You could browse the remaining chapters. They are a selection of themes from the reference that very frequently come up as questions in our http://forums.db4o.com/forums/.
-(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 Jira or visit our forums at http://forums.db4o.com/forums/.
11.6. Full source
using System.IO;
using Db4objects.Db4o;
namespace Db4objects.Db4o.Tutorial.F1.Chapter5
{
public class ClientServerExample : Util
{
public static void Main(string[] args)
{
File.Delete(Util.YapFileName);
AccessLocalServer();
File.Delete(Util.YapFileName);
IObjectContainer db = Db4oFactory.OpenFile(Util.YapFileName);
try
{
SetFirstCar(db);
SetSecondCar(db);
}
finally
{
db.Close();
}
ConfigureDb4o();
IObjectServer server = Db4oFactory.OpenServer(Util.YapFileName, 0);
try
{
QueryLocalServer(server);
DemonstrateLocalReadCommitted(server);
DemonstrateLocalRollback(server);
}
finally
{
server.Close();
}
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();
}
}
public static void SetFirstCar(IObjectContainer db)
{
Pilot pilot = new Pilot("Rubens Barrichello", 99);
Car car = new Car("BMW");
car.Pilot = pilot;
db.Store(car);
}
public static void SetSecondCar(IObjectContainer db)
{
Pilot pilot = new Pilot("Michael Schumacher", 100);
Car car = new Car("Ferrari");
car.Pilot = pilot;
db.Store(car);
}
public static void AccessLocalServer()
{
IObjectServer server = Db4oFactory.OpenServer(Util.YapFileName, 0);
try
{
IObjectContainer client = server.OpenClient();
// Do something with this client, or open more clients
client.Close();
}
finally
{
server.Close();
}
}
public static void QueryLocalServer(IObjectServer server)
{
IObjectContainer client = server.OpenClient();
ListResult(client.QueryByExample(new Car(null)));
client.Close();
}
public static void ConfigureDb4o()
{
Db4oFactory.Configure().ObjectClass(typeof(Car)).UpdateDepth(3);
}
public static void DemonstrateLocalReadCommitted(IObjectServer server)
{
IObjectContainer client1 =server.OpenClient();
IObjectContainer client2 =server.OpenClient();
Pilot pilot = new Pilot("David Coulthard", 98);
IObjectSet result = client1.QueryByExample(new Car("BMW"));
Car car = (Car)result.Next();
car.Pilot = pilot;
client1.Store(car);
ListResult(client1.QueryByExample(new Car(null)));
ListResult(client2.QueryByExample(new Car(null)));
client1.Commit();
ListResult(client1.QueryByExample(typeof(Car)));
ListRefreshedResult(client2, client2.QueryByExample(typeof(Car)), 2);
client1.Close();
client2.Close();
}
public static void DemonstrateLocalRollback(IObjectServer server)
{
IObjectContainer client1 = server.OpenClient();
IObjectContainer client2 = server.OpenClient();
IObjectSet result = client1.QueryByExample(new Car("BMW"));
Car car = (Car)result.Next();
car.Pilot = new Pilot("Someone else", 0);
client1.Store(car);
ListResult(client1.QueryByExample(new Car(null)));
ListResult(client2.QueryByExample(new Car(null)));
client1.Rollback();
client1.Ext().Refresh(car, 2);
ListResult(client1.QueryByExample(new Car(null)));
ListResult(client2.QueryByExample(new Car(null)));
client1.Close();
client2.Close();
}
public static void AccessRemoteServer()
{
IObjectServer server = Db4oFactory.OpenServer(Util.YapFileName, ServerPort);
server.GrantAccess(ServerUser, ServerPassword);
try
{
IObjectContainer client = Db4oFactory.OpenClient("localhost", ServerPort, ServerUser, ServerPassword);
// Do something with this client, or open more clients
client.Close();
}
finally
{
server.Close();
}
}
public static void QueryRemoteServer(int port, string user, string password)
{
IObjectContainer client = Db4oFactory.OpenClient("localhost", port, user, password);
ListResult(client.QueryByExample(new Car(null)));
client.Close();
}
public static void DemonstrateRemoteReadCommitted(int port, string user, string password)
{
IObjectContainer client1 = Db4oFactory.OpenClient("localhost", port, user, password);
IObjectContainer client2 = Db4oFactory.OpenClient("localhost", port, user, password);
Pilot pilot = new Pilot("Jenson Button", 97);
IObjectSet result = client1.QueryByExample(new Car(null));
Car car = (Car)result.Next();
car.Pilot = pilot;
client1.Store(car);
ListResult(client1.QueryByExample(new Car(null)));
ListResult(client2.QueryByExample(new Car(null)));
client1.Commit();
ListResult(client1.QueryByExample(new Car(null)));
ListResult(client2.QueryByExample(new Car(null)));
client1.Close();
client2.Close();
}
public static void DemonstrateRemoteRollback(int port, string user, string password)
{
IObjectContainer client1 = Db4oFactory.OpenClient("localhost", port, user, password);
IObjectContainer client2 = Db4oFactory.OpenClient("localhost", port, user, password);
IObjectSet result = client1.QueryByExample(new Car(null));
Car car = (Car)result.Next();
car.Pilot = new Pilot("Someone else", 0);
client1.Store(car);
ListResult(client1.QueryByExample(new Car(null)));
ListResult(client2.QueryByExample(new Car(null)));
client1.Rollback();
client1.Ext().Refresh(car,2);
ListResult(client1.QueryByExample(new Car(null)));
ListResult(client2.QueryByExample(new Car(null)));
client1.Close();
client2.Close();
}
}
}
|
www.db4o.com
|