======================== Using the gensio library ======================== The gensio library is designed to provide a single abstraction over a large number of I/O and network devices. This file describes the basic concepts of using the library. For a heavily annotated example of this, see basic_server.c and basic_client.c in the examples directory. General Concepts ================ gensio has an object oriented interface that is event-driven. Synchronous interfaces are also available. You deal with two main objects in gensio: a gensio and a gensio accepter. A gensio provides a communication interface where you can connect, disconnect, write, receive, etc. A gensio accepter lets you receive incoming connections. If a connection comes in, it gives you a gensio. The interface is event-driven because it is, for the most part, completely non-blocking. If you open a gensio, you give it a callback that will be called when the connection is up, or the connection fails. Same for close. A write will return the number of bytes accepted, but it may not take all the bytes (or even any of the bytes) and the caller must account for that. The open and close interfaces have a secondary blocking interface for convenience. These end in _s. This is for convenience, but it's not necessary and use of these must be careful because you can't really use them from callbacks. Speaking of callbacks, data and information coming from gensio to the user is done with a function callback. Read data, and when the gensio is ready for write data comes back in a callback. A similar interface is used for calling from the user to the gensio layer, but it is hidden from the user. This sort of interface is easily extensible, new operations can be easily added without breaking old interfaces. The library provides several ways to create a gensio or gensio accepter. The main way is str_to_gensio() and str_to_gensio_accepter(). These provide a way to specify a stack of gensios or accepters as a string and build. In general, you should use this interface if you can. In general, interfaces that are not performance sensitive are string based. You will see this in gensio_control, and in auxiliary data in the read and write interface to control certain aspects of the write. The library also provides ways to set up your gensios by individually creating each one. In some situations this might be necessary, but it limits the ability to use new features of the gensio library as it gets extended. If a gensio supports multiple streams (like SCTP), stream numbers are passed in the auxdata with "stream=n". Streams are not individually flow controlled. Channels, on the other hand, are separate flows of data over the same connection. Channels are represented as separate gensios, and they can be individually flow controlled. Include Files ============= There are a few include files you might need to deal with when using gensios: gensio.h The main include files for gensios and gensio accepters. gensio_os_funcs.h The definition for an OS handler. argvutils.h Many gensio functions take an argv array, this is utilities for dealing with argvs. gensio_selector.h A definition for a default OS handler. These are for the most part documented in the man pages. For creating your own gensios, the following include files are available for you: gensio_class.h The main include file for creating your own gensio. gensio_base.h This handles a lot of the boiler plate for a gensio. Most of the standard gensios use this. It splits the gensio function into an optional filter, and a lower layer interface called an ll. gensio_ll_fd.h An ll that provides most of the boilerplate for dealing with a file descriptor. gensio_ll_gensio.h An ll that provides all that is necessary for stacking a gensio on top of another gensio. The filter gensios (telnet, ssl, etc.) use this as the ll. Each include file has lots of documentation about the individual calls and handlers. Errors ====== gensio has it's own set of errors to abstract it from the OS errors (named GE_xxx) and provide more flexibility in error reporting. These are in the gensio_err.h include file (automatically included from gensio.h) and may be translated from numbers to a meaningful string with gensio_err_to_str(). Zero is defined to be not an error. If an unrecognized operating system error occurs, GE_OSERR is returned and a log is reported through the OS handler log interface. OS Handler ========== One slightly annoying thing about gensio is that it requires you to provide an OS handler (struct gensio_os_funcs) to handle OS-type functions like memory allocation, mutexes, the ability to handle file descriptors, timers and time, and a few other things. The library does provide several OS handlers. You can call gensio_alloc_os_funcs() to allocate a default one for your system (POSIX or Windows). You can see that man page for more details. This will generally be the best performing option you have for your system. For POSIX systems, OS handlers for glib and TCL are available, allocated with gensio_glib_funcs_alloc() and gensio_tcl_funcs_alloc(). These really don't work very well, especially from a performance point of view, the APIs for glib and TCL are not well designed for what gensio does. TCL can only support single-threaded operation. glib multithreaded operation only has one thread at a time waiting for I/O. But they do work, and the tests are run with them. These are not available on Windows because of poor abstractions on glib and because of lack of motivation on TCL. But if you are using something else like X Windows, etc that has it's own event loop, you may need to adapt one for your needs. But the good thing is that you can do this, and integrate gensio with pretty much anything. There is also a waiter interface that provides a convenient way to wait for things to occur while running the event loop. This is how you generally enter the event loop, because it provides a convenient way to signal when you are done and need to leave the loop. Documentation for this is in:: include/gensio/gensio_os_funcs.h Threads ======= The gensio library fully supports threads and is completely thread-safe. However, it uses signals on POSIX system, and COM on Windows systems, so some setup is required. The "main" thread should call gensio_os_proc_setup() at startup and call gensio_os_proc_cleanup() when it is complete. This sets up signals and signal handlers, thread local storage on Windows, and other sorts of things. You can spawn new threads from a thread that is already set up using gensio_os_new_thread(). This gives you a basic OS thread and is configured properly for gensio. If you have a thread created by other means that you want to use in gensio, as long as the thread create another thread and doesn't do any blocking functions (any sort of wait, background processing, functions that end in _s like read_s, etc.) you don't have to set them up. That way, some external thread can write data, wake another thread, or do things like that. If an external thread needs to do those things, it should call gensio_os_thread_setup(). Signals ======= As mentioned in the threads section, the gensio library on Unix uses signals for inter-thread wakeups. I looked hard, and there's really no other way to do this cleanly. But Windows has a couple of signal-like things, too, and these are available in gensio, also. If you use gensio_alloc_os_funcs(), you will get an OS funcs using the passed in signal for IPC. You can pass in GENSIO_OS_FUNCS_DEFAULT_THREAD_SIGNAL for the signal if you want the default, which is SIGUSR1. The signal you use will be blocked and taken over by gensio, you can't use it. gensio also provides some generic handling for a few signals. On Unix, it will handle SIGHUP through the gensio_os_proc_register_reload_handler() function. On Windows and Unix you can use gensio_os_proce_register_term_handler(), which will handle termination requests (SIGINT, SIGTERM, SIGQUIT on Unix) and gensio_os_proc_register_winsize_handler() (SIGWINCH on Unix). How these come in through Windows is a little messier, but invisible to the user. All the callbacks from from a waiting routine's wait, *not* from the signal handler. That should simplify your life a lot. You can see the man pages for more details on all of these. Creating a gensio ================= Connecting gensios ------------------ To create a gensio, the general way to do this is to call ``str_to_gensio()`` with a properly formatted string. The string is formatted like so:: [([