Fortunately, there are already some primitive engines we can just instantiate, and see what they are doing. The function connector creates an engine that connects to a TCP service in the network, and returns the connected socket as result:
val connector : ?proxy:#client_socket_connector -> connect_address -> Unixqueue.event_system -> connect_status engineTo create and setup the engine, just call this function, as in:
let ues = Unixqueue.create_unix_event_system() in let addr = `Socket(`Sock_inet_byname(Unix.SOCK_STREAM, "www.npc.de", 80)) in let eng = connector addr ues in ...The engine will connect to the web server (port 80) on www.npc.de. It has added handlers and resources to the event system ues such that the action of connecting will be triggered when Unixqueue.run becomes active. To see the effect, just activate the event system:
Unixqueue.run uesWhen the connection is established, eng#state changes to `Done(`Socket(fd,addr)) where fd is the socket, and addr is the logical address of the client socket (which may be different than the physical address because connect supports network proxies). It is also possible that the state changes to `Error e where e is the problematic exception. Note that there is no timeout value; to limit the time of engine actions one has to attach a watchdog to the engine.
This is not yet very impressive, because we have only a single engine. As mentioned, engines run in parallel, so we can connect to several web services in parallel by just creating several engines:
let ues = Unixqueue.create_unix_event_system() in let addr1 = `Socket(`Sock_inet_byname(Unix.SOCK_STREAM, "www.npc.de", 80)) in let addr2 = `Socket(`Sock_inet_byname(Unix.SOCK_STREAM, "caml.inria.fr", 80)) in let addr3 = `Socket(`Sock_inet_byname(Unix.SOCK_STREAM, "ocaml-programming.de", 80)) in let eng1 = connector addr1 ues in let eng2 = connector addr2 ues in let eng3 = connector addr3 ues in Unixqueue.run uesNote that the resolution of DNS names is not done in the background, and may block the whole event system for a moment.
As a variant, we can also connect to one service after the other:
let eng1 = connector addr1 ues in let eng123 = new seq_engine eng1 (fun result1 -> let eng2 = connector addr2 ues in new seq_engine eng2 (fun result2 -> let eng3 = connector addr3 ues in eng3)))The constructor for sequential engine execution, seq_engine, expects one engine and a function as arguments. When the engine is done, the function is invoked with the result of the engine, and the function must return a second engine. The result of seq_engine is the result of the second engine.
In these examples, we have called Unixqueue.run to start the event system. This function returns when all actions are completed; this implies that finally all engines are synchronized again (i.e. in a final state). We can also synchronize in the middle of the execution by using sync_engine. In the following code snipped, two services are connected in parallel, and when both connections have been established, a third connection is started:
let eng1 = connector addr1 ues in let eng2 = connector addr2 ues in let eng12 = new sync_engine eng1 eng2 in let eng123 = new seq_engine eng12 (fun result12 -> let eng3 = connector addr3 ues in eng3)