Handles, Eventers and Events
This guide is mostly ment for people who want to delve deeper into lNet’s workings, but can be useful for everyone as it explains the inner workings of events in the library. As mentioned in the previous chapter, lNet consists of Sockets, Eventers, Protocols and Sessions. This guide will look at the first two.
First things first tho.. what’s a handle? A handle is the absolute basic abstraction above OS handles, that is file descriptors, sockets and so on. On the cross-platform level, only sockets are handles (because Windows does not allow otherwise), however on Unix platforms, the eventers can monitor also file descriptors, pipes and possibly others too. So a handle (TLHandle) is the base class for a socket (TLSocket), and provides the “low level” abstractions above generic handles (not socket-specific yet). Handles also happen to be a double-linked-list.
A little side-note about sockets. Since they are handles, they already inherit all the stuff, but they also add their own. Namely socket-specific methods and data, but also ANOTHER double-linked list. The handle-level linked list data (next/previous) is for the eventers. The socket level linked list data (nextsock, prevsock) is for the protocols (TCP and UDP mainly).
1. ALL sockets ARE handles, but NOT all handles must be sockets.
2. Handles are owned by the eventer, but created elsewhere (in case of sockets, by the protocols. The creator property of TLSocket is used to store who made the socket)
The eventer is used to handle all the handles and their events. It’s the object which watches all of them for events (OnReceive etc. are ultimately called by the eventer). There’s one abstract eventer and multiple concrete eventers for specific OSes. The idea is to use the best possible candidate function on given OS (decided runtime, so no need to compile for linux version xxx). For example, on linux the best candidate is the Epoll() function, but it only exists on kernel 2.6+. On FreeBSD the KQueue() is used. The common, but low performance version is the Select() which exists on just about everything. All the specific eventers you will find are just implementations above specific OS functions to get the best out of it.
For example, while the select eventer has to cycle through all sockets each iteration, the epoll and kqueue eventers only have to go through those sockets on which something actually happened.
The eventer’s main method is the CallAction() in which all this monitoring is being done. All eventers watch for 3 basic events. Error, Write and Read. At this level, higher level socket-specific events like OnAccept or OnConnect are not yet known. The eventer then calls the handle’s OnXXX method (OnRead, OnWrite or OnError) and whatever handler is assigned there will handle the events.
In case of lNet’s protocols the handlers are inside TLConnection and TLTcp/TLUdp. It is these handlers that decide exactly what happened on the socket and call the higher level event callbacks like OnConnect or OnReceive.
The eventer mechanism however is completely independant of the rest of lNet. Neither handles nor eventers know about sockets or anything above them. The good thing is, that you can (on non-windows platforms) add your own handles to be watched to the same eventer which you use for your sockets (and you should, for performance reasons, eventers should be shared).
So say you’re writing a web server, and want to also watch your html/php/whatever files, and some pipes (fastcgi or such). All you have to do is to create TLHandles around the file descriptors, assign OnRead, OnWrite and OnError and add them to the eventer to be watched.
Sharing is good: (only for non-visual lNet! Visual lNet eventer is already auto-shared)
Sharing eventers is another important aspect of lNet. Imagine you have an elaborate program, or server which has multiple protocols. Say, raw TCP, HTTP and FTP all in one. The best thing to do to get the most performance is to share one eventer accross all these protocols, so ALL the sockets of ALL the protocols will be handles in one syscall. There are two ways to do this.
1. Create the eventer (with the BestEventerClass() metaclass returning function) BEFORE using any of the protocols (calling Listen or Connect on them). Then assign the eventer to them and start using them. Don’t forget to free the eventer on end (after freeing the protocols) as this is the “manual” method.
2. Activate any of your protocols (call Connect or Listen, no need to wait for OnXXX tho) and assign it’s eventer to all the other protocols. This method is “automatic” as it doesn’t require memory management (when the last protocol is freed, the eventer is freed).
Also note that you need to only call CallAction on one of the protocols (or directly on the eventer) once per iteration (if you’re on non-visual lnet of course), otherwise you’re just calling the same thing multiple times (Protocol.CallAction calls Eventer.CallAction)